From e40f4c07731eb5fe6a7d4afe4edadf88b2ca09a0 Mon Sep 17 00:00:00 2001 From: Jovan Ssebaggala Date: Wed, 31 Jul 2024 07:37:14 +0300 Subject: [PATCH 1/5] Add min and max date validations to obsDate field --- .../src/config-schema.ts | 3 +- .../field/obs/obs-field.component.tsx | 35 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/esm-patient-registration-app/src/config-schema.ts b/packages/esm-patient-registration-app/src/config-schema.ts index 1e2cadd78..6b9d6f866 100644 --- a/packages/esm-patient-registration-app/src/config-schema.ts +++ b/packages/esm-patient-registration-app/src/config-schema.ts @@ -12,7 +12,8 @@ export interface FieldDefinition { label?: string; uuid: string; placeholder?: string; - dateFormat?: string; + maxDate?: string; + minDate?: string; showHeading: boolean; validation?: { required: boolean; diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx index 7eecbf520..c05b37ade 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx @@ -57,8 +57,8 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) { concept={concept} label={fieldDefinition.label} required={fieldDefinition.validation.required} - dateFormat={fieldDefinition.dateFormat} - placeholder={fieldDefinition.placeholder} + minDate={fieldDefinition.minDate} + maxDate={fieldDefinition.maxDate} /> ); case 'Coded': @@ -159,15 +159,38 @@ interface DateObsFieldProps { concept: ConceptResponse; label: string; required?: boolean; - dateFormat?: string; - placeholder?: string; + minDate?: string; + maxDate?: string; } -function DateObsField({ concept, label, required, placeholder }: DateObsFieldProps) { +const evaluateConfigDate = (dateString: string): Date | string | null => { + if (!dateString) { + return null; + } + + if (dateString === 'today') { + return new Date(); + } + + const date = new Date(dateString); + if (!isNaN(date.getTime())) { + return date; + } + + return 'Invalid min or max date!'; +}; + +function DateObsField({ concept, label, required, maxDate, minDate }: DateObsFieldProps) { const { t } = useTranslation(); const fieldName = `obs.${concept.uuid}`; const { setFieldValue } = useContext(PatientRegistrationContext); + const evaluatedMinDate = evaluateConfigDate(minDate); + const evaluatedMaxDate = evaluateConfigDate(maxDate); + const configDateError = + (typeof evaluatedMinDate === 'string' && evaluatedMinDate) || + (typeof evaluatedMaxDate === 'string' && evaluatedMaxDate); + const onDateChange = (date: Date) => { setFieldValue(fieldName, date); }; @@ -188,6 +211,8 @@ function DateObsField({ concept, label, required, placeholder }: DateObsFieldPro isInvalid={errors[fieldName] && touched[fieldName]} invalidText={t(meta.error)} value={field.value} + minDate={typeof evaluatedMinDate !== 'string' && evaluatedMinDate} + maxDate={typeof evaluatedMaxDate !== 'string' && evaluatedMaxDate} /> ); From 0cd381bcc15ffdbcdbe41b4e2f1b36ce2ca4c821 Mon Sep 17 00:00:00 2001 From: Jovan Ssebaggala Date: Fri, 27 Sep 2024 14:08:48 +0300 Subject: [PATCH 2/5] refactor code to address review comments --- .../src/config-schema.ts | 1 - .../field/obs/obs-field.component.tsx | 42 +++++++++++-------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/esm-patient-registration-app/src/config-schema.ts b/packages/esm-patient-registration-app/src/config-schema.ts index 6b9d6f866..234d319a4 100644 --- a/packages/esm-patient-registration-app/src/config-schema.ts +++ b/packages/esm-patient-registration-app/src/config-schema.ts @@ -11,7 +11,6 @@ export interface FieldDefinition { type: string; label?: string; uuid: string; - placeholder?: string; maxDate?: string; minDate?: string; showHeading: boolean; diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx index c05b37ade..cbad9c8be 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx @@ -3,13 +3,15 @@ import classNames from 'classnames'; import { Field } from 'formik'; import { useTranslation } from 'react-i18next'; import { InlineNotification, Layer, Select, SelectItem } from '@carbon/react'; -import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework'; +import { OpenmrsDatePicker, translateFrom, useConfig } from '@openmrs/esm-framework'; import { type ConceptResponse } from '../../patient-registration.types'; import { type FieldDefinition, type RegistrationConfig } from '../../../config-schema'; import { Input } from '../../input/basic-input/input/input.component'; import { useConcept, useConceptAnswers } from '../field.resource'; import { PatientRegistrationContext } from '../../patient-registration-context'; import styles from './../field.scss'; +import dayjs from 'dayjs'; +import { moduleName } from '../../../constants'; export interface ObsFieldProps { fieldDefinition: FieldDefinition; @@ -163,33 +165,39 @@ interface DateObsFieldProps { maxDate?: string; } -const evaluateConfigDate = (dateString: string): Date | string | null => { +const evaluateConfigDate = (dateString: string, dateType: string): Date | null => { if (!dateString) { return null; } - if (dateString === 'today') { - return new Date(); + return dayjs(new Date()).toDate(); } - const date = new Date(dateString); - if (!isNaN(date.getTime())) { - return date; + const parsedDate = dayjs(dateString); + if (!parsedDate.isValid()) { + throw new Error( + translateFrom(moduleName, 'invalidObsDateErrorMessage', `The ${dateType} date value provided is invalid!`), + ); } - return 'Invalid min or max date!'; + return parsedDate.toDate(); }; function DateObsField({ concept, label, required, maxDate, minDate }: DateObsFieldProps) { const { t } = useTranslation(); const fieldName = `obs.${concept.uuid}`; const { setFieldValue } = useContext(PatientRegistrationContext); + let evaluatedMinDate = null; + let evaluatedMaxDate = null; + let configDateError = ''; - const evaluatedMinDate = evaluateConfigDate(minDate); - const evaluatedMaxDate = evaluateConfigDate(maxDate); - const configDateError = - (typeof evaluatedMinDate === 'string' && evaluatedMinDate) || - (typeof evaluatedMaxDate === 'string' && evaluatedMaxDate); + try { + evaluatedMinDate = evaluateConfigDate(minDate, 'Min'); + evaluatedMaxDate = evaluateConfigDate(maxDate, 'Max'); + } catch (error) { + configDateError = error.message; + console.error(configDateError); + } const onDateChange = (date: Date) => { setFieldValue(fieldName, date); @@ -208,11 +216,11 @@ function DateObsField({ concept, label, required, maxDate, minDate }: DateObsFie isRequired={required} onChange={onDateChange} labelText={label ?? concept.display} - isInvalid={errors[fieldName] && touched[fieldName]} - invalidText={t(meta.error)} + isInvalid={(errors[fieldName] && touched[fieldName]) || configDateError} + invalidText={t(meta.error) || configDateError} value={field.value} - minDate={typeof evaluatedMinDate !== 'string' && evaluatedMinDate} - maxDate={typeof evaluatedMaxDate !== 'string' && evaluatedMaxDate} + minDate={evaluatedMinDate} + maxDate={evaluatedMaxDate} /> ); From 32486f2bd65c71c9e62d30045ae82cac0b721a94 Mon Sep 17 00:00:00 2001 From: Jovan Ssebaggala Date: Wed, 16 Oct 2024 14:38:29 +0300 Subject: [PATCH 3/5] Add flags to disable future and past dates --- .../src/config-schema.ts | 5 +- .../field/obs/obs-field.component.tsx | 51 ++++--------------- 2 files changed, 13 insertions(+), 43 deletions(-) diff --git a/packages/esm-patient-registration-app/src/config-schema.ts b/packages/esm-patient-registration-app/src/config-schema.ts index 234d319a4..12a72dca6 100644 --- a/packages/esm-patient-registration-app/src/config-schema.ts +++ b/packages/esm-patient-registration-app/src/config-schema.ts @@ -11,8 +11,9 @@ export interface FieldDefinition { type: string; label?: string; uuid: string; - maxDate?: string; - minDate?: string; + placeholder?: string; + disableFutureDates?: boolean; + disablePastDates?: boolean; showHeading: boolean; validation?: { required: boolean; diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx index cbad9c8be..d8ad393d4 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx @@ -3,15 +3,13 @@ import classNames from 'classnames'; import { Field } from 'formik'; import { useTranslation } from 'react-i18next'; import { InlineNotification, Layer, Select, SelectItem } from '@carbon/react'; -import { OpenmrsDatePicker, translateFrom, useConfig } from '@openmrs/esm-framework'; +import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework'; import { type ConceptResponse } from '../../patient-registration.types'; import { type FieldDefinition, type RegistrationConfig } from '../../../config-schema'; import { Input } from '../../input/basic-input/input/input.component'; import { useConcept, useConceptAnswers } from '../field.resource'; import { PatientRegistrationContext } from '../../patient-registration-context'; import styles from './../field.scss'; -import dayjs from 'dayjs'; -import { moduleName } from '../../../constants'; export interface ObsFieldProps { fieldDefinition: FieldDefinition; @@ -59,8 +57,8 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) { concept={concept} label={fieldDefinition.label} required={fieldDefinition.validation.required} - minDate={fieldDefinition.minDate} - maxDate={fieldDefinition.maxDate} + disablePastDates={fieldDefinition.disablePastDates} + disableFutureDates={fieldDefinition.disableFutureDates} /> ); case 'Coded': @@ -161,43 +159,14 @@ interface DateObsFieldProps { concept: ConceptResponse; label: string; required?: boolean; - minDate?: string; - maxDate?: string; + disablePastDates?: boolean; + disableFutureDates?: boolean; } -const evaluateConfigDate = (dateString: string, dateType: string): Date | null => { - if (!dateString) { - return null; - } - if (dateString === 'today') { - return dayjs(new Date()).toDate(); - } - - const parsedDate = dayjs(dateString); - if (!parsedDate.isValid()) { - throw new Error( - translateFrom(moduleName, 'invalidObsDateErrorMessage', `The ${dateType} date value provided is invalid!`), - ); - } - - return parsedDate.toDate(); -}; - -function DateObsField({ concept, label, required, maxDate, minDate }: DateObsFieldProps) { +function DateObsField({ concept, label, required, disablePastDates, disableFutureDates }: DateObsFieldProps) { const { t } = useTranslation(); const fieldName = `obs.${concept.uuid}`; const { setFieldValue } = useContext(PatientRegistrationContext); - let evaluatedMinDate = null; - let evaluatedMaxDate = null; - let configDateError = ''; - - try { - evaluatedMinDate = evaluateConfigDate(minDate, 'Min'); - evaluatedMaxDate = evaluateConfigDate(maxDate, 'Max'); - } catch (error) { - configDateError = error.message; - console.error(configDateError); - } const onDateChange = (date: Date) => { setFieldValue(fieldName, date); @@ -216,11 +185,11 @@ function DateObsField({ concept, label, required, maxDate, minDate }: DateObsFie isRequired={required} onChange={onDateChange} labelText={label ?? concept.display} - isInvalid={(errors[fieldName] && touched[fieldName]) || configDateError} - invalidText={t(meta.error) || configDateError} + isInvalid={errors[fieldName] && touched[fieldName]} + invalidText={t(meta.error)} value={field.value} - minDate={evaluatedMinDate} - maxDate={evaluatedMaxDate} + minDate={disablePastDates ? new Date() : undefined} + maxDate={disableFutureDates ? new Date() : undefined} /> ); From e1aa33b64341611d801e87029e51652df9a1b787 Mon Sep 17 00:00:00 2001 From: Jovan Ssebaggala Date: Tue, 29 Oct 2024 20:33:09 +0300 Subject: [PATCH 4/5] change wording from disable to allow --- .../src/config-schema.ts | 4 ++-- .../field/obs/obs-field.component.tsx | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/esm-patient-registration-app/src/config-schema.ts b/packages/esm-patient-registration-app/src/config-schema.ts index 12a72dca6..b2181f72a 100644 --- a/packages/esm-patient-registration-app/src/config-schema.ts +++ b/packages/esm-patient-registration-app/src/config-schema.ts @@ -12,8 +12,8 @@ export interface FieldDefinition { label?: string; uuid: string; placeholder?: string; - disableFutureDates?: boolean; - disablePastDates?: boolean; + allowFutureDates?: boolean; + allowPastDates?: boolean; showHeading: boolean; validation?: { required: boolean; diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx index d8ad393d4..6e4881511 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/obs/obs-field.component.tsx @@ -57,8 +57,8 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) { concept={concept} label={fieldDefinition.label} required={fieldDefinition.validation.required} - disablePastDates={fieldDefinition.disablePastDates} - disableFutureDates={fieldDefinition.disableFutureDates} + allowPastDates={fieldDefinition.allowPastDates} + allowFutureDates={fieldDefinition.allowFutureDates} /> ); case 'Coded': @@ -159,14 +159,16 @@ interface DateObsFieldProps { concept: ConceptResponse; label: string; required?: boolean; - disablePastDates?: boolean; - disableFutureDates?: boolean; + allowPastDates?: boolean; + allowFutureDates?: boolean; } -function DateObsField({ concept, label, required, disablePastDates, disableFutureDates }: DateObsFieldProps) { +function DateObsField({ concept, label, required, allowPastDates, allowFutureDates }: DateObsFieldProps) { const { t } = useTranslation(); const fieldName = `obs.${concept.uuid}`; const { setFieldValue } = useContext(PatientRegistrationContext); + const futureDatesAllowed = allowFutureDates ?? true; + const pastDatesAllowed = allowPastDates ?? true; const onDateChange = (date: Date) => { setFieldValue(fieldName, date); @@ -188,8 +190,8 @@ function DateObsField({ concept, label, required, disablePastDates, disableFutur isInvalid={errors[fieldName] && touched[fieldName]} invalidText={t(meta.error)} value={field.value} - minDate={disablePastDates ? new Date() : undefined} - maxDate={disableFutureDates ? new Date() : undefined} + minDate={!pastDatesAllowed ? new Date() : undefined} + maxDate={!futureDatesAllowed ? new Date() : undefined} /> ); From f95c9bb35cb3177965c63ca8231a86337fb4bdb9 Mon Sep 17 00:00:00 2001 From: Jovan Ssebaggala Date: Wed, 13 Nov 2024 14:05:00 +0300 Subject: [PATCH 5/5] Update config-schema --- .../esm-patient-registration-app/src/config-schema.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/esm-patient-registration-app/src/config-schema.ts b/packages/esm-patient-registration-app/src/config-schema.ts index b2181f72a..e6d6f9c73 100644 --- a/packages/esm-patient-registration-app/src/config-schema.ts +++ b/packages/esm-patient-registration-app/src/config-schema.ts @@ -1,4 +1,5 @@ import { Type, validator, validators } from '@openmrs/esm-framework'; +import _default from 'yup/lib/locale'; export interface SectionDefinition { id: string; @@ -176,6 +177,16 @@ export const esmPatientRegistrationSchema = { _default: '', _description: 'Placeholder that will appear in the input.', }, + allowFutureDates: { + _type: Type.Boolean, + _default: true, + _description: 'Indicates whether the date input field should allow the selection of future dates or not.', + }, + allowPastDates: { + _type: Type.Boolean, + _default: true, + _description: 'Indicates whether the date input field should allow the selection of past dates or not.', + }, validation: { required: { _type: Type.Boolean, _default: false }, matches: {