Skip to content

Commit

Permalink
feat: add org unit selector in schedule event form
Browse files Browse the repository at this point in the history
  • Loading branch information
henrikmv committed Jan 2, 2025
1 parent 0a73ff8 commit e4e6b9b
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 189 deletions.
31 changes: 17 additions & 14 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-12-05T11:39:04.447Z\n"
"PO-Revision-Date: 2024-12-05T11:39:04.447Z\n"
"POT-Creation-Date: 2025-01-02T12:54:39.984Z\n"
"PO-Revision-Date: 2025-01-02T12:54:39.984Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down Expand Up @@ -1270,6 +1270,12 @@ msgstr "Write a note about this event"
msgid "This event doesn't have any notes"
msgstr "This event doesn't have any notes"

msgid "after"
msgstr "after"

msgid "before"
msgstr "before"

msgid "Schedule date info"
msgstr "Schedule date info"

Expand All @@ -1284,32 +1290,32 @@ msgid_plural "The scheduled date is {{count}} days {{position}} the suggested da
msgstr[0] "The scheduled date is {{count}} day {{position}} the suggested date."
msgstr[1] "The scheduled date is {{count}} days {{position}} the suggested date."

msgid "after"
msgstr "after"

msgid "before"
msgstr "before"

msgid "There are {{count}} scheduled event in {{orgUnitName}} on this day."
msgid_plural "There are {{count}} scheduled event in {{orgUnitName}} on this day."
msgstr[0] "There are {{count}} scheduled event in {{orgUnitName}} on this day."
msgstr[1] "There are {{count}} scheduled events in {{orgUnitName}} on this day."

msgid "Schedule date / Due date"
msgstr "Schedule date / Due date"

msgid "Please provide a valid organisation unit"
msgstr "Please provide a valid organisation unit"

msgid "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}"
msgstr "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}"

msgid "Schedule info"
msgstr "Schedule info"

msgid "Schedule date / Due date"
msgstr "Schedule date / Due date"

msgid "Event notes"
msgstr "Event notes"

msgid "Write a note about this scheduled event"
msgstr "Write a note about this scheduled event"

msgid "Program or stage is invalid"
msgstr "Program or stage is invalid"

msgid "Save note"
msgstr "Save note"

Expand Down Expand Up @@ -1406,9 +1412,6 @@ msgstr "Report date"
msgid "Please enter a date"
msgstr "Please enter a date"

msgid "Please provide a valid organisation unit"
msgstr "Please provide a valid organisation unit"

msgid "Please select a valid event"
msgstr "Please select a valid event"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
}

.orgUnitLabel {
padding-top: 3px;
padding-top: 13px;
}

.selectLabel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ const styles = {
infoBox: {
marginTop: spacersNum.dp16,
padding: spacersNum.dp16,
width: 'fit-content',
},
};

const getDayDifference = (startDate: string, endDate: string): number =>
moment(startDate).diff(moment(endDate), 'days');

const InfoBoxPlain = ({
scheduleDate,
suggestedScheduleDate,
Expand All @@ -21,38 +25,49 @@ const InfoBoxPlain = ({
orgUnitName,
classes,
}: Props) => {
if (!scheduleDate || !suggestedScheduleDate) { return null; }
const differenceScheduleDateAndSuggestedDate = moment(scheduleDate).diff(moment(suggestedScheduleDate), 'days');
if (!scheduleDate || !suggestedScheduleDate) {
return null;
}

const dayDifference = getDayDifference(scheduleDate, suggestedScheduleDate);
const absoluteDifference = Math.abs(dayDifference);
const position = dayDifference > 0 ? i18n.t('after') : i18n.t('before');
const scheduledDateMatchesSuggested = scheduleDate === suggestedScheduleDate;

return (
<NoticeBox className={classes.infoBox} title={i18n.t('Schedule date info')}>
{hideDueDate ? <>
{i18n.t('Scheduled automatically for {{suggestedScheduleDate}}', { suggestedScheduleDate })}
</> : <>
{scheduleDate === suggestedScheduleDate ?
i18n.t('The scheduled date matches the suggested date, but can be changed if needed.')
:
i18n.t(
'The scheduled date is {{count}} days {{position}} the suggested date.',
{
position: differenceScheduleDateAndSuggestedDate > 0 ? i18n.t('after') : i18n.t('before'),
count: Math.abs(differenceScheduleDateAndSuggestedDate),
defaultValue: 'The scheduled date is {{count}} day {{position}} the suggested date.',
defaultValue_plural: 'The scheduled date is {{count}} days {{position}} the suggested date.',
})
}
{' '}
{i18n.t('There are {{count}} scheduled event in {{orgUnitName}} on this day.', {
count: eventCountInOrgUnit,
orgUnitName,
defaultValue: 'There are {{count}} scheduled event in {{orgUnitName}} on this day.',
defaultValue_plural: 'There are {{count}} scheduled events in {{orgUnitName}} on this day.',
interpolation: {
escapeValue: false,
},
})}</>}
{hideDueDate ? (
<>
{i18n.t('Scheduled automatically for {{suggestedScheduleDate}}', { suggestedScheduleDate })}
</>
) : (
<>
{scheduledDateMatchesSuggested
? i18n.t('The scheduled date matches the suggested date, but can be changed if needed.')
: i18n.t(
'The scheduled date is {{count}} days {{position}} the suggested date.',
{
position,
count: absoluteDifference,
defaultValue: 'The scheduled date is {{count}} day {{position}} the suggested date.',
defaultValue_plural: 'The scheduled date is {{count}} days {{position}} the suggested date.',
},
)
}
{' '}
{i18n.t('There are {{count}} scheduled event in {{orgUnitName}} on this day.', {
count: eventCountInOrgUnit,
orgUnitName,
defaultValue: 'There are {{count}} scheduled event in {{orgUnitName}} on this day.',
defaultValue_plural: 'There are {{count}} scheduled events in {{orgUnitName}} on this day.',
interpolation: {
escapeValue: false,
},
})}
</>
)}
</NoticeBox>
);
};

export const InfoBox: ComponentType<$Diff<Props, CssClasses>> = (withStyles(styles)(InfoBoxPlain));
export const InfoBox: ComponentType<$Diff<Props, CssClasses>> = withStyles(styles)(InfoBoxPlain);
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
// @flow
import React, { type ComponentType } from 'react';
import { spacersNum } from '@dhis2/ui';
import i18n from '@dhis2/d2-i18n';
import withStyles from '@material-ui/core/styles/withStyles';
import { DateField } from 'capture-core/components/FormFields/New';
import { InfoBox } from '../InfoBox';
import { spacers, colors } from '@dhis2/ui';
import {
DateField,
withDefaultFieldContainer,
withLabel,
withDisplayMessages,
withInternalChangeHandler,
} from 'capture-core/components/FormFields/New';

import type { Props } from './scheduleDate.types';
import labelTypeClasses from './dataEntryFieldLabels.module.css';
import { InfoBox } from '../InfoBox';
import { baseInputStyles } from '../ScheduleOrgUnit/commonProps';


const ScheduleDataField = withDefaultFieldContainer()(
withLabel({
onGetCustomFieldLabeClass: () => labelTypeClasses.dateLabel,
})(
withDisplayMessages()(
withInternalChangeHandler()(
DateField,
),
),
),
);

const styles = {
container: {
infoBox: {
padding: `0 ${spacers.dp16} ${spacers.dp16} ${spacers.dp16}`,
},
fieldWrapper: {
display: 'flex',
marginTop: spacersNum.dp4,
flexWrap: 'wrap',
},
button: {
paddingRight: spacersNum.dp16,
fieldLabel: {
color: colors.grey900,
padding: `${spacers.dp16} ${spacers.dp24} 0 ${spacers.dp16}`,
fontSize: '14px',
},
};

Expand All @@ -22,35 +50,51 @@ const ScheduleDatePlain = ({
setScheduleDate,
orgUnit,
serverSuggestedScheduleDate,
displayDueDateLabel,
eventCountInOrgUnit,
classes,
hideDueDate,
}: Props) => (<>
{!hideDueDate && <div className={classes.container}>
<DateField
value={scheduleDate}
width="100%"
calendarWidth={350}
onSetFocus={() => {}}
onFocus={() => { }}
onRemoveFocus={() => { }}
onBlur={(e, internalComponentError) => {
const { error } = internalComponentError;
if (error) {
setScheduleDate('');
return;
}
setScheduleDate(e);
}}
/>
</div>}
<InfoBox
scheduleDate={serverScheduleDate}
suggestedScheduleDate={serverSuggestedScheduleDate}
eventCountInOrgUnit={eventCountInOrgUnit}
orgUnitName={orgUnit?.name}
hideDueDate={hideDueDate}
/>
</>);
}: Props) => (
<div className={classes.fieldWrapper}>
{!hideDueDate ?
<ScheduleDataField
label={i18n.t('Schedule date / Due date')}
required
value={scheduleDate}
width="100%"
calendarWidth={350}
styles={baseInputStyles}
onSetFocus={() => { }}
onFocus={() => { }}
onRemoveFocus={() => { }}
onBlur={(e, internalComponentError) => {
const { error } = internalComponentError;
if (error) {
setScheduleDate('');
return;
}
setScheduleDate(e);
}}
/>
:
<div className={classes.fieldLabel}>
{displayDueDateLabel ?? i18n.t('Schedule date / Due date', {
interpolation: { escapeValue: false },
},
)}
</div>
}
<div className={classes.infoBox}>
<InfoBox
scheduleDate={serverScheduleDate}
suggestedScheduleDate={serverSuggestedScheduleDate}
eventCountInOrgUnit={eventCountInOrgUnit}
orgUnitName={orgUnit?.name}
hideDueDate={hideDueDate}
/>
</div>
</div>
);


export const ScheduleDate: ComponentType<$Diff<Props, CssClasses>> = (withStyles(styles)(ScheduleDatePlain));
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dateLabel {
padding-top: 13px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type Props = {|
stageId: string,
programId: string,
enrolledAt: string,
displayDueDateLabel: string,
scheduleDate?: ?string,
serverScheduleDate?: ?string,
setScheduleDate: (date: string) => void,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// @flow
import React, { useState } from 'react';
import i18n from '@dhis2/d2-i18n';
import { isValidOrgUnit } from 'capture-core-utils/validators/form';
import labelTypeClasses from './dataEntryFieldLabels.module.css';
import { baseInputStyles } from './commonProps';
import {
SingleOrgUnitSelectField,
withDefaultFieldContainer,
withDisplayMessages,
withInternalChangeHandler,
withLabel,
} from '../../FormFields/New';

type OrgUnitValue = {|
checked: boolean,
id: string,
children: number,
name: string,
displayName: string,
path: string,
selected: string[],
|}

type Props = {
onSelectOrgUnit: (orgUnit: OrgUnitValue) => void,
onDeselectOrgUnit: () => void,
orgUnit: OrgUnitValue,
};

const OrgUnitFieldForForm = withDefaultFieldContainer()(
withLabel({
onGetCustomFieldLabeClass: () => labelTypeClasses.dateLabel,
})(
withDisplayMessages()(
withInternalChangeHandler()(
SingleOrgUnitSelectField,
),
),
),
);

export const ScheduleOrgUnit = ({
onSelectOrgUnit,
onDeselectOrgUnit,
orgUnit,
}: Props) => {
const [touched, setTouched] = useState(false);

const handleSelect = (event) => {
setTouched(true);
onSelectOrgUnit(event);
};

const handleDeselect = () => {
setTouched(true);
onDeselectOrgUnit();
};

const shouldShowError = (!isValidOrgUnit(orgUnit) && touched);
const errorMessages = i18n.t('Please provide a valid organisation unit');

return (
<OrgUnitFieldForForm
label={i18n.t('Organisation unit')}
value={orgUnit}
required
onSelectClick={handleSelect}
onBlur={handleDeselect}
styles={baseInputStyles}
errorMessage={shouldShowError && errorMessages}
/>
);
};
Loading

0 comments on commit e4e6b9b

Please sign in to comment.