Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [DHIS2-18310] enable non-Gregorian calendars in views & lists & forms #3900

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
],
"dependencies": {
"@dhis2/rules-engine-javascript": "101.19.2",
"@dhis2-ui/calendar": "^10.0.3",
"@dhis2/app-runtime": "^3.9.3",
"@dhis2/d2-i18n": "^1.1.0",
"@dhis2/d2-icons": "^1.0.1",
Expand All @@ -19,7 +20,6 @@
"@dhis2/d2-ui-rich-text": "^7.4.0",
"@dhis2/d2-ui-sharing-dialog": "^7.3.3",
"@dhis2/ui": "^9.10.1",
"@dhis2-ui/calendar": "^10.0.3",
"@joakim_sm/react-infinite-calendar": "^2.4.2",
"@material-ui/core": "3.9.4",
"@material-ui/icons": "3",
Expand Down
4 changes: 2 additions & 2 deletions src/components/AppLoader/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ async function initializeMetaDataAsync(dbLocale: string, onQueryApi: Function, m

async function initializeSystemSettingsAsync(
uiLocale: string,
systemSettings: { dateFormat: string, serverTimeZoneId: string },
systemSettings: { dateFormat: string, serverTimeZoneId: string, calendar: string, },
) {
const systemSettingsCacheData = await cacheSystemSettings(uiLocale, systemSettings);
await buildSystemSettingsAsync(systemSettingsCacheData);
Expand All @@ -158,7 +158,7 @@ export async function initializeAsync(
const systemSettings = await onQueryApi({
resource: 'system/info',
params: {
fields: 'dateFormat,serverTimeZoneId',
fields: 'dateFormat,serverTimeZoneId,calendar',
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import moment from 'moment';
import { createFieldConfig, createProps } from '../base/configBaseDefaultForm';
import { DateFieldForForm } from '../../Components';
import { convertDateObjectToDateFormatString } from '../../../../../../capture-core/utils/converters/date';
import type { DateDataElement } from '../../../../../metaData';
import type { QuerySingleResource } from '../../../../../utils/api/api.types';

Expand All @@ -15,7 +16,7 @@ export const getDateFieldConfig = (metaData: DateDataElement, options: Object, q
maxWidth: options.formHorizontal ? 150 : 350,
calendarWidth: options.formHorizontal ? 250 : 350,
popupAnchorPosition: getCalendarAnchorPosition(options.formHorizontal),
calendarMaxMoment: !metaData.allowFutureDate ? moment() : undefined,
calendarMaxMoment: !metaData.allowFutureDate ? convertDateObjectToDateFormatString(moment()) : undefined,
alaa-yahia marked this conversation as resolved.
Show resolved Hide resolved
}, options, metaData);

return createFieldConfig({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import moment from 'moment';
import { createFieldConfig, createProps } from '../base/configBaseCustomForm';
import { DateFieldForCustomForm } from '../../Components';
import { convertDateObjectToDateFormatString } from '../../../../../../capture-core/utils/converters/date';
import type { DateDataElement } from '../../../../../metaData';
import type { QuerySingleResource } from '../../../../../utils/api/api.types';

Expand All @@ -10,7 +11,7 @@ export const getDateFieldConfigForCustomForm = (metaData: DateDataElement, optio
width: 350,
maxWidth: 350,
calendarWidth: 350,
calendarMaxMoment: !metaData.allowFutureDate ? moment() : undefined,
calendarMaxMoment: !metaData.allowFutureDate ? convertDateObjectToDateFormatString(moment()) : undefined,
}, metaData);

return createFieldConfig({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
withAOCFieldBuilder,
withDataEntryFields,
} from '../../DataEntryDhis2Helpers';
import { convertDateObjectToDateFormatString } from '../../../../capture-core/utils/converters/date';

const overrideMessagePropNames = {
errorMessage: 'validationError',
Expand Down Expand Up @@ -111,7 +112,7 @@ const getEnrollmentDateSettings = () => {
required: true,
calendarWidth: props.formHorizontal ? 250 : 350,
popupAnchorPosition: getCalendarAnchorPosition(props.formHorizontal),
calendarMaxMoment: !props.enrollmentMetadata.allowFutureEnrollmentDate ? moment() : undefined,
calendarMaxMoment: !props.enrollmentMetadata.allowFutureEnrollmentDate ? convertDateObjectToDateFormatString(moment()) : undefined,
}),
getPropName: () => 'enrolledAt',
getValidatorContainers: getEnrollmentDateValidatorContainer,
Expand Down Expand Up @@ -159,7 +160,9 @@ const getIncidentDateSettings = () => {
required: true,
calendarWidth: props.formHorizontal ? 250 : 350,
popupAnchorPosition: getCalendarAnchorPosition(props.formHorizontal),
calendarMaxMoment: !props.enrollmentMetadata.allowFutureIncidentDate ? moment() : undefined,
calendarMaxMoment: !props.enrollmentMetadata.allowFutureIncidentDate ?
convertDateObjectToDateFormatString(moment()) :
undefined,
}),
getPropName: () => 'occurredAt',
getPassOnFieldData: () => true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
alaa-yahia marked this conversation as resolved.
Show resolved Hide resolved
import React, { Component } from 'react';
import classNames from 'classnames';
import moment from 'moment';
import { withStyles } from '@material-ui/core/styles';
import i18n from '@dhis2/d2-i18n';
import { isValidZeroOrPositiveInteger } from 'capture-core-utils/validators/form';
Expand All @@ -16,7 +17,8 @@ import './calendarFilterStyles.css';
import { mainOptionKeys, mainOptionTranslatedTexts } from './options';
import { getDateFilterData } from './dateFilterDataGetter';
import { RangeFilter } from './RangeFilter.component';
import { parseDate } from '../../../utils/converters/date';
import { parseDate, convertLocalToIsoCalendar, convertIsoToLocalCalendar } from '../../../utils/converters/date';
import { systemSettingsStore } from '../../../metaDataMemoryStores';

const getStyles = (theme: Theme) => ({
fromToContainer: {
Expand Down Expand Up @@ -117,16 +119,24 @@ const getRelativeRangeErrors = (startValue, endValue, submitAttempted) => {
return errors;
};

const isAbsoluteRangeFilterValid = (fromValue, toValue) => {
if (!fromValue && !toValue) {
const isAbsoluteRangeFilterValid = (from, to) => {
if (!from?.value && !to?.value) {
return false;
}
const fromValue = from?.value;
const toValue = to?.value;
const parseResultFrom = fromValue ? parseDate(fromValue) : { isValid: true, moment: null };
const parseResultTo = toValue ? parseDate(toValue) : { isValid: true, moment: null };

if ((fromValue && !fromValue.isValid) || (toValue && !toValue.isValid)) {
if (!(parseResultFrom.isValid && parseResultTo.isValid)) {
return false;
}
const isValidMomentDate = () =>
parseResultFrom.momentDate &&
parseResultTo.momentDate &&
parseResultFrom.momentDate.isAfter(parseResultTo.momentDate);

return !DateFilter.isFromAfterTo(fromValue?.value, toValue?.value);
return !isValidMomentDate();
};

const isRelativeRangeFilterValid = (startValue, endValue) => {
Expand Down Expand Up @@ -257,12 +267,25 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
return !values || DateFilter.isFilterValid(values.main, values.from, values.to, values.start, values.end);
}

getUpdatedValue(valuePart: { [key: string]: string }) {
// $FlowFixMe[cannot-spread-indexer] automated comment
getUpdatedValue(valuePart: Object) {
const valueObject = {
...this.props.value,
...valuePart,
};
const dateFormat = systemSettingsStore.get().dateFormat;

['from', 'to'].forEach((key) => {
if (valuePart[key] && valueObject[key]?.value) {
const isValidDate = valuePart[key]?.isValid;
valueObject[key] = {
...valueObject[key],
value: isValidDate ?
moment(convertLocalToIsoCalendar(valueObject[key].value)).format(dateFormat) :
valueObject[key].value,
};
}
});

const isRelativeRangeValue = () => valueObject?.start || valuePart?.start || valuePart?.end;
const isAbsoluteRangevalue = () => valueObject?.from || valuePart?.from || valuePart?.to;

Expand Down Expand Up @@ -335,10 +358,23 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter

render() {
const { value, classes, onFocusUpdateButton } = this.props;
const fromValue = value?.from;
const toValue = value?.to;
const { startValueError, endValueError, dateLogicError, bufferLogicError } =
this.getErrors();
this.getErrors();

const dateFormat = systemSettingsStore.get().dateFormat;

const formatDate = (dateValue) => {
if (!dateValue) return '';
const momentValue = moment(dateValue, dateFormat);
if (!momentValue.isValid()) return dateValue;
const isoDate = momentValue.format('YYYY-MM-DD');
return convertIsoToLocalCalendar(isoDate);
};

const from = value?.from;
const to = value?.to;
const fromDate = formatDate(from?.value);
const toDate = formatDate(to?.value);
alaa-yahia marked this conversation as resolved.
Show resolved Hide resolved

return (
<div id="dateFilter">
Expand All @@ -358,11 +394,11 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
{/* $FlowSuppress: Flow not working 100% with HOCs */}
{/* $FlowFixMe[prop-missing] automated comment */}
<FromDateFilter
value={fromValue?.value}
value={fromDate}
onBlur={this.handleFieldBlur}
onEnterKey={this.handleEnterKeyInFrom}
onDateSelectedFromCalendar={this.handleDateSelectedFromCalendarInFrom}
error={fromValue?.error}
error={from?.error}
errorClass={classes.error}
/>
</div>
Expand All @@ -371,11 +407,11 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
{/* $FlowSuppress: Flow not working 100% with HOCs */}
{/* $FlowFixMe[prop-missing] automated comment */}
<ToDateFilter
value={toValue?.value}
value={toDate}
onBlur={this.handleFieldBlur}
textFieldRef={this.setToD2DateTextFieldInstance}
onFocusUpdateButton={onFocusUpdateButton}
error={toValue?.error}
error={to?.error}
errorClass={classes.error}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { type DateValue } from '../../../FiltersForTypes/Date/types/date.types';
type Props = {
label?: ?string,
value: ?string,
calendar?: string,
calendarWidth?: ?number,
inputWidth?: ?number,
onBlur: (value: DateValue) => void,
Expand Down Expand Up @@ -50,7 +49,6 @@ export class D2Date extends React.Component<Props, State> {

render() {
const {
calendar,
calendarWidth,
inputWidth,
classes,
Expand All @@ -62,7 +60,7 @@ export class D2Date extends React.Component<Props, State> {
...passOnProps
} = this.props;

const calendarType = calendar || 'gregory';
const calendarType = systemSettingsStore.get().calendar || 'gregory';
const format = systemSettingsStore.get().dateFormat;

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// @flow
import i18n from '@dhis2/d2-i18n';
import { pipe } from 'capture-core-utils';
import moment from 'moment';
import { convertMomentToDateFormatString } from '../../../../../../utils/converters/date';
import { convertIsoToLocalCalendar } from '../../../../../../utils/converters/date';
import type { DateFilterData, AbsoluteDateFilterData } from '../../../../../FiltersForTypes';
import { areRelativeRangeValuesSupported }
from '../../../../../../utils/validation/validators/areRelativeRangeValuesSupported';
Expand Down Expand Up @@ -30,11 +29,6 @@ const translatedPeriods = {
[periods.RELATIVE_RANGE]: i18n.t('Relative range'),
};

const convertToViewValue = (filterValue: string) => pipe(
value => moment(value),
momentDate => convertMomentToDateFormatString(momentDate),
)(filterValue);

function translateAbsoluteDate(filter: AbsoluteDateFilterData) {
let appliedText = '';
const fromValue = filter.ge;
Expand All @@ -44,18 +38,18 @@ function translateAbsoluteDate(filter: AbsoluteDateFilterData) {
const momentFrom = moment(fromValue);
const momentTo = moment(toValue);
if (momentFrom.isSame(momentTo)) {
appliedText = convertMomentToDateFormatString(momentFrom);
appliedText = convertIsoToLocalCalendar(fromValue);
} else {
const appliedTextFrom = convertMomentToDateFormatString(momentFrom);
const appliedTextTo = convertMomentToDateFormatString(momentTo);
const appliedTextFrom = convertIsoToLocalCalendar(fromValue);
const appliedTextTo = convertIsoToLocalCalendar(toValue);
appliedText = i18n.t('{{fromDate}} to {{toDate}}', { fromDate: appliedTextFrom, toDate: appliedTextTo });
}
} else if (fromValue) {
const appliedTextFrom = convertToViewValue(fromValue);
const appliedTextFrom = convertIsoToLocalCalendar(fromValue);
appliedText = i18n.t('after or equal to {{date}}', { date: appliedTextFrom });
} else {
// $FlowFixMe[incompatible-call] automated comment
const appliedTextTo = convertToViewValue(toValue);
const appliedTextTo = convertIsoToLocalCalendar(toValue);
appliedText = i18n.t('before or equal to {{date}}', { date: appliedTextTo });
}
return appliedText;
Expand Down
Loading
Loading