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-15463] Use dhis2 ui calendarInput component in working list #3712

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
08063b7
feat: add calendarInput
alaa-yahia Jul 4, 2024
5855c58
fix: calendarInput zindex
simonadomnisoru Jul 4, 2024
5241682
feat: use calendarInput
alaa-yahia Jul 11, 2024
796c02d
fix: linter errors
alaa-yahia Jul 11, 2024
6899da2
fix: flow errors
alaa-yahia Jul 11, 2024
bc566c7
fix: calendar version
alaa-yahia Aug 7, 2024
7744732
fix: updated yarn.lock
alaa-yahia Aug 7, 2024
3ae9dd5
fix: update calendar version
alaa-yahia Aug 8, 2024
ad40a7a
fix: add type state
alaa-yahia Aug 8, 2024
7169cd9
fix: display date in wrong format when passing dd-mm-yyyy
alaa-yahia Aug 8, 2024
a050dd7
fix: display date in wrong format when passing dd-mm-yyyy
alaa-yahia Aug 8, 2024
f5c66f7
fix: remove editable prop
alaa-yahia Aug 12, 2024
52e3c40
fix: label type definition
alaa-yahia Aug 12, 2024
1ac9aff
fix: add label
alaa-yahia Aug 12, 2024
22483c9
fix: allow empty strings to run onBlur
alaa-yahia Aug 13, 2024
2623729
chore: remove unnecessary props
alaa-yahia Aug 15, 2024
1ff177d
fix: replace onKeyPress with onKeyDown
alaa-yahia Aug 19, 2024
c53e104
Merge remote-tracking branch 'origin/master' into DHIS2-15463-use-dhi…
alaa-yahia Aug 19, 2024
1a2c994
feat: display errors
alaa-yahia Oct 3, 2024
cd82da1
fix: refactor code to apply latest changes
alaa-yahia Oct 30, 2024
15d8ac1
Merge branch 'master' into DHIS2-15463-use-dhis2-ui-calendarinput-com…
alaa-yahia Oct 30, 2024
e315967
fix: linter error
alaa-yahia Oct 30, 2024
81cfabf
fix: error is missing when the calendar is in the dd-MM-yyyy format
alaa-yahia Nov 12, 2024
9ef8b02
fix: flow errors
alaa-yahia Nov 12, 2024
dfcf025
chore: update calendarInput ui version
alaa-yahia Nov 25, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,9 @@ Then('the list should display data ordered descendingly by report date', () => {
.click();

cy.get('input[placeholder="From"]')
.type(`${lastYear}-01-01`);
.type(`${lastYear}-01-01`).blur();

cy.get('input[placeholder="To"]').click();
cy.get('input[placeholder="To"]').click().blur();

cy.contains('Update')
.click({ force: true });
Expand Down Expand Up @@ -399,10 +399,10 @@ When('you set the date of admission filter', () => {
cy.get('input[type="text"]')
.then(($elements) => {
cy.wrap($elements[0])
.type('2018-01-01');
.type('2018-01-01').blur();

cy.wrap($elements[1])
.type('2018-12-31');
.type('2018-12-31').blur();
});

cy.contains('Update')
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@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 Expand Up @@ -136,6 +137,7 @@
"@dhis2/app-runtime": "^3.10.2",
"@babel/preset-react": "7.16.7",
"@dhis2/ui": "^9.10.1",
"@dhis2-ui/calendar": "10.0.3",
"@js-temporal/polyfill": "0.4.3",
"core-js": "2.5.7",
"i18next": "^20.5.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ import { isValidZeroOrPositiveInteger } from 'capture-core-utils/validators/form
import { SelectBoxes, orientations } from '../../FormFields/Options/SelectBoxes';
import { OptionSet } from '../../../metaData/OptionSet/OptionSet';
import { Option } from '../../../metaData/OptionSet/Option';

import type { UpdatableFilterContent } from '../types';
import { type DateValue } from './types';
import { FromDateFilter } from './From.component';
import { ToDateFilter } from './To.component';
import { isValidDate } from '../../../utils/validators/form';
import { parseDate } from '../../../utils/converters/date';
import { dataElementTypes } from '../../../metaData';
import type { UpdatableFilterContent } from '../types';
import './calendarFilterStyles.css';
import { mainOptionKeys, mainOptionTranslatedTexts } from './options';
import { getDateFilterData } from './dateFilterDataGetter';
import { RangeFilter } from './RangeFilter.component';
import { parseDate } from '../../../utils/converters/date';

const getStyles = (theme: Theme) => ({
fromToContainer: {
Expand All @@ -29,7 +28,7 @@ const getStyles = (theme: Theme) => ({
width: 30,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
alignItems: 'start',
paddingTop: theme.typography.pxToRem(6),
fontSize: theme.typography.body1.fontSize,
},
Expand All @@ -43,18 +42,16 @@ const getStyles = (theme: Theme) => ({
});

export type Value = ?{
from?: ?string,
to?: ?string,
from?: ?DateValue,
to?: ?DateValue,
main?: ?string,
start?: ?string,
end?: ?string,
};

type Props = {
onCommitValue: (value: ?{ from?: ?string, to?: ?string }) => void,
onUpdate: (commitValue?: any) => void,
value: Value,
type: $Keys<typeof dataElementTypes>,
classes: {
fromToContainer: string,
inputContainer: string,
Expand All @@ -69,33 +66,33 @@ type State = {
submitAttempted: boolean,
};

const getAbsoluteRangeErrors = (fromValue, toValue, type, submitAttempted) => {
let errors = {
minValueError: null,
maxValueError: null,
dateLogicError: null,
};
// eslint-disable-next-line complexity
const getAbsoluteRangeErrors = (fromValue, toValue, submitAttempted) => {
const fromValueString = fromValue?.value;
const toValueString = toValue?.value;
const isFromValueValid = fromValue?.isValid;
const isToValueValid = toValue?.isValid;

if (!fromValue && !toValue) {
errors = {
...errors,
dateLogicError: submitAttempted ? i18n.t(DateFilter.errorMessages.ABSOLUTE_RANGE_WITHOUT_VALUES) : null,
};
} else {
const { isValid: isMinValueValid, error: minValueError } = DateFilter.validateField(fromValue, type);
const { isValid: isMaxValueValid, error: maxValueError } = DateFilter.validateField(toValue, type);
const hasDateLogicError = () =>
isMinValueValid && isMaxValueValid && fromValue && toValue && DateFilter.isFromAfterTo(fromValue, toValue);

errors = {
...errors,
minValueError,
maxValueError,
dateLogicError: hasDateLogicError() ? i18n.t(DateFilter.errorMessages.FROM_GREATER_THAN_TO) : null,
if (!fromValueString && !toValueString) {
return {
dateLogicError: submitAttempted
? i18n.t(DateFilter.errorMessages.ABSOLUTE_RANGE_WITHOUT_VALUES)
: null,
};
}

return errors;
const hasDateLogicError =
fromValueString &&
toValueString &&
isFromValueValid &&
isToValueValid &&
DateFilter.isFromAfterTo(fromValueString, toValueString);

return {
dateLogicError: hasDateLogicError
? i18n.t(DateFilter.errorMessages.FROM_GREATER_THAN_TO)
: null,
};
};

const getRelativeRangeErrors = (startValue, endValue, submitAttempted) => {
Expand All @@ -110,8 +107,8 @@ const getRelativeRangeErrors = (startValue, endValue, submitAttempted) => {
bufferLogicError: submitAttempted ? i18n.t(DateFilter.errorMessages.RELATIVE_RANGE_WITHOUT_VALUES) : null,
};
}
const { error: startValueError } = DateFilter.validateField(startValue, dataElementTypes.INTEGER_ZERO_OR_POSITIVE);
const { error: endValueError } = DateFilter.validateField(endValue, dataElementTypes.INTEGER_ZERO_OR_POSITIVE);
const { error: startValueError } = DateFilter.validateRelativeRangeValue(startValue);
const { error: endValueError } = DateFilter.validateRelativeRangeValue(endValue);
errors = {
...errors,
startValueError,
Expand All @@ -125,27 +122,20 @@ const isAbsoluteRangeFilterValid = (fromValue, toValue) => {
return false;
}

const parseResultFrom = fromValue ? parseDate(fromValue) : { isValid: true, moment: null };
const parseResultTo = toValue ? parseDate(toValue) : { isValid: true, moment: null };

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

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

const isRelativeRangeFilterValid = (startValue, endValue) => {
if (!startValue && !endValue) {
return false;
}
if (
!DateFilter.validateField(startValue, dataElementTypes.INTEGER_ZERO_OR_POSITIVE).isValid ||
!DateFilter.validateField(endValue, dataElementTypes.INTEGER_ZERO_OR_POSITIVE).isValid
!DateFilter.validateRelativeRangeValue(startValue).isValid ||
!DateFilter.validateRelativeRangeValue(endValue).isValid
) {
return false;
}
Expand All @@ -154,29 +144,26 @@ const isRelativeRangeFilterValid = (startValue, endValue) => {

// $FlowFixMe[incompatible-variance] automated comment
class DateFilterPlain extends Component<Props, State> implements UpdatableFilterContent<Value> {
static validateField(value: ?string, type: $Keys<typeof dataElementTypes>) {
static validateRelativeRangeValue(value: ?string) {
if (!value) {
return {
isValid: true,
error: null,
};
}

// $FlowFixMe dataElementTypes flow error
const typeValidator = DateFilter.validatorForTypes[type];
const isValid = typeValidator(value);
const isValid = isValidZeroOrPositiveInteger(value);

return {
isValid,
// $FlowFixMe dataElementTypes flow error
error: isValid ? null : i18n.t(DateFilter.errorMessages[type]),
error: isValid ? null : i18n.t(DateFilter.errorMessages[dataElementTypes.INTEGER_ZERO_OR_POSITIVE]),
};
}

static isFilterValid(
mainValue?: ?string,
fromValue?: ?string,
toValue?: ?string,
fromValue?: ?DateValue,
toValue?: ?DateValue,
startValue?: ?string,
endValue?: ?string,
) {
Expand All @@ -199,24 +186,19 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
}

toD2DateTextFieldInstance: any;

constructor(props: Props) {
super(props);
this.state = { submitAttempted: false };
}

static errorMessages = {
ABSOLUTE_RANGE_WITHOUT_VALUES: 'Please specify a range',
RELATIVE_RANGE_WITHOUT_VALUES: 'Please specify the number of days',
FROM_GREATER_THAN_TO: "The From date can't be after the To date",
MIN_GREATER_THAN_MAX: 'Days in the past cannot be greater than days in the future',
[dataElementTypes.DATE]: 'Please provide a valid date',
[dataElementTypes.INTEGER_ZERO_OR_POSITIVE]: 'Please provide zero or a positive integer',
};

static validatorForTypes = {
[dataElementTypes.DATE]: isValidDate,
[dataElementTypes.INTEGER_ZERO_OR_POSITIVE]: isValidZeroOrPositiveInteger,
};

static mainOptionSet = new OptionSet('mainOptions', [
new Option((_this) => {
_this.text = mainOptionTranslatedTexts[mainOptionKeys.TODAY];
Expand Down Expand Up @@ -251,12 +233,14 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
_this.value = mainOptionKeys.ABSOLUTE_RANGE;
}),
]);

static optionSet = new OptionSet('mainOptions', [
new Option((_this) => {
_this.text = mainOptionTranslatedTexts[mainOptionKeys.RELATIVE_RANGE];
_this.value = mainOptionKeys.RELATIVE_RANGE;
}),
]);

onGetUpdateData(updatedValues?: Value) {
const value = typeof updatedValues !== 'undefined' ? updatedValues : this.props.value;

Expand Down Expand Up @@ -302,23 +286,11 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
}

handleEnterKeyInFrom = () => {
this.toD2DateTextFieldInstance.focus();
this.toD2DateTextFieldInstance && this.toD2DateTextFieldInstance.focus();
};

handleDateSelectedFromCalendarInFrom = () => {
this.toD2DateTextFieldInstance.focus();
};

handleEnterKeyInTo = (value: { [key: string]: string }) => {
// validate with updated values
const values = this.getUpdatedValue(value);
this.setState({ submitAttempted: true });

if (values && !DateFilter.isFilterValid(values.main, values.from, values.to, values.start, values.end)) {
this.props.onCommitValue(values);
} else {
this.props.onUpdate(values || null);
}
this.toD2DateTextFieldInstance && this.toD2DateTextFieldInstance.focus();
};

handleFieldBlur = (value: { [key: string]: string }) => {
Expand All @@ -342,7 +314,6 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
const toValue = values && values.to;
const startValue = values && values.start;
const endValue = values && values.end;
const type = this.props.type;
const errors = {
minValueError: null,
maxValueError: null,
Expand All @@ -353,7 +324,7 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
};

if (mainValue === mainOptionKeys.ABSOLUTE_RANGE) {
return { ...errors, ...getAbsoluteRangeErrors(fromValue, toValue, type, submitAttempted) };
return { ...errors, ...getAbsoluteRangeErrors(fromValue, toValue, submitAttempted) };
}

if (mainValue === mainOptionKeys.RELATIVE_RANGE) {
Expand All @@ -364,8 +335,11 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter

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

return (
<div id="dateFilter">
<div>
Expand All @@ -384,26 +358,25 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
{/* $FlowSuppress: Flow not working 100% with HOCs */}
{/* $FlowFixMe[prop-missing] automated comment */}
<FromDateFilter
value={value && value.from}
error={minValueError}
errorClass={classes.error}
value={fromValue?.value}
onBlur={this.handleFieldBlur}
onEnterKey={this.handleEnterKeyInFrom}
onDateSelectedFromCalendar={this.handleDateSelectedFromCalendarInFrom}
error={fromValue?.error}
errorClass={classes.error}
/>
</div>
<div className={classes.toLabelContainer}>{i18n.t('to')}</div>
<div className={classes.inputContainer}>
{/* $FlowSuppress: Flow not working 100% with HOCs */}
{/* $FlowFixMe[prop-missing] automated comment */}
<ToDateFilter
value={value && value.to}
error={maxValueError}
errorClass={classes.error}
value={toValue?.value}
onBlur={this.handleFieldBlur}
onEnterKey={this.handleEnterKeyInTo}
textFieldRef={this.setToD2DateTextFieldInstance}
onFocusUpdateButton={onFocusUpdateButton}
error={toValue?.error}
errorClass={classes.error}
/>
</div>
</div>
Expand All @@ -424,7 +397,6 @@ class DateFilterPlain extends Component<Props, State> implements UpdatableFilter
startValueError={startValueError}
endValueError={endValueError}
handleFieldBlur={this.handleFieldBlur}
handleEnterKeyInTo={this.handleEnterKeyInTo}
/>
</div>
<div className={classNames(classes.error, classes.logicErrorContainer)}>{dateLogicError}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ export class DateFilterManager extends React.Component<Props, State> {
static calculateAbsoluteRangeValueState(filter: DateFilterData) {
return {
main: mainOptionKeys.ABSOLUTE_RANGE,
from: filter.ge && DateFilterManager.convertDateForEdit(filter.ge),
to: filter.le && DateFilterManager.convertDateForEdit(filter.le),
from: filter.ge ? {
value: filter.ge && DateFilterManager.convertDateForEdit(filter.ge),
isValid: true,
} : undefined,
to: filter.le ? {
value: filter.le && DateFilterManager.convertDateForEdit(filter.le),
isValid: true,
} : undefined,
};
}
static calculateRelativeRangeValueState(filter: DateFilterData) {
Expand Down Expand Up @@ -74,7 +80,7 @@ export class DateFilterManager extends React.Component<Props, State> {
};
}

handleCommitValue = (value: ?Object) => {
handleCommitValue = (value: ?Value) => {
this.setState({ value });
this.props.handleCommitValue && this.props.handleCommitValue();
};
Expand Down
Loading
Loading