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 11 commits into
base: master
Choose a base branch
from
Open
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"packages/rules-engine"
],
"dependencies": {
"@dhis2/rules-engine-javascript": "101.19.1",
alaa-yahia marked this conversation as resolved.
Show resolved Hide resolved
"@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 +19,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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change the property name as it is not a moment anymore? calendarMax is probably good enough for now.

}, 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 @@ -257,12 +259,28 @@ 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
// eslint-disable-next-line complexity
alaa-yahia marked this conversation as resolved.
Show resolved Hide resolved
getUpdatedValue(valuePart: Object) {
const valueObject = {
...this.props.value,
...valuePart,
};
const dateFormat = systemSettingsStore.get().dateFormat;

if (valuePart.from && valueObject?.from?.value) {
valueObject.from = {
...valueObject.from,
value: moment(convertLocalToIsoCalendar(valueObject.from.value)).format(dateFormat),
alaa-yahia marked this conversation as resolved.
Show resolved Hide resolved
};
}

if (valuePart.to && valueObject?.to?.value) {
valueObject.to = {
...valueObject.to,
value: moment(convertLocalToIsoCalendar(valueObject.to.value)).format(dateFormat),
};
}

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

Expand Down Expand Up @@ -358,7 +376,7 @@ 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={convertIsoToLocalCalendar(fromValue?.value)}
onBlur={this.handleFieldBlur}
onEnterKey={this.handleEnterKeyInFrom}
onDateSelectedFromCalendar={this.handleDateSelectedFromCalendarInFrom}
Expand All @@ -371,7 +389,7 @@ 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={convertIsoToLocalCalendar(toValue?.value)}
onBlur={this.handleFieldBlur}
textFieldRef={this.setToD2DateTextFieldInstance}
onFocusUpdateButton={onFocusUpdateButton}
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
11 changes: 6 additions & 5 deletions src/core_modules/capture-core/converters/clientToForm.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import moment from 'moment';
import { convertMomentToDateFormatString } from '../utils/converters/date';
import { convertMomentToDateFormatString, convertIsoToLocalCalendar } from '../utils/converters/date';
import { dataElementTypes } from '../metaData';

import { stringifyNumber } from './common/stringifyNumber';
Expand All @@ -23,16 +23,17 @@ type RangeValue = {
}

function convertDateForEdit(rawValue: string): string {
const momentInstance = moment(rawValue);
return convertMomentToDateFormatString(momentInstance);
const momentDate = moment(rawValue);
const dateString = momentDate.format('YYYY-MM-DD');
return convertIsoToLocalCalendar(dateString);
}

function convertDateTimeForEdit(rawValue: string): DateTimeFormValue {
const dateTime = moment(rawValue);
const dateString = convertMomentToDateFormatString(dateTime);
const dateString = dateTime.format('YYYY-MM-DD');
const timeString = dateTime.format('HH:mm');
return {
date: dateString,
date: convertIsoToLocalCalendar(dateString),
time: timeString,
};
}
Expand Down
10 changes: 6 additions & 4 deletions src/core_modules/capture-core/converters/clientToList.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@ import i18n from '@dhis2/d2-i18n';
import { Tag } from '@dhis2/ui';
import { PreviewImage } from 'capture-ui';
import { dataElementTypes, type DataElement } from '../metaData';
import { convertMomentToDateFormatString } from '../utils/converters/date';
import { convertIsoToLocalCalendar } from '../utils/converters/date';
import { stringifyNumber } from './common/stringifyNumber';
import { MinimalCoordinates } from '../components/MinimalCoordinates';
import { TooltipOrgUnit } from '../components/Tooltips/TooltipOrgUnit';

function convertDateForListDisplay(rawValue: string): string {
const momentDate = moment(rawValue);
return convertMomentToDateFormatString(momentDate);
const dateString = momentDate.format('YYYY-MM-DD');
return convertIsoToLocalCalendar(dateString);
}

function convertDateTimeForListDisplay(rawValue: string): string {
const momentDate = moment(rawValue);
const dateString = convertMomentToDateFormatString(momentDate);
const dateString = momentDate.format('YYYY-MM-DD');
const timeString = momentDate.format('HH:mm');
return `${dateString} ${timeString}`;
const localDate = convertIsoToLocalCalendar(dateString);
return `${localDate} ${timeString}`;
}

function convertTimeForListDisplay(rawValue: string): string {
Expand Down
12 changes: 8 additions & 4 deletions src/core_modules/capture-core/converters/clientToView.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@ import moment from 'moment';
import i18n from '@dhis2/d2-i18n';
import { PreviewImage } from 'capture-ui';
import { dataElementTypes, type DataElement } from '../metaData';
import { convertMomentToDateFormatString } from '../utils/converters/date';
import { convertIsoToLocalCalendar } from '../utils/converters/date';
import { stringifyNumber } from './common/stringifyNumber';
import { MinimalCoordinates } from '../components/MinimalCoordinates';
import { TooltipOrgUnit } from '../components/Tooltips/TooltipOrgUnit';


function convertDateForView(rawValue: string): string {
const momentDate = moment(rawValue);
return convertMomentToDateFormatString(momentDate);
const dateString = momentDate.format('YYYY-MM-DD');
return convertIsoToLocalCalendar(dateString);
Comment on lines 14 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can see we can just pass on the ISO-string (rawValue in this case) to the convertIsoToLocalCalendar function without doing the conversion to moment first. There is quite few examples like this where we convert back and forth, but this should not be needed as the convertIsoToLocalCalendar function accepts an ISO-string (And I would like the function to continue accepting an ISOstring).

}
function convertDateTimeForView(rawValue: string): string {
const momentDate = moment(rawValue);
const dateString = convertMomentToDateFormatString(momentDate);
const dateString = momentDate.format('YYYY-MM-DD');
const timeString = momentDate.format('HH:mm');
return `${dateString} ${timeString}`;

const localDate = convertIsoToLocalCalendar(dateString);
return `${localDate} ${timeString}`;
}

function convertTimeForView(rawValue: string): string {
const momentDate = moment(rawValue, 'HH:mm', true);
return momentDate.format('HH:mm');
Expand Down
19 changes: 13 additions & 6 deletions src/core_modules/capture-core/converters/formToClient.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @flow
import moment from 'moment';
import isString from 'd2-utilizr/lib/isString';
import { parseNumber, parseTime } from 'capture-core-utils/parsers';
import { dataElementTypes } from '../metaData';
import { parseDate } from '../utils/converters/date';
import { parseDate, convertLocalToIsoCalendar } from '../utils/converters/date';

type DateTimeValue = {
date: string,
Expand All @@ -25,18 +26,24 @@ function convertDateTime(formValue: DateTimeValue): ?string {
const minutes = momentTime.minute();

const parsedDate = editedDate ? parseDate(editedDate) : null;
if (!(parsedDate && parsedDate.isValid)) return null;
// $FlowFixMe[incompatible-type] automated comment
const momentDateTime: moment$Moment = parsedDate.momentDate;
if (!(parsedDate && parsedDate.isValid && parsedDate.momentDate)) return null;

const formattedDate = parsedDate.momentDate.format('YYYY-MM-DD');
const isoDate = convertLocalToIsoCalendar(formattedDate);
const momentDateTime = moment(isoDate);
momentDateTime.hour(hours);
momentDateTime.minute(minutes);
return momentDateTime.toISOString();
}

function convertDate(dateValue: string) {
const parsedDate = parseDate(dateValue);
// $FlowFixMe[incompatible-use] automated comment
return parsedDate.isValid ? parsedDate.momentDate.toISOString() : null;
if (!parsedDate.isValid || !parsedDate.momentDate) {
return null;
}
const formattedDate = parsedDate.momentDate.format('YYYY-MM-DD');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment (parsedDate.momentDate.toISOString())


return convertLocalToIsoCalendar(formattedDate);
}

function convertTime(timeValue: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export class SystemSettings {
dateFormat: string;
dir: string;
trackerAppRelativePath: string;
calendar: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function isLangRTL(code) {

export async function cacheSystemSettings(
uiLocale: string,
systemSettings: { dateFormat: string, serverTimeZoneId: string },
systemSettings: { dateFormat: string, serverTimeZoneId: string, calendar: string, },
) {
const systemSettingsArray = [
{
Expand All @@ -25,6 +25,10 @@ export async function cacheSystemSettings(
id: 'serverTimeZoneId',
value: systemSettings.serverTimeZoneId,
},
{
id: 'calendar',
value: systemSettings.calendar,
},
];

const storageController = getMainStorageController();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @flow
import {
convertFromIso8601,
} from '@dhis2/multi-calendar-dates';
import { systemSettingsStore } from '../../../../capture-core/metaDataMemoryStores';
import { padWithZeros } from './padWithZeros';

/**
* Converts a date from ISO calendar to local calendar
* @export
* @param {string} isoDate - date in ISO format
* @returns {string}
*/

export function convertIsoToLocalCalendar(isoDate: ?string): string {
if (!isoDate) {
return '';
}
const calendar = systemSettingsStore.get().calendar;
const dateFormat = systemSettingsStore.get().dateFormat;

const { year, eraYear, month, day } = convertFromIso8601(isoDate, calendar);
const localYear = calendar === 'ethiopian' ? eraYear : year;

return dateFormat === 'DD-MM-YYYY'
? `${padWithZeros(day, 2)}-${padWithZeros(month, 2)}-${padWithZeros(localYear, 4)}`
: `${padWithZeros(localYear, 4)}-${padWithZeros(month, 2)}-${padWithZeros(day, 2)}`;
}
Loading
Loading