@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2716 → 5.4.2-pre.2722

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/805.js +1 -0
  3. package/dist/805.js.map +1 -0
  4. package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
  5. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +27 -27
  6. package/dist/main.js +27 -27
  7. package/dist/main.js.map +1 -1
  8. package/dist/routes.json +1 -1
  9. package/package.json +1 -1
  10. package/src/config-schema.ts +97 -0
  11. package/src/contact-list/contact-tracing-history.component.tsx +18 -15
  12. package/src/maternal-and-child-health/partography/components/pulse-bp-graph.component.tsx +1 -0
  13. package/src/maternal-and-child-health/partography/components/temperature-graph.component.tsx +218 -0
  14. package/src/maternal-and-child-health/partography/components/uterine-contractions-graph.component.tsx +209 -0
  15. package/src/maternal-and-child-health/partography/forms/cervical-contractions-form.component.tsx +211 -0
  16. package/src/maternal-and-child-health/partography/forms/cervix-form.component.tsx +354 -0
  17. package/src/maternal-and-child-health/partography/forms/drugs-iv-fluids-form.component.tsx +321 -0
  18. package/src/maternal-and-child-health/partography/forms/fetal-heart-rate-form.component.tsx +275 -0
  19. package/src/maternal-and-child-health/partography/forms/index.ts +9 -0
  20. package/src/maternal-and-child-health/partography/forms/membrane-amniotic-fluid-form.component.tsx +330 -0
  21. package/src/maternal-and-child-health/partography/forms/oxytocin-form.component.tsx +207 -0
  22. package/src/maternal-and-child-health/partography/forms/pulse-bp-form.component.tsx +174 -0
  23. package/src/maternal-and-child-health/partography/forms/temperature-form.component.tsx +210 -0
  24. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.component.tsx +218 -0
  25. package/src/maternal-and-child-health/partography/forms/time-picker-dropdown.scss +107 -0
  26. package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.component.tsx +174 -0
  27. package/src/maternal-and-child-health/partography/forms/time-picker-with-clock.scss +178 -0
  28. package/src/maternal-and-child-health/partography/forms/urine-test-form.component.tsx +255 -0
  29. package/src/maternal-and-child-health/partography/forms/useCervixData.ts +16 -0
  30. package/src/maternal-and-child-health/partography/graphs/cervical-contractions-graph.component.tsx +266 -0
  31. package/src/maternal-and-child-health/partography/graphs/cervix-graph.component.tsx +429 -0
  32. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph-wrapper.component.tsx +163 -0
  33. package/src/maternal-and-child-health/partography/graphs/drugs-iv-fluids-graph.component.tsx +82 -0
  34. package/src/maternal-and-child-health/partography/graphs/fetal-heart-rate-graph.component.tsx +359 -0
  35. package/src/maternal-and-child-health/partography/graphs/index.ts +10 -0
  36. package/src/maternal-and-child-health/partography/graphs/membrane-amniotic-fluid-graph.component.tsx +266 -0
  37. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph-wrapper.component.tsx +190 -0
  38. package/src/maternal-and-child-health/partography/graphs/oxytocin-graph.component.tsx +126 -0
  39. package/src/maternal-and-child-health/partography/graphs/partograph-graph.component.tsx +266 -0
  40. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph-wrapper.component.tsx +298 -0
  41. package/src/maternal-and-child-health/partography/graphs/pulse-bp-graph.component.tsx +267 -0
  42. package/src/maternal-and-child-health/partography/graphs/temperature-graph.component.tsx +242 -0
  43. package/src/maternal-and-child-health/partography/graphs/urine-test-graph.component.tsx +246 -0
  44. package/src/maternal-and-child-health/partography/partograph.component.tsx +2141 -118
  45. package/src/maternal-and-child-health/partography/partography-dashboard.meta.ts +8 -0
  46. package/src/maternal-and-child-health/partography/partography-data-form.scss +163 -0
  47. package/src/maternal-and-child-health/partography/partography.resource.ts +233 -326
  48. package/src/maternal-and-child-health/partography/partography.scss +1341 -3
  49. package/src/maternal-and-child-health/partography/resources/blood-pressure.resource.ts +96 -0
  50. package/src/maternal-and-child-health/partography/resources/cervical-dilation.resource.ts +109 -0
  51. package/src/maternal-and-child-health/partography/resources/cervix.resource.ts +362 -0
  52. package/src/maternal-and-child-health/partography/resources/descent-of-head.resource.ts +101 -0
  53. package/src/maternal-and-child-health/partography/resources/drugs-fluids.resource.ts +88 -0
  54. package/src/maternal-and-child-health/partography/resources/fetal-heart-rate.resource.ts +122 -0
  55. package/src/maternal-and-child-health/partography/resources/maternal-pulse.resource.ts +77 -0
  56. package/src/maternal-and-child-health/partography/resources/membrane-amniotic-fluid.resource.ts +108 -0
  57. package/src/maternal-and-child-health/partography/resources/oxytocin.resource.ts +159 -0
  58. package/src/maternal-and-child-health/partography/resources/progress-events.resource.ts +6 -0
  59. package/src/maternal-and-child-health/partography/resources/pulse-bp-combined.resource.ts +53 -0
  60. package/src/maternal-and-child-health/partography/resources/temperature.resource.ts +84 -0
  61. package/src/maternal-and-child-health/partography/resources/uterine-contractions.resource.ts +173 -0
  62. package/src/maternal-and-child-health/partography/table/temperature-table.component.tsx +99 -0
  63. package/src/maternal-and-child-health/partography/table/uterine-contractions-table.component.tsx +86 -0
  64. package/src/maternal-and-child-health/partography/types/index.ts +319 -101
  65. package/dist/397.js +0 -1
  66. package/dist/397.js.map +0 -1
@@ -0,0 +1,275 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useForm, Controller } from 'react-hook-form';
4
+ import { Modal, Grid, Column, NumberInput, Select, SelectItem, InlineNotification } from '@carbon/react';
5
+ import { saveFetalHeartRateData } from '../resources/fetal-heart-rate.resource';
6
+ import TimePickerDropdown from './time-picker-dropdown.component';
7
+ import styles from '../partography-data-form.scss';
8
+
9
+ type FetalHeartRateFormData = {
10
+ hour: string;
11
+ time: string;
12
+ fetalHeartRate: string;
13
+ };
14
+
15
+ type FetalHeartRateFormProps = {
16
+ isOpen: boolean;
17
+ onClose: () => void;
18
+ onSubmit: (data: { hour: number; time: string; fetalHeartRate: number }) => void;
19
+ onDataSaved?: () => void;
20
+ selectedHours?: number[];
21
+ existingTimeEntries?: Array<{ hour: number; time: string }>;
22
+ patient?: {
23
+ uuid: string;
24
+ name: string;
25
+ gender: string;
26
+ age: string;
27
+ };
28
+ };
29
+
30
+ const FetalHeartRateForm: React.FC<FetalHeartRateFormProps> = ({
31
+ isOpen,
32
+ onClose,
33
+ onSubmit,
34
+ onDataSaved,
35
+ selectedHours = [],
36
+ existingTimeEntries = [],
37
+ patient,
38
+ }) => {
39
+ const { t } = useTranslation();
40
+ const [isSaving, setIsSaving] = React.useState(false);
41
+ const [saveError, setSaveError] = React.useState<string | null>(null);
42
+ const [saveSuccess, setSaveSuccess] = React.useState(false);
43
+
44
+ const {
45
+ control,
46
+ handleSubmit,
47
+ reset,
48
+ setError,
49
+ clearErrors,
50
+ formState: { errors },
51
+ } = useForm<FetalHeartRateFormData>({
52
+ defaultValues: {
53
+ hour: '',
54
+ time: '',
55
+ fetalHeartRate: '',
56
+ },
57
+ });
58
+ const generateHourOptions = () => {
59
+ const options = [];
60
+ for (let i = 0; i <= 24; i++) {
61
+ if (i === 0) {
62
+ options.push({ value: '0.5', label: '30min' });
63
+ } else {
64
+ options.push({ value: i.toString(), label: `${i}hr` });
65
+ }
66
+ if (i < 24) {
67
+ const halfHourValue = (i + 0.5).toString();
68
+ const halfHourLabel = i === 0 ? '1hr' : `${i}hr 30min`;
69
+ options.push({ value: halfHourValue, label: halfHourLabel });
70
+ }
71
+ }
72
+ return options;
73
+ };
74
+
75
+ const hourOptions = generateHourOptions();
76
+
77
+ const handleFormSubmit = async (data: FetalHeartRateFormData) => {
78
+ const hourValue = parseFloat(data.hour);
79
+ const fetalHeartRateValue = parseInt(data.fetalHeartRate);
80
+
81
+ clearErrors();
82
+ setSaveError(null);
83
+ setSaveSuccess(false);
84
+
85
+ if (!data.hour || isNaN(hourValue) || hourValue < 0 || hourValue > 24) {
86
+ setError('hour', { type: 'manual', message: t('hourRequired', 'Please select a valid hour') });
87
+ return;
88
+ }
89
+ if (!data.time || data.time.trim() === '') {
90
+ setError('time', { type: 'manual', message: t('timeRequired', 'Time is required') });
91
+ return;
92
+ }
93
+ if (!data.fetalHeartRate || isNaN(fetalHeartRateValue)) {
94
+ setError('fetalHeartRate', {
95
+ type: 'manual',
96
+ message: t('fetalHeartRateRequired', 'Fetal heart rate is required'),
97
+ });
98
+ return;
99
+ }
100
+ if (fetalHeartRateValue < 80) {
101
+ setError('fetalHeartRate', {
102
+ type: 'manual',
103
+ message: t('fetalHeartRateTooLow', 'Fetal heart rate is too low. Minimum is 80 bpm'),
104
+ });
105
+ return;
106
+ }
107
+
108
+ if (fetalHeartRateValue > 200) {
109
+ setError('fetalHeartRate', {
110
+ type: 'manual',
111
+ message: t('fetalHeartRateTooHigh', 'Fetal heart rate is too high. Maximum is 200 bpm'),
112
+ });
113
+ return;
114
+ }
115
+
116
+ if (patient?.uuid) {
117
+ setIsSaving(true);
118
+ try {
119
+ const result = await saveFetalHeartRateData(
120
+ patient.uuid,
121
+ {
122
+ hour: hourValue,
123
+ time: data.time,
124
+ fetalHeartRate: fetalHeartRateValue,
125
+ },
126
+ t,
127
+ );
128
+
129
+ if (result.success) {
130
+ setSaveSuccess(true);
131
+ if (onDataSaved) {
132
+ onDataSaved();
133
+ }
134
+ onSubmit({
135
+ hour: hourValue,
136
+ time: data.time,
137
+ fetalHeartRate: fetalHeartRateValue,
138
+ });
139
+ reset();
140
+ setTimeout(() => {
141
+ setSaveSuccess(false);
142
+ onClose();
143
+ }, 1500);
144
+ } else {
145
+ setSaveError(result.message || t('saveError', 'Failed to save data'));
146
+ }
147
+ } catch (error) {
148
+ setSaveError(error?.message || t('saveError', 'Failed to save data'));
149
+ } finally {
150
+ setIsSaving(false);
151
+ }
152
+ } else {
153
+ onSubmit({
154
+ hour: hourValue,
155
+ time: data.time,
156
+ fetalHeartRate: fetalHeartRateValue,
157
+ });
158
+ reset();
159
+ }
160
+ };
161
+
162
+ const handleClose = () => {
163
+ reset();
164
+ clearErrors();
165
+ setSaveError(null);
166
+ setSaveSuccess(false);
167
+ onClose();
168
+ };
169
+
170
+ return (
171
+ <Modal
172
+ open={isOpen}
173
+ onRequestClose={handleClose}
174
+ modalHeading={t('fetalHeartRateData', 'Foetal Heart Rate Data')}
175
+ modalLabel=""
176
+ primaryButtonText={isSaving ? t('saving', 'Saving...') : t('save', 'Save')}
177
+ primaryButtonDisabled={isSaving}
178
+ secondaryButtonText={t('cancel', 'Cancel')}
179
+ onRequestSubmit={handleSubmit(handleFormSubmit)}
180
+ onSecondarySubmit={handleClose}
181
+ className={styles.cervixModal}
182
+ size="md">
183
+ {saveSuccess && (
184
+ <InlineNotification
185
+ kind="success"
186
+ title={t('saveSuccess', 'Data saved successfully')}
187
+ subtitle={t('fetalHeartRateDataSaved', 'Fetal heart rate data has been saved')}
188
+ hideCloseButton
189
+ />
190
+ )}
191
+
192
+ {saveError && (
193
+ <InlineNotification
194
+ kind="error"
195
+ title={t('saveError', 'Error saving data')}
196
+ subtitle={saveError}
197
+ onCloseButtonClick={() => setSaveError(null)}
198
+ />
199
+ )}
200
+
201
+ <Grid className={styles.formGrid}>
202
+ <Column lg={8} md={4} sm={2}>
203
+ <div className={styles.formField}>
204
+ <Controller
205
+ name="hour"
206
+ control={control}
207
+ render={({ field, fieldState }) => (
208
+ <Select
209
+ id="hour-select"
210
+ labelText={t('hour', 'Hour')}
211
+ invalid={!!fieldState.error}
212
+ invalidText={fieldState.error?.message}
213
+ value={field.value}
214
+ onChange={(e) => field.onChange(e.target.value)}>
215
+ <SelectItem value="" text={t('admissionTime', 'Admission')} />
216
+ {hourOptions.map((option) => (
217
+ <SelectItem key={option.value} value={option.value} text={option.label} />
218
+ ))}
219
+ </Select>
220
+ )}
221
+ />
222
+ </div>
223
+ </Column>
224
+
225
+ <Column lg={8} md={4} sm={2}>
226
+ <div className={styles.formField}>
227
+ <Controller
228
+ name="time"
229
+ control={control}
230
+ render={({ field, fieldState }) => (
231
+ <div>
232
+ <label className={styles.fieldLabel}>{t('time', 'Time')}</label>
233
+ <TimePickerDropdown
234
+ id="time-picker"
235
+ labelText=""
236
+ value={field.value}
237
+ onChange={field.onChange}
238
+ existingTimeEntries={existingTimeEntries}
239
+ invalid={!!fieldState.error}
240
+ invalidText={fieldState.error?.message}
241
+ />
242
+ </div>
243
+ )}
244
+ />
245
+ </div>
246
+ </Column>
247
+
248
+ <Column lg={16} md={8} sm={4}>
249
+ <div className={styles.formField}>
250
+ <Controller
251
+ name="fetalHeartRate"
252
+ control={control}
253
+ render={({ field, fieldState }) => (
254
+ <NumberInput
255
+ id="fetal-heart-rate-input"
256
+ label={t('fetalHeartRate', 'Foetal Heart Rate')}
257
+ min={80}
258
+ max={200}
259
+ step={1}
260
+ value={field.value}
261
+ onChange={(_, state) => field.onChange(state.value)}
262
+ invalid={!!fieldState.error}
263
+ invalidText={fieldState.error?.message}
264
+ size="lg"
265
+ />
266
+ )}
267
+ />
268
+ </div>
269
+ </Column>
270
+ </Grid>
271
+ </Modal>
272
+ );
273
+ };
274
+
275
+ export default FetalHeartRateForm;
@@ -0,0 +1,9 @@
1
+ export { default as CervixForm } from './cervix-form.component';
2
+ export { default as FetalHeartRateForm } from './fetal-heart-rate-form.component';
3
+ export { default as MembraneAmnioticFluidForm } from './membrane-amniotic-fluid-form.component';
4
+ export { default as CervicalContractionsForm } from './cervical-contractions-form.component';
5
+ export { default as OxytocinForm } from './oxytocin-form.component';
6
+ export { default as DrugsIVFluidsForm } from './drugs-iv-fluids-form.component';
7
+ export { default as PulseBPForm } from './pulse-bp-form.component';
8
+ export { default as TemperatureForm } from './temperature-form.component';
9
+ export { default as UrineTestForm } from './urine-test-form.component';
@@ -0,0 +1,330 @@
1
+ import React, { useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useForm, Controller } from 'react-hook-form';
4
+ import { Button, Modal, Grid, Column, Select, SelectItem, TimePicker } from '@carbon/react';
5
+ import TimePickerDropdown from './time-picker-dropdown.component';
6
+ import styles from '../partography-data-form.scss';
7
+ import { MEMBRANE_TIME_SLOT_OPTIONS } from '../types';
8
+ import {
9
+ AMNIOTIC_MEMBRANE_INTACT_CONCEPT,
10
+ AMNIOTIC_CLEAR_LIQUOR_CONCEPT,
11
+ AMNIOTIC_MECONIUM_STAINED_CONCEPT,
12
+ AMNIOTIC_ABSENT_CONCEPT,
13
+ AMNIOTIC_BLOOD_STAINED_CONCEPT,
14
+ MOULDING_NONE_CONCEPT,
15
+ MOULDING_SLIGHT_CONCEPT,
16
+ MOULDING_MODERATE_CONCEPT,
17
+ MOULDING_SEVERE_CONCEPT,
18
+ } from '../../../config-schema';
19
+
20
+ type MembraneAmnioticFluidFormData = {
21
+ timeSlot: string;
22
+ exactTime: string;
23
+ amnioticFluid: string;
24
+ moulding: string;
25
+ };
26
+
27
+ type MembraneAmnioticFluidFormProps = {
28
+ isOpen: boolean;
29
+ onClose: () => void;
30
+ onSubmit: (data: { timeSlot: string; exactTime: string; amnioticFluid: string; moulding: string }) => void;
31
+ onDataSaved?: () => void;
32
+ existingTimeEntries?: Array<{ timeSlot: string; exactTime: string }>;
33
+ patient?: {
34
+ uuid: string;
35
+ name: string;
36
+ gender: string;
37
+ age: string;
38
+ };
39
+ };
40
+
41
+ const MembraneAmnioticFluidForm: React.FC<MembraneAmnioticFluidFormProps> = ({
42
+ isOpen,
43
+ onClose,
44
+ onSubmit,
45
+ onDataSaved,
46
+ existingTimeEntries = [],
47
+ patient,
48
+ }) => {
49
+ const { t } = useTranslation();
50
+
51
+ const {
52
+ control,
53
+ handleSubmit,
54
+ reset,
55
+ setError,
56
+ clearErrors,
57
+ formState: { errors },
58
+ } = useForm<MembraneAmnioticFluidFormData>({
59
+ defaultValues: {
60
+ timeSlot: '',
61
+ exactTime: '',
62
+ amnioticFluid: '',
63
+ moulding: '',
64
+ },
65
+ });
66
+
67
+ const timeSlotOptions = useMemo(
68
+ () => MEMBRANE_TIME_SLOT_OPTIONS as unknown as { value: string; label: string }[],
69
+ [],
70
+ );
71
+
72
+ const latestUsedTimeSlot = React.useMemo(() => {
73
+ if (!existingTimeEntries || existingTimeEntries.length === 0) {
74
+ return null;
75
+ }
76
+ const sortedTimeSlots = existingTimeEntries
77
+ .map((entry) => entry.timeSlot)
78
+ .slice()
79
+ .sort((a, b) => {
80
+ const getMinutes = (time: string) => {
81
+ const [hours, minutes] = time.split(':').map(Number);
82
+
83
+ const adjustedHours = hours <= 5 ? hours + 24 : hours;
84
+ return adjustedHours * 60 + minutes;
85
+ };
86
+ return getMinutes(a) - getMinutes(b);
87
+ });
88
+ return sortedTimeSlots[sortedTimeSlots.length - 1];
89
+ }, [existingTimeEntries]);
90
+
91
+ const isTimeSlotDisabled = (timeSlot: string) => {
92
+ if (!latestUsedTimeSlot) {
93
+ return false;
94
+ }
95
+
96
+ const getMinutes = (time: string) => {
97
+ const [hours, minutes] = time.split(':').map(Number);
98
+
99
+ const adjustedHours = hours <= 5 ? hours + 24 : hours;
100
+ return adjustedHours * 60 + minutes;
101
+ };
102
+
103
+ return getMinutes(timeSlot) <= getMinutes(latestUsedTimeSlot);
104
+ };
105
+
106
+ const amnioticFluidOptions = useMemo(
107
+ () => [
108
+ {
109
+ value: 'Membrane intact',
110
+ label: t('membraneIntact', 'Membrane intact'),
111
+ concept: AMNIOTIC_MEMBRANE_INTACT_CONCEPT,
112
+ },
113
+ { value: 'Clear liquor', label: t('clearLiquor', 'Clear liquor'), concept: AMNIOTIC_CLEAR_LIQUOR_CONCEPT },
114
+ {
115
+ value: 'Meconium Stained',
116
+ label: t('meconiumStained', 'Meconium Stained'),
117
+ concept: AMNIOTIC_MECONIUM_STAINED_CONCEPT,
118
+ },
119
+ { value: 'Absent', label: t('absent', 'Absent'), concept: AMNIOTIC_ABSENT_CONCEPT },
120
+ {
121
+ value: 'Blood Stained',
122
+ label: t('bloodStained', 'Blood Stained'),
123
+ concept: AMNIOTIC_BLOOD_STAINED_CONCEPT,
124
+ },
125
+ ],
126
+ [t],
127
+ );
128
+
129
+ const mouldingOptions = useMemo(
130
+ () => [
131
+ { value: '0', label: '0', concept: MOULDING_NONE_CONCEPT },
132
+ { value: '+', label: '+', concept: MOULDING_SLIGHT_CONCEPT },
133
+ { value: '++', label: '++', concept: MOULDING_MODERATE_CONCEPT },
134
+ { value: '+++', label: '+++', concept: MOULDING_SEVERE_CONCEPT },
135
+ ],
136
+ [],
137
+ );
138
+
139
+ const handleFormSubmit = (data: MembraneAmnioticFluidFormData) => {
140
+ clearErrors();
141
+
142
+ let hasErrors = false;
143
+
144
+ if (!data.timeSlot || data.timeSlot.trim() === '') {
145
+ setError('timeSlot', { type: 'manual', message: t('timeSlotRequired', 'Please select a time slot') });
146
+ hasErrors = true;
147
+ }
148
+
149
+ if (!data.exactTime || data.exactTime.trim() === '') {
150
+ setError('exactTime', { type: 'manual', message: t('exactTimeRequired', 'Exact time is required') });
151
+ hasErrors = true;
152
+ }
153
+
154
+ if (!data.amnioticFluid || data.amnioticFluid.trim() === '') {
155
+ setError('amnioticFluid', {
156
+ type: 'manual',
157
+ message: t('amnioticFluidRequired', 'Please select amniotic fluid status'),
158
+ });
159
+ hasErrors = true;
160
+ }
161
+
162
+ if (!data.moulding || data.moulding.trim() === '') {
163
+ setError('moulding', {
164
+ type: 'manual',
165
+ message: t('mouldingRequired', 'Please select moulding status'),
166
+ });
167
+ hasErrors = true;
168
+ }
169
+
170
+ if (hasErrors) {
171
+ alert(t('formValidationError', 'Please fill in all required fields before submitting.'));
172
+ return;
173
+ }
174
+
175
+ if (isTimeSlotDisabled(data.timeSlot)) {
176
+ setError('timeSlot', {
177
+ type: 'manual',
178
+ message: t('timeSlotDisabled', 'Selected time slot is not available. Please select a later time.'),
179
+ });
180
+ alert(t('timeSlotValidationError', 'Please select a valid time slot that comes after the previous entry.'));
181
+ return;
182
+ }
183
+
184
+ onSubmit({
185
+ timeSlot: data.timeSlot,
186
+ exactTime: data.exactTime,
187
+ amnioticFluid: data.amnioticFluid,
188
+ moulding: data.moulding,
189
+ });
190
+
191
+ reset();
192
+ };
193
+
194
+ const handleClose = () => {
195
+ reset();
196
+ clearErrors();
197
+ onClose();
198
+ };
199
+
200
+ return (
201
+ <Modal
202
+ open={isOpen}
203
+ onRequestClose={handleClose}
204
+ modalHeading={t('membraneAmnioticFluidData', 'Membrane Amniotic Fluid & Moulding')}
205
+ modalLabel={patient ? `${patient.name}, ${patient.gender}, ${patient.age}` : ''}
206
+ primaryButtonText={t('save', 'Save')}
207
+ secondaryButtonText={t('cancel', 'Cancel')}
208
+ onRequestSubmit={handleSubmit(handleFormSubmit)}
209
+ onSecondarySubmit={handleClose}
210
+ className={styles.cervixModal}
211
+ size="md">
212
+ <Grid className={styles.formGrid}>
213
+ <Column lg={8} md={4} sm={2}>
214
+ <div className={styles.formField}>
215
+ <Controller
216
+ name="timeSlot"
217
+ control={control}
218
+ rules={{ required: t('timeSlotRequired', 'Please select a time slot') }}
219
+ render={({ field, fieldState }) => (
220
+ <Select
221
+ id="time-slot-select"
222
+ labelText={t('timeSlot', 'Time Slot')}
223
+ invalid={!!fieldState.error}
224
+ invalidText={fieldState.error?.message}
225
+ helperText={
226
+ latestUsedTimeSlot
227
+ ? t('timeSlotInfo', `Select a time after ${latestUsedTimeSlot}`)
228
+ : t('timeSlotInfoInitial', 'Select a time slot')
229
+ }
230
+ value={field.value}
231
+ onChange={(e) => field.onChange(e.target.value)}>
232
+ <SelectItem value="" text={t('chooseAnOption', 'Choose an option')} />
233
+ {timeSlotOptions.map((option) => {
234
+ const isDisabled = isTimeSlotDisabled(option.value);
235
+ return (
236
+ <SelectItem
237
+ key={option.value}
238
+ value={option.value}
239
+ text={option.label}
240
+ disabled={isDisabled}
241
+ className={isDisabled ? styles.disabledOption : undefined}
242
+ />
243
+ );
244
+ })}
245
+ </Select>
246
+ )}
247
+ />
248
+ </div>
249
+ </Column>
250
+
251
+ <Column lg={8} md={4} sm={2}>
252
+ <div className={styles.formField}>
253
+ <Controller
254
+ name="exactTime"
255
+ control={control}
256
+ rules={{ required: t('exactTimeRequired', 'Exact time is required') }}
257
+ render={({ field, fieldState }) => (
258
+ <div>
259
+ <label className={styles.fieldLabel}>{t('exactTime', 'Exact Time')}</label>
260
+ <TimePickerDropdown
261
+ id="exact-time-picker"
262
+ labelText=""
263
+ value={field.value}
264
+ onChange={field.onChange}
265
+ existingTimeEntries={existingTimeEntries.map((e) => ({
266
+ hour: parseInt((e.exactTime || '00:00').split(':')[0] || '0', 10) || 0,
267
+ time: e.exactTime || '',
268
+ }))}
269
+ invalid={!!fieldState.error}
270
+ invalidText={fieldState.error?.message}
271
+ />
272
+ </div>
273
+ )}
274
+ />
275
+ </div>
276
+ </Column>
277
+
278
+ <Column lg={8} md={4} sm={2}>
279
+ <div className={styles.formField}>
280
+ <Controller
281
+ name="amnioticFluid"
282
+ control={control}
283
+ rules={{ required: t('amnioticFluidRequired', 'Please select amniotic fluid status') }}
284
+ render={({ field, fieldState }) => (
285
+ <Select
286
+ id="amniotic-fluid-select"
287
+ labelText={t('amnioticFluid', 'Amniotic Fluid')}
288
+ invalid={!!fieldState.error}
289
+ invalidText={fieldState.error?.message}
290
+ value={field.value}
291
+ onChange={(e) => field.onChange(e.target.value)}>
292
+ <SelectItem value="" text={t('chooseAnOption', 'Choose an option')} />
293
+ {amnioticFluidOptions.map((option) => (
294
+ <SelectItem key={option.value} value={option.value} text={option.label} />
295
+ ))}
296
+ </Select>
297
+ )}
298
+ />
299
+ </div>
300
+ </Column>
301
+
302
+ <Column lg={8} md={4} sm={2}>
303
+ <div className={styles.formField}>
304
+ <Controller
305
+ name="moulding"
306
+ control={control}
307
+ rules={{ required: t('mouldingRequired', 'Please select moulding status') }}
308
+ render={({ field, fieldState }) => (
309
+ <Select
310
+ id="moulding-select"
311
+ labelText={t('moulding', 'Moulding')}
312
+ invalid={!!fieldState.error}
313
+ invalidText={fieldState.error?.message}
314
+ value={field.value}
315
+ onChange={(e) => field.onChange(e.target.value)}>
316
+ <SelectItem value="" text={t('chooseAnOption', 'Choose an option')} />
317
+ {mouldingOptions.map((option) => (
318
+ <SelectItem key={option.value} value={option.value} text={option.label} />
319
+ ))}
320
+ </Select>
321
+ )}
322
+ />
323
+ </div>
324
+ </Column>
325
+ </Grid>
326
+ </Modal>
327
+ );
328
+ };
329
+
330
+ export default MembraneAmnioticFluidForm;