Skip to content

Commit

Permalink
feat(react-scheduler): support scheduler localization capability (#1985)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaximKudriavtsev committed May 7, 2019
1 parent 568ba8a commit 286003e
Show file tree
Hide file tree
Showing 64 changed files with 836 additions and 217 deletions.
@@ -0,0 +1,61 @@
import * as React from 'react';
import Paper from '@material-ui/core/Paper';
import moment from 'moment';
import { ViewState } from '@devexpress/dx-react-scheduler';
import {
Scheduler,
WeekView,
Toolbar,
Appointments,
} from '@devexpress/dx-react-scheduler-material-ui';
import { appointments } from '../../../demo-data/appointments';

const formatDayScaleDate = (date, options) => {
const momentDate = moment(date);
const { weekday } = options;
return momentDate.format(weekday ? 'dddd' : 'D');
};
const formatTimeScaleDate = date => moment(date).format('hh:mm:ss');

const DayScaleCell = (
{ formatDate, ...restProps },
) => <WeekView.DayScaleCell {...restProps} formatDate={formatDayScaleDate} />;
const TimeScaleCell = (
{ formatDate, ...restProps },
) => <WeekView.TimeScaleCell {...restProps} formatDate={formatTimeScaleDate} />;

export default class Demo extends React.PureComponent {
constructor(props) {
super(props);

this.state = {
data: appointments,
currentDate: '2018-06-27',
};
}

render() {
const { data, currentDate, locale } = this.state;

return (
<Paper>
<Scheduler
data={data}
locale={locale}
>
<ViewState
defaultCurrentDate={currentDate}
/>
<WeekView
startDayHour={9}
endDayHour={19}
dayScaleCellComponent={DayScaleCell}
timeScaleCellComponent={TimeScaleCell}
/>
<Toolbar />
<Appointments />
</Scheduler>
</Paper>
);
}
}
@@ -0,0 +1,108 @@
import * as React from 'react';
import Paper from '@material-ui/core/Paper';
import TextField from '@material-ui/core/TextField';
import MenuItem from '@material-ui/core/MenuItem';
import { ViewState } from '@devexpress/dx-react-scheduler';
import { withStyles } from '@material-ui/core/styles';
import {
Scheduler,
WeekView,
Toolbar,
DateNavigator,
Appointments,
AllDayPanel,
} from '@devexpress/dx-react-scheduler-material-ui';

import { appointments } from '../../../demo-data/appointments';

const allDayLocalizationMessages = {
'fr-FR': {
allDay: "Autour de l'horloge",
},
'de-GR': {
allDay: 'Rund um die Uhr',
},
'en-US': {
allDay: 'All Day',
},
};

const getAllDayMessages = locale => allDayLocalizationMessages[locale];

const styles = theme => ({
container: {
display: 'flex',
marginBottom: theme.spacing.unit * 2,
justifyContent: 'flex-end',
},
text: {
...theme.typography.h6,
marginRight: theme.spacing.unit * 2,
},
});

const LocaleSwitcher = withStyles(styles, { name: 'LocaleSwitcher' })(
({ onLocaleChange, currentLocale, classes }) => (
<div className={classes.container}>
<div className={classes.text}>
Locale:
</div>
<TextField
select
value={currentLocale}
onChange={onLocaleChange}
>
<MenuItem value="fr-FR">Le français (French)</MenuItem>
<MenuItem value="de-GR">Deutsch (German)</MenuItem>
<MenuItem value="en-US">English (United States)</MenuItem>
</TextField>
</div>
),
);

export default class Demo extends React.PureComponent {
constructor(props) {
super(props);

this.state = {
data: appointments,
currentDate: '2018-06-27',
locale: 'fr-FR',
};

this.changeLocale = event => this.setState({ locale: event.target.value });
}

render() {
const { data, currentDate, locale } = this.state;

return (
<div>
<LocaleSwitcher
currentLocale={locale}
onLocaleChange={this.changeLocale}
/>
<Paper>
<Scheduler
data={data}
locale={locale}
>
<ViewState
defaultCurrentDate={currentDate}
/>
<WeekView
startDayHour={9}
endDayHour={19}
/>
<Toolbar />
<DateNavigator />
<Appointments />
<AllDayPanel
messages={getAllDayMessages(locale)}
/>
</Scheduler>
</Paper>
</div>
);
}
}
Expand Up @@ -551,6 +551,7 @@ declare const Scheduler: React.ComponentType<SchedulerProps> & {
// @public (undocumented)
interface SchedulerProps {
data?: Array<AppointmentModel>;
locale?: string;
rootComponent?: React.ComponentType<Scheduler_2.RootProps>;
}

Expand Down
Expand Up @@ -3,8 +3,10 @@ import * as PropTypes from 'prop-types';
import Popover from '@material-ui/core/Popover';
import AccessTime from '@material-ui/icons/AccessTime';
import Grid from '@material-ui/core/Grid';
import moment from 'moment';
import { withStyles } from '@material-ui/core/styles';
import { HOUR_MINUTE_OPTIONS } from '@devexpress/dx-scheduler-core';

const verticalTopHorizontalCenterOptions = { vertical: 'top', horizontal: 'center' };

const styles = theme => ({
text: {
Expand Down Expand Up @@ -47,6 +49,7 @@ const LayoutBase = ({
commandButtonIds,
onOpenButtonClick,
onDeleteButtonClick,
formatDate,
classes,
...restProps
}) => {
Expand All @@ -61,8 +64,8 @@ const LayoutBase = ({
open={visible}
anchorEl={target}
onClose={onHide}
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
anchorOrigin={verticalTopHorizontalCenterOptions}
transformOrigin={verticalTopHorizontalCenterOptions}
{...restProps}
>
<Header appointmentData={data}>
Expand Down Expand Up @@ -92,7 +95,7 @@ const LayoutBase = ({
</Grid>
<Grid item xs={10}>
<div className={classes.text}>
{`${moment(data.startDate).format('h:mm A')} - ${moment(data.endDate).format('h:mm A')}`}
{`${formatDate(data.startDate, HOUR_MINUTE_OPTIONS)} - ${formatDate(data.endDate, HOUR_MINUTE_OPTIONS)}`}
</div>
</Grid>
</Grid>
Expand All @@ -110,6 +113,7 @@ LayoutBase.propTypes = {
showDeleteButton: PropTypes.bool.isRequired,
commandButtonIds: PropTypes.object.isRequired,
classes: PropTypes.object.isRequired,
formatDate: PropTypes.func.isRequired,
onOpenButtonClick: PropTypes.func,
onDeleteButtonClick: PropTypes.func,
appointmentMeta: PropTypes.shape({
Expand Down
Expand Up @@ -24,6 +24,7 @@ describe('Appointment Tooltip', () => {
close: 'close',
delete: 'delete',
},
formatDate: () => undefined,
};
beforeAll(() => {
classes = getClasses(<Layout {...defaultProps} />);
Expand Down Expand Up @@ -121,14 +122,20 @@ describe('Appointment Tooltip', () => {
.toBeTruthy();
});

it('should render appointment dates', () => {
it('should call dates format function', () => {
const dateTimeFormat = jest.fn();
const tree = shallow((
<Layout {...defaultProps} />
<Layout {...defaultProps} formatDate={dateTimeFormat} />
));
const text = tree.find(defaultProps.contentComponent).find(`.${classes.text}`).text();

expect(text)
.toEqual('10:00 AM - 11:00 AM');
expect(dateTimeFormat)
.toBeCalledTimes(2);
expect(dateTimeFormat)
.toHaveBeenCalledWith(defaultProps.appointmentMeta.data.startDate, { hour: 'numeric', minute: 'numeric' });
expect(dateTimeFormat)
.toHaveBeenCalledWith(defaultProps.appointmentMeta.data.endDate, { hour: 'numeric', minute: 'numeric' });
expect(tree.find(`.${classes.text}`).props().children)
.toBeTruthy();
});
});
});
Expand Up @@ -11,6 +11,7 @@ describe('AppointmentContent', () => {
data: {},
type: 'horizontal',
recurringIconComponent: () => <div />,
formatDate: () => undefined,
};
beforeAll(() => {
shallow = createShallow({ dive: true });
Expand Down
Expand Up @@ -39,6 +39,7 @@ const HorizontalAppointmentBase = ({
children,
className,
recurringIconComponent: RecurringIcon,
formatDate,
...restProps
}) => {
const repeat = !!data.rRule;
Expand Down Expand Up @@ -69,9 +70,11 @@ HorizontalAppointmentBase.propTypes = {
data: PropTypes.object.isRequired,
children: PropTypes.node,
className: PropTypes.string,
formatDate: PropTypes.func,
};

HorizontalAppointmentBase.defaultProps = {
formatDate: () => '',
children: undefined,
className: undefined,
};
Expand Down
@@ -1,8 +1,8 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import moment from 'moment';
import { withStyles } from '@material-ui/core/styles';
import classNames from 'classnames';
import { HOUR_MINUTE_OPTIONS } from '@devexpress/dx-scheduler-core';

const styles = ({ palette, spacing }) => ({
title: {
Expand Down Expand Up @@ -51,6 +51,7 @@ const VerticalAppointmentBase = ({
data,
children,
className,
formatDate,
recurringIconComponent: RecurringIcon,
...restProps
}) => {
Expand All @@ -64,13 +65,13 @@ const VerticalAppointmentBase = ({
</div>
<div className={classes.textContainer}>
<div className={classes.time}>
{moment(data.startDate).format('h:mm A')}
{formatDate(data.startDate, HOUR_MINUTE_OPTIONS)}
</div>
<div className={classes.time}>
{' - '}
</div>
<div className={classes.time}>
{moment(data.endDate).format('h:mm A')}
{formatDate(data.endDate, HOUR_MINUTE_OPTIONS)}
</div>
</div>
</div>
Expand All @@ -88,6 +89,7 @@ VerticalAppointmentBase.propTypes = {
recurringIconComponent: PropTypes.func.isRequired,
classes: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
formatDate: PropTypes.func.isRequired,
children: PropTypes.node,
className: PropTypes.string,
};
Expand Down
Expand Up @@ -10,6 +10,7 @@ describe('VerticalAppointment', () => {
endDate: new Date('2018-07-27 17:10'),
},
recurringIconComponent: () => <div />,
formatDate: () => undefined,
};

let classes;
Expand All @@ -30,19 +31,24 @@ describe('VerticalAppointment', () => {
.toBe('title');
});

it('should render appointment times', () => {
it('should call time format function', () => {
const formatDate = jest.fn();
formatDate.mockImplementation(() => 'time');
const tree = mount((
<VerticalAppointment
{...defaultProps}
formatDate={formatDate}
/>
));

expect(tree.find(`.${classes.time}`).at(0).text())
.toBe('1:10 PM');
expect(tree.find(`.${classes.time}`).at(1).text())
.toBe(' - ');
expect(tree.find(`.${classes.time}`).at(2).text())
.toBe('5:10 PM');
expect(formatDate)
.toHaveBeenCalledWith(defaultProps.data.startDate, { hour: 'numeric', minute: 'numeric' });
expect(formatDate)
.toHaveBeenCalledWith(defaultProps.data.startDate, { hour: 'numeric', minute: 'numeric' });
expect(tree.find(`.${classes.time}`).at(0).props().children)
.toBeTruthy();
expect(tree.find(`.${classes.time}`).at(2).props().children)
.toBeTruthy();
});

it('should render children', () => {
Expand Down
Expand Up @@ -18,6 +18,7 @@ const NavigatorBase = ({
textComponent: Text,
navigationButtonComponent: NavigationButton,
onNavigate,
formatDate,
...restProps
}) => (
<Toolbar
Expand All @@ -28,7 +29,7 @@ const NavigatorBase = ({
type="back"
onClick={() => { onNavigate({ back: true }); }}
/>
<Text currentDate={currentDate} />
<Text currentDate={currentDate} formatDate={formatDate} />
<NavigationButton
type="forward"
onClick={() => { onNavigate({ back: false }); }}
Expand All @@ -45,6 +46,7 @@ NavigatorBase.propTypes = {
PropTypes.string,
PropTypes.instanceOf(Date),
]).isRequired,
formatDate: PropTypes.func.isRequired,
className: PropTypes.string,
onNavigate: PropTypes.func,
};
Expand Down

0 comments on commit 286003e

Please sign in to comment.