@palladium-ethiopia/esm-clinical-workflow-app 5.4.2-pre.20

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 (89) hide show
  1. package/.turbo/turbo-build.log +14 -0
  2. package/README.md +1 -0
  3. package/dist/152.js +1 -0
  4. package/dist/152.js.map +1 -0
  5. package/dist/159.js +1 -0
  6. package/dist/159.js.map +1 -0
  7. package/dist/208.js +1 -0
  8. package/dist/208.js.map +1 -0
  9. package/dist/209.js +1 -0
  10. package/dist/209.js.map +1 -0
  11. package/dist/363.js +1 -0
  12. package/dist/363.js.map +1 -0
  13. package/dist/410.js +1 -0
  14. package/dist/410.js.map +1 -0
  15. package/dist/442.js +1 -0
  16. package/dist/442.js.map +1 -0
  17. package/dist/466.js +1 -0
  18. package/dist/466.js.map +1 -0
  19. package/dist/484.js +11 -0
  20. package/dist/484.js.map +1 -0
  21. package/dist/540.js +1 -0
  22. package/dist/540.js.map +1 -0
  23. package/dist/545.js +43 -0
  24. package/dist/545.js.map +1 -0
  25. package/dist/61.js +1 -0
  26. package/dist/61.js.map +1 -0
  27. package/dist/677.js +1 -0
  28. package/dist/677.js.map +1 -0
  29. package/dist/689.js +1 -0
  30. package/dist/689.js.map +1 -0
  31. package/dist/697.js +1 -0
  32. package/dist/697.js.map +1 -0
  33. package/dist/712.js +1 -0
  34. package/dist/712.js.map +1 -0
  35. package/dist/771.js +1 -0
  36. package/dist/771.js.map +1 -0
  37. package/dist/789.js +1 -0
  38. package/dist/789.js.map +1 -0
  39. package/dist/ethiopia-esm-clinical-workflow-app.js +6 -0
  40. package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +579 -0
  41. package/dist/ethiopia-esm-clinical-workflow-app.js.map +1 -0
  42. package/dist/main.js +16 -0
  43. package/dist/main.js.map +1 -0
  44. package/dist/routes.json +1 -0
  45. package/jest.config.js +3 -0
  46. package/package.json +59 -0
  47. package/rspack.config.js +1 -0
  48. package/src/config-schema.ts +69 -0
  49. package/src/constants.ts +2 -0
  50. package/src/createDashboardLink.tsx +10 -0
  51. package/src/dashboard.meta.ts +6 -0
  52. package/src/declarations.d.ts +3 -0
  53. package/src/helper.ts +115 -0
  54. package/src/index.ts +51 -0
  55. package/src/mru/billing-information/billing-information.resource.ts +139 -0
  56. package/src/mru/billing-information/billing-information.scss +55 -0
  57. package/src/mru/billing-information/billing-information.workspace.tsx +371 -0
  58. package/src/mru/dashboard.component.tsx +18 -0
  59. package/src/mru/mru.component.tsx +106 -0
  60. package/src/mru/mru.scss +28 -0
  61. package/src/patient-registration/patient-registration.resource.tsx +129 -0
  62. package/src/patient-registration/patient.registration.workspace.scss +47 -0
  63. package/src/patient-registration/patient.registration.workspace.tsx +443 -0
  64. package/src/patient-registration/useGenerateIdentifier.ts +26 -0
  65. package/src/patient-scoreboard/appointment-cards/checked-in-appointments.card.tsx +18 -0
  66. package/src/patient-scoreboard/appointment-cards/not-arrived-appointments.card.tsx +18 -0
  67. package/src/patient-scoreboard/appointment-cards/total-appointments.card.tsx +18 -0
  68. package/src/patient-scoreboard/hooks/useAppointmentList.ts +61 -0
  69. package/src/patient-scoreboard/hooks/useVisitList.ts +104 -0
  70. package/src/patient-scoreboard/metrics-card/metrics-card.component.scss +84 -0
  71. package/src/patient-scoreboard/metrics-card/metrics-card.component.tsx +40 -0
  72. package/src/patient-scoreboard/patient-scoreboard.component.scss +47 -0
  73. package/src/patient-scoreboard/patient-scoreboard.component.tsx +70 -0
  74. package/src/patient-scoreboard/visit-cards/active-visits.card.tsx +18 -0
  75. package/src/patient-scoreboard/visit-cards/scheduled-visits.card.tsx +18 -0
  76. package/src/patient-scoreboard/visit-cards/total-visits.card.tsx +18 -0
  77. package/src/patient-scoreboard/visits-table/visits-table.component.scss +31 -0
  78. package/src/patient-scoreboard/visits-table/visits-table.component.tsx +181 -0
  79. package/src/root.component.tsx +20 -0
  80. package/src/root.scss +10 -0
  81. package/src/routes.json +108 -0
  82. package/src/triage/patient-banner.component.tsx +59 -0
  83. package/src/triage/patient-banner.scss +14 -0
  84. package/src/triage/triage-dashboard.component.tsx +116 -0
  85. package/src/triage/triage-dashboard.scss +107 -0
  86. package/src/triage/triage.resource.tsx +44 -0
  87. package/src/triage/useStartVisitAndLaunchTriageForm.ts +156 -0
  88. package/src/types/index.ts +0 -0
  89. package/tsconfig.json +4 -0
@@ -0,0 +1,371 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import {
3
+ Button,
4
+ ButtonSet,
5
+ InlineLoading,
6
+ ContentSwitcher,
7
+ Dropdown,
8
+ Switch,
9
+ TextInput,
10
+ Form,
11
+ FormGroup,
12
+ } from '@carbon/react';
13
+ import {
14
+ DefaultWorkspaceProps,
15
+ ExtensionSlot,
16
+ usePatient,
17
+ OpenmrsDatePicker,
18
+ useConfig,
19
+ useVisit,
20
+ showSnackbar,
21
+ } from '@openmrs/esm-framework';
22
+ import { useTranslation } from 'react-i18next';
23
+ import type { TFunction } from 'i18next';
24
+
25
+ import { useForm, Controller, Control, FieldErrors, useWatch } from 'react-hook-form';
26
+ import { zodResolver } from '@hookform/resolvers/zod';
27
+
28
+ import {
29
+ type BillingFormData,
30
+ createBillingFormSchema,
31
+ createBillingInformationVisitAttribute,
32
+ updateVisitWithBillingInformation,
33
+ } from './billing-information.resource';
34
+ import type { ClinicalWorkflowConfig } from '../../config-schema';
35
+ import styles from './billing-information.scss';
36
+
37
+ type BillingInformationWorkspaceProps = DefaultWorkspaceProps & {
38
+ patientUuid: string;
39
+ };
40
+
41
+ const BillingInformationWorkspace: React.FC<BillingInformationWorkspaceProps> = ({
42
+ patientUuid,
43
+ closeWorkspace,
44
+ promptBeforeClosing,
45
+ closeWorkspaceWithSavedChanges,
46
+ }) => {
47
+ const { t } = useTranslation();
48
+ const { patient, isLoading } = usePatient(patientUuid);
49
+ const { activeVisit, mutate: mutateVisit } = useVisit(patientUuid);
50
+ const { billingVisitAttributeTypes } = useConfig<ClinicalWorkflowConfig>();
51
+ const billingFormSchema = useMemo(() => createBillingFormSchema(t), [t]);
52
+
53
+ const {
54
+ control,
55
+ handleSubmit,
56
+ watch,
57
+ setValue,
58
+ formState: { errors, isDirty },
59
+ } = useForm<BillingFormData>({
60
+ resolver: zodResolver(billingFormSchema),
61
+ mode: 'onTouched',
62
+ defaultValues: {
63
+ billingType: undefined,
64
+ creditType: '',
65
+ name: '',
66
+ code: '',
67
+ id: '',
68
+ expiryDate: '',
69
+ zone: '',
70
+ freeType: '',
71
+ },
72
+ });
73
+
74
+ const billingType = watch('billingType');
75
+
76
+ const onSubmit = async (data: BillingFormData) => {
77
+ try {
78
+ const visitAttributePayload = createBillingInformationVisitAttribute(data, billingVisitAttributeTypes);
79
+ const response = await updateVisitWithBillingInformation(visitAttributePayload, activeVisit?.uuid);
80
+ if (response.status === 200) {
81
+ showSnackbar({
82
+ title: t('updateVisitWithBillingInfo', 'Update Visit With Billing Information'),
83
+ subtitle: t('updateVisitWithBillingInfoSuccess', 'Update Visit With Billing Information Success'),
84
+ kind: 'success',
85
+ isLowContrast: true,
86
+ timeoutInMs: 5000,
87
+ });
88
+ mutateVisit();
89
+ closeWorkspaceWithSavedChanges();
90
+ }
91
+ } catch (error) {
92
+ showSnackbar({
93
+ title: t('error', 'Error'),
94
+ subtitle: t('errorUpdatingBillingInformation', 'Error updating billing information, {{error}}', {
95
+ error: error.message,
96
+ }),
97
+ kind: 'error',
98
+ isLowContrast: true,
99
+ timeoutInMs: 5000,
100
+ });
101
+ }
102
+ };
103
+
104
+ const handleBillingTypeChange = (type: 'credit' | 'free' | 'cash') => {
105
+ // Clear form fields to avoid carrying over values from previous billing type
106
+ setValue('billingType', type, { shouldDirty: true });
107
+ setValue('creditType', '', { shouldDirty: false });
108
+ setValue('name', '', { shouldDirty: false });
109
+ setValue('code', '', { shouldDirty: false });
110
+ setValue('id', '', { shouldDirty: false });
111
+ setValue('expiryDate', '', { shouldDirty: false });
112
+ setValue('zone', '', { shouldDirty: false });
113
+ setValue('freeType', '', { shouldDirty: false });
114
+ };
115
+
116
+ const getSelectedIndex = () => {
117
+ if (billingType === 'cash') {
118
+ return 0;
119
+ }
120
+ if (billingType === 'free') {
121
+ return 1;
122
+ }
123
+ if (billingType === 'credit') {
124
+ return 2;
125
+ }
126
+ return -1;
127
+ };
128
+
129
+ const handleContentSwitcherChange = ({ index }: { index: number }) => {
130
+ const types: ('cash' | 'free' | 'credit')[] = ['cash', 'free', 'credit'];
131
+ if (index >= 0 && index < types.length) {
132
+ handleBillingTypeChange(types[index]);
133
+ }
134
+ };
135
+
136
+ useEffect(() => {
137
+ promptBeforeClosing(() => isDirty);
138
+ }, [promptBeforeClosing, isDirty]);
139
+
140
+ if (isLoading) {
141
+ return <InlineLoading status="active" iconDescription="Loading" description="Loading billing information..." />;
142
+ }
143
+
144
+ return (
145
+ <>
146
+ {patient && (
147
+ <ExtensionSlot
148
+ name="patient-header-slot"
149
+ state={{
150
+ patient,
151
+ patientUuid: patientUuid,
152
+ hideActionsOverflow: true,
153
+ }}
154
+ />
155
+ )}
156
+ <Form onSubmit={handleSubmit(onSubmit)}>
157
+ <div className={styles.billingInformationContainer}>
158
+ <p className={styles.sectionTitle}>{t('paymentMethods', 'Payment Methods')}</p>
159
+ <ContentSwitcher onChange={handleContentSwitcherChange} selectedIndex={getSelectedIndex()} size="md">
160
+ <Switch name="cash" text={t('cash', 'Cash')} />
161
+ <Switch name="free" text={t('free', 'Free')} />
162
+ <Switch name="credit" text={t('credit', 'Credit')} />
163
+ </ContentSwitcher>
164
+
165
+ {billingType === 'credit' && <CreditDetails control={control} errors={errors} t={t} />}
166
+ {billingType === 'free' && <FreeDetails control={control} errors={errors} t={t} />}
167
+ </div>
168
+ <ButtonSet className={styles.buttonSet}>
169
+ <Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
170
+ {t('discard', 'Discard')}
171
+ </Button>
172
+ <Button disabled={!isDirty} className={styles.button} kind="primary" type="submit">
173
+ {t('saveAndClose', 'Save & Close')}
174
+ </Button>
175
+ </ButtonSet>
176
+ </Form>
177
+ </>
178
+ );
179
+ };
180
+
181
+ export default BillingInformationWorkspace;
182
+
183
+ type CreditDetailsProps = {
184
+ control: Control<BillingFormData>;
185
+ errors: FieldErrors<BillingFormData>;
186
+ t: TFunction;
187
+ };
188
+
189
+ const CreditDetails: React.FC<CreditDetailsProps> = ({ control, errors, t }) => {
190
+ const creditType = useWatch({
191
+ control,
192
+ name: 'creditType',
193
+ });
194
+
195
+ const creditTypeOptions = [
196
+ {
197
+ id: 'chbi',
198
+ text: t('chbi', 'CHBI'),
199
+ },
200
+ {
201
+ id: 'shi',
202
+ text: t('shi', 'SHI'),
203
+ },
204
+ {
205
+ id: 'creditCompanies',
206
+ text: t('creditCompanies', 'Credit Companies'),
207
+ },
208
+ {
209
+ id: 'insurance',
210
+ text: t('insurance', 'Insurance'),
211
+ },
212
+ ];
213
+
214
+ const showCreditCompanyFields = creditType === 'creditCompanies' || creditType === 'insurance';
215
+
216
+ return (
217
+ <FormGroup className={styles.creditDetailsContainer} legendText={t('creditDetails', 'Credit Details')}>
218
+ <Controller
219
+ name="creditType"
220
+ control={control}
221
+ render={({ field: { onChange, value } }) => (
222
+ <Dropdown
223
+ id="credit-type"
224
+ invalid={!!errors.creditType}
225
+ invalidText={errors.creditType?.message || 'invalid selection'}
226
+ itemToString={(item) => item?.text ?? ''}
227
+ items={creditTypeOptions}
228
+ label="Credit"
229
+ titleText="Credit"
230
+ type="default"
231
+ selectedItem={creditTypeOptions.find((item) => item.id === value) || null}
232
+ onChange={({ selectedItem }) => onChange(selectedItem?.id || '')}
233
+ />
234
+ )}
235
+ />
236
+
237
+ {showCreditCompanyFields && (
238
+ <FormGroup
239
+ legendText={t('creditTypeDetails', 'Credit Type Details')}
240
+ className={styles.creditTypeDetailsContainer}>
241
+ <Controller
242
+ name="id"
243
+ control={control}
244
+ render={({ field: { onChange, value } }) => (
245
+ <TextInput
246
+ id="credit-id"
247
+ labelText={t('id', 'ID')}
248
+ value={value || ''}
249
+ onChange={(e) => onChange(e.target.value)}
250
+ placeholder={t('enterId', 'Enter ID')}
251
+ invalid={!!errors.id}
252
+ invalidText={errors.id?.message}
253
+ />
254
+ )}
255
+ />
256
+
257
+ <Controller
258
+ name="name"
259
+ control={control}
260
+ render={({ field: { onChange, value } }) => (
261
+ <TextInput
262
+ id="credit-name"
263
+ labelText={t('name', 'Name')}
264
+ value={value || ''}
265
+ onChange={(e) => onChange(e.target.value)}
266
+ placeholder={t('enterName', 'Enter name')}
267
+ invalid={!!errors.name}
268
+ invalidText={errors.name?.message}
269
+ />
270
+ )}
271
+ />
272
+
273
+ <Controller
274
+ name="code"
275
+ control={control}
276
+ render={({ field: { onChange, value } }) => (
277
+ <TextInput
278
+ id="credit-code"
279
+ labelText={t('code', 'Code')}
280
+ value={value || ''}
281
+ onChange={(e) => onChange(e.target.value)}
282
+ placeholder={t('enterCode', 'Enter code')}
283
+ invalid={!!errors.code}
284
+ invalidText={errors.code?.message}
285
+ />
286
+ )}
287
+ />
288
+
289
+ <Controller
290
+ name="expiryDate"
291
+ control={control}
292
+ render={({ field: { onChange, value } }) => (
293
+ <OpenmrsDatePicker
294
+ id="credit-expiry-date"
295
+ labelText={t('expiryDate', 'Expiry Date')}
296
+ minDate={new Date()}
297
+ value={value || ''}
298
+ onChange={(date) => {
299
+ const dateValue = typeof date === 'string' ? date : date.toISOString().split('T')[0];
300
+ onChange(dateValue);
301
+ }}
302
+ />
303
+ )}
304
+ />
305
+ <Controller
306
+ name="zone"
307
+ control={control}
308
+ render={({ field: { onChange, value } }) => (
309
+ <TextInput
310
+ id="credit-zone"
311
+ labelText={t('zone', 'Zone')}
312
+ value={value || ''}
313
+ onChange={(e) => onChange(e.target.value)}
314
+ placeholder={t('enterZone', 'Enter zone')}
315
+ invalid={!!errors.zone}
316
+ invalidText={errors.zone?.message}
317
+ />
318
+ )}
319
+ />
320
+ </FormGroup>
321
+ )}
322
+ </FormGroup>
323
+ );
324
+ };
325
+
326
+ type FreeDetailsProps = {
327
+ control: Control<BillingFormData>;
328
+ errors: FieldErrors<BillingFormData>;
329
+ t: TFunction;
330
+ };
331
+
332
+ const FreeDetails: React.FC<FreeDetailsProps> = ({ control, errors, t }) => {
333
+ const items = [
334
+ {
335
+ id: 'staff',
336
+ text: t('staff', 'Staff'),
337
+ },
338
+ {
339
+ id: '24Hours',
340
+ text: t('24Hours', '24 Hours'),
341
+ },
342
+ {
343
+ id: 'exempted',
344
+ text: t('exempted', 'Exempted'),
345
+ },
346
+ ];
347
+
348
+ return (
349
+ <FormGroup className={styles.freeDetailsContainer} legendText={t('freeDetails', 'Free Details')}>
350
+ <Controller
351
+ name="freeType"
352
+ control={control}
353
+ render={({ field: { onChange, value } }) => (
354
+ <Dropdown
355
+ id="free-type"
356
+ hideLabel={true}
357
+ invalid={!!errors.freeType}
358
+ invalidText={errors.freeType?.message || 'invalid selection'}
359
+ itemToString={(item) => item?.text ?? ''}
360
+ items={items}
361
+ label={t('selectOption', 'Select a free type')}
362
+ titleText={t('selectOption', 'Select a free type')}
363
+ type="default"
364
+ selectedItem={items.find((item) => item.id === value) || null}
365
+ onChange={({ selectedItem }) => onChange(selectedItem?.id || '')}
366
+ />
367
+ )}
368
+ />
369
+ </FormGroup>
370
+ );
371
+ };
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { BrowserRouter, Routes, Route } from 'react-router-dom';
3
+
4
+ import { spaBasePath } from '../constants';
5
+ import MRU from './mru.component';
6
+
7
+ const MRUDashboard: React.FC = () => {
8
+ return (
9
+ <BrowserRouter basename={`${spaBasePath}/mru`}>
10
+ <Routes>
11
+ <Route path="/" element={<MRU />} />
12
+ <Route path="/:patientUuid" element={<MRU />} />
13
+ </Routes>
14
+ </BrowserRouter>
15
+ );
16
+ };
17
+
18
+ export default MRUDashboard;
@@ -0,0 +1,106 @@
1
+ import React from 'react';
2
+ import { Button, InlineLoading, Layer } from '@carbon/react';
3
+ import {
4
+ ExtensionSlot,
5
+ PageHeader,
6
+ RegistrationPictogram,
7
+ fetchCurrentPatient,
8
+ launchWorkspace,
9
+ navigate,
10
+ useConfig,
11
+ useVisit,
12
+ } from '@openmrs/esm-framework';
13
+ import { useTranslation } from 'react-i18next';
14
+ import { Close, Edit, Money } from '@carbon/react/icons';
15
+ import useSWR from 'swr';
16
+ import { useParams } from 'react-router-dom';
17
+
18
+ import styles from './mru.scss';
19
+ import { type ClinicalWorkflowConfig } from '../config-schema';
20
+ const MRU: React.FC = () => {
21
+ const { t } = useTranslation();
22
+
23
+ return (
24
+ <div>
25
+ <PageHeader
26
+ title={t('medicalRecordUpdate', 'Medical Record Unit(MRU)')}
27
+ illustration={<RegistrationPictogram />}
28
+ className={styles.pageHeader}
29
+ />
30
+ <PatientSearch />
31
+ </div>
32
+ );
33
+ };
34
+
35
+ export default MRU;
36
+
37
+ const PatientSearch: React.FC = () => {
38
+ const params = useParams<{ patientUuid: string }>();
39
+ const { t } = useTranslation();
40
+ const { billingVisitAttributeTypes } = useConfig<ClinicalWorkflowConfig>();
41
+ const [patientUuid, setPatientUuid] = React.useState<string>(params.patientUuid || undefined);
42
+ const { data: patient, isLoading } = useSWR(patientUuid ? ['patient', patientUuid] : null, () =>
43
+ fetchCurrentPatient(patientUuid!),
44
+ );
45
+ const { activeVisit } = useVisit(patientUuid);
46
+
47
+ // TODO: Add ability for user to edit billing information
48
+ const hasAtLeastOneBillingInformation = activeVisit?.attributes?.some(
49
+ (attribute) => attribute.attributeType.uuid === billingVisitAttributeTypes.paymentMethod,
50
+ );
51
+
52
+ const handleLaunchBillingInformationWorkspace = () => {
53
+ launchWorkspace('billing-information-workspace', {
54
+ patientUuid: patientUuid,
55
+ });
56
+ };
57
+
58
+ const handlePatientInformationEdit = () => {
59
+ navigate({ to: `${window.spaBase}/patient/${patientUuid}/edit` });
60
+ };
61
+
62
+ return (
63
+ <div className={styles.patientSearchContainer}>
64
+ <Layer>
65
+ <ExtensionSlot
66
+ name="patient-search-bar-slot"
67
+ state={{
68
+ selectPatientAction: (patientUuid: string) => setPatientUuid(patientUuid),
69
+ buttonProps: {
70
+ kind: 'secondary',
71
+ },
72
+ }}
73
+ />
74
+ </Layer>
75
+ {isLoading && <InlineLoading description={t('loading', 'Loading...')} />}
76
+ {patient && (
77
+ <div className={styles.patientHeaderContainer}>
78
+ <div className={styles.patientHeaderActions}>
79
+ <Button kind="ghost" size="md" renderIcon={Edit} onClick={handlePatientInformationEdit}>
80
+ {t('editPatientInformation', 'Edit Patient Information')}
81
+ </Button>
82
+ <Button
83
+ disabled={hasAtLeastOneBillingInformation}
84
+ kind="ghost"
85
+ size="md"
86
+ renderIcon={Money}
87
+ onClick={handleLaunchBillingInformationWorkspace}>
88
+ {t('addBillingInformation', 'Add Billing Information')}
89
+ </Button>
90
+ <Button kind="ghost" size="md" renderIcon={Close} onClick={() => setPatientUuid(undefined)}>
91
+ {t('close', 'Close')}
92
+ </Button>
93
+ </div>
94
+ <ExtensionSlot
95
+ name="patient-header-slot"
96
+ state={{
97
+ patient,
98
+ patientUuid: patientUuid,
99
+ hideActionsOverflow: true,
100
+ }}
101
+ />
102
+ </div>
103
+ )}
104
+ </div>
105
+ );
106
+ };
@@ -0,0 +1,28 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+
5
+ .pageHeader {
6
+ border-bottom: 1px solid colors.$gray-20;
7
+ }
8
+
9
+ .patientSearchContainer {
10
+ padding: layout.$spacing-05;
11
+ background-color: colors.$gray-10;
12
+
13
+ & form {
14
+ border: none;
15
+ }
16
+ }
17
+
18
+ .patientHeaderContainer {
19
+ margin-top: layout.$spacing-05;
20
+ border: 1px solid colors.$gray-20;
21
+ background-color: colors.$white;
22
+ }
23
+
24
+ .patientHeaderActions {
25
+ display: flex;
26
+ justify-content: flex-end;
27
+ align-items: center;
28
+ }
@@ -0,0 +1,129 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import dayjs from 'dayjs';
3
+ import type { PatientRegistrationFormData } from './patient.registration.workspace';
4
+
5
+ const calculateBirthdate = (
6
+ formData: PatientRegistrationFormData,
7
+ ): { formattedBirthDate: string; birthdateEstimated: boolean } => {
8
+ let formattedBirthDate: string;
9
+ let birthdateEstimated = false;
10
+
11
+ if (formData.dateOfBirth) {
12
+ formattedBirthDate = dayjs(formData.dateOfBirth).format('YYYY-M-D');
13
+ birthdateEstimated = formData.isEstimatedDOB || false;
14
+ } else if (
15
+ formData.isEstimatedDOB &&
16
+ (formData.ageYears !== undefined || formData.ageMonths !== undefined || formData.ageDays !== undefined)
17
+ ) {
18
+ const currentDate = dayjs();
19
+ let calculatedDate = currentDate;
20
+
21
+ if (formData.ageYears !== undefined && formData.ageYears !== null) {
22
+ calculatedDate = calculatedDate.subtract(formData.ageYears, 'year');
23
+ }
24
+ if (formData.ageMonths !== undefined && formData.ageMonths !== null) {
25
+ calculatedDate = calculatedDate.subtract(formData.ageMonths, 'month');
26
+ }
27
+ if (formData.ageDays !== undefined && formData.ageDays !== null) {
28
+ calculatedDate = calculatedDate.subtract(formData.ageDays, 'day');
29
+ }
30
+
31
+ formattedBirthDate = calculatedDate.format('YYYY-M-D');
32
+ birthdateEstimated = true;
33
+ } else if (formData.ageYears !== undefined || formData.ageMonths !== undefined || formData.ageDays !== undefined) {
34
+ const currentDate = dayjs();
35
+ let calculatedDate = currentDate;
36
+
37
+ if (formData.ageYears !== undefined && formData.ageYears !== null) {
38
+ calculatedDate = calculatedDate.subtract(formData.ageYears, 'year');
39
+ }
40
+ if (formData.ageMonths !== undefined && formData.ageMonths !== null) {
41
+ calculatedDate = calculatedDate.subtract(formData.ageMonths, 'month');
42
+ }
43
+ if (formData.ageDays !== undefined && formData.ageDays !== null) {
44
+ calculatedDate = calculatedDate.subtract(formData.ageDays, 'day');
45
+ }
46
+
47
+ formattedBirthDate = calculatedDate.format('YYYY-M-D');
48
+ birthdateEstimated = false;
49
+ } else {
50
+ formattedBirthDate = dayjs().format('YYYY-M-D');
51
+ birthdateEstimated = false;
52
+ }
53
+
54
+ return { formattedBirthDate, birthdateEstimated };
55
+ };
56
+
57
+ export const buildPatientRegistrationPayload = (
58
+ formData: PatientRegistrationFormData,
59
+ uuid: string,
60
+ identifier: string,
61
+ defaultIdentifierTypeUuid: string,
62
+ locationUuid: string,
63
+ isMedicoLegalCase?: boolean,
64
+ medicoLegalCasesAttributeTypeUuid?: string,
65
+ triageFormName?: string,
66
+ ) => {
67
+ const { formattedBirthDate, birthdateEstimated } = calculateBirthdate(formData);
68
+
69
+ const genderCode = formData.gender === 'Male' ? 'M' : 'F';
70
+
71
+ const attributes: Array<{ attributeType: string; value: string }> = [];
72
+ if (isMedicoLegalCase === true && medicoLegalCasesAttributeTypeUuid && triageFormName === 'Emergency Triage Form') {
73
+ attributes.push({
74
+ attributeType: medicoLegalCasesAttributeTypeUuid,
75
+ value: 'true',
76
+ });
77
+ }
78
+
79
+ return {
80
+ uuid: uuid,
81
+ person: {
82
+ uuid: uuid,
83
+ names: [
84
+ {
85
+ preferred: true,
86
+ givenName: formData.firstName,
87
+ middleName: formData.middleName,
88
+ familyName: formData.lastName,
89
+ },
90
+ ],
91
+ gender: genderCode,
92
+ birthdate: formattedBirthDate,
93
+ birthdateEstimated: birthdateEstimated,
94
+ attributes: attributes,
95
+ addresses: [{}],
96
+ dead: false,
97
+ },
98
+ identifiers: [
99
+ {
100
+ identifier: identifier,
101
+ identifierType: defaultIdentifierTypeUuid,
102
+ location: locationUuid,
103
+ preferred: true,
104
+ },
105
+ ],
106
+ };
107
+ };
108
+
109
+ export const registerNewPatient = (payload) => {
110
+ const url = `${restBaseUrl}/patient`;
111
+
112
+ return openmrsFetch<fhir.Patient>(url, {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Content-Type': 'application/json',
116
+ },
117
+ body: JSON.stringify(payload),
118
+ });
119
+ };
120
+ export const generateIdentifier = (identifierSourceUuid: string) => {
121
+ const url = `${restBaseUrl}/idgen/identifiersource/${identifierSourceUuid}/identifier`;
122
+ return openmrsFetch<fhir.Identifier>(url, {
123
+ method: 'POST',
124
+ headers: {
125
+ 'Content-Type': 'application/json',
126
+ },
127
+ body: {},
128
+ });
129
+ };
@@ -0,0 +1,47 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+
5
+ .patientRegistrationFormContainer {
6
+ padding: 16px;
7
+ }
8
+
9
+ .submitButton {
10
+ margin-top: 16px;
11
+ }
12
+
13
+ .form {
14
+ display: flex;
15
+ flex-direction: column;
16
+ justify-content: space-between;
17
+ height: 100%;
18
+ }
19
+
20
+ .formContainer {
21
+ margin: layout.$spacing-05;
22
+ row-gap: layout.$spacing-05;
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: layout.$spacing-05;
26
+ }
27
+
28
+ .button {
29
+ height: layout.$spacing-10;
30
+ display: flex;
31
+ align-content: flex-start;
32
+ align-items: baseline;
33
+ min-width: 50%;
34
+ }
35
+
36
+ .tablet {
37
+ padding: layout.$spacing-06 layout.$spacing-05;
38
+ background-color: colors.$white;
39
+ }
40
+
41
+ .desktop {
42
+ padding: 0;
43
+ }
44
+
45
+ .ageFormGroup {
46
+ width: 100%;
47
+ }