@palladium-ethiopia/esm-clinical-workflow-app 5.4.2-pre.34 → 5.4.2-pre.37
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.
- package/.turbo/turbo-build.log +4 -4
- package/dist/164.js +1 -1
- package/dist/164.js.map +1 -1
- package/dist/{160.js → 693.js} +1 -1
- package/dist/693.js.map +1 -0
- package/dist/ethiopia-esm-clinical-workflow-app.js +1 -1
- package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +28 -28
- package/dist/main.js +4 -4
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +2 -0
- package/src/index.ts +1 -1
- package/src/mru/billing-information/billing-information.resource.ts +60 -59
- package/src/mru/billing-information/billing-information.scss +21 -3
- package/src/mru/billing-information/billing-information.workspace.tsx +106 -252
- package/src/mru/billing-information/components/BillingTypeAttributes.tsx +106 -0
- package/src/mru/billing-information/components/CreditSubTypeFields.tsx +145 -0
- package/src/mru/billing-information/components/CreditSubTypeSelection.tsx +66 -0
- package/src/mru/billing-information/components/FreeSubTypeFields.tsx +7 -0
- package/src/mru/billing-information/components/FreeSubTypeSelection.tsx +61 -0
- package/src/mru/billing-information/components/index.ts +5 -0
- package/src/mru/billing-information/hooks/index.ts +4 -0
- package/src/mru/billing-information/hooks/useBillingForm.ts +22 -0
- package/src/mru/billing-information/hooks/useBillingType.ts +18 -0
- package/src/mru/billing-information/hooks/useCreditCompanies.ts +17 -0
- package/src/mru/billing-information/hooks/usePaymentModes.ts +17 -0
- package/dist/160.js.map +0 -1
|
@@ -1,37 +1,29 @@
|
|
|
1
|
-
import React, { useEffect
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
4
|
-
ButtonSet,
|
|
5
|
-
InlineLoading,
|
|
6
|
-
ContentSwitcher,
|
|
7
|
-
Dropdown,
|
|
8
|
-
Switch,
|
|
9
|
-
TextInput,
|
|
10
|
-
Form,
|
|
11
|
-
FormGroup,
|
|
12
|
-
} from '@carbon/react';
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { Button, ButtonSet, InlineLoading, ContentSwitcher, Switch, Form } from '@carbon/react';
|
|
13
3
|
import {
|
|
14
4
|
DefaultWorkspaceProps,
|
|
15
5
|
ExtensionSlot,
|
|
16
6
|
usePatient,
|
|
17
|
-
OpenmrsDatePicker,
|
|
18
7
|
useConfig,
|
|
19
8
|
useVisit,
|
|
20
9
|
showSnackbar,
|
|
21
10
|
} from '@openmrs/esm-framework';
|
|
22
11
|
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
12
|
|
|
28
13
|
import {
|
|
29
14
|
type BillingFormData,
|
|
30
|
-
createBillingFormSchema,
|
|
31
15
|
createBillingInformationVisitAttribute,
|
|
32
16
|
updateVisitWithBillingInformation,
|
|
33
17
|
} from './billing-information.resource';
|
|
34
18
|
import type { ClinicalWorkflowConfig } from '../../config-schema';
|
|
19
|
+
import { usePaymentModes, useCreditCompanies, useBillingForm, useBillingType } from './hooks';
|
|
20
|
+
import {
|
|
21
|
+
BillingTypeAttributes,
|
|
22
|
+
CreditSubTypeSelection,
|
|
23
|
+
FreeSubTypeSelection,
|
|
24
|
+
CreditSubTypeFields,
|
|
25
|
+
FreeSubTypeFields,
|
|
26
|
+
} from './components';
|
|
35
27
|
import styles from './billing-information.scss';
|
|
36
28
|
|
|
37
29
|
type BillingInformationWorkspaceProps = DefaultWorkspaceProps & {
|
|
@@ -48,30 +40,30 @@ const BillingInformationWorkspace: React.FC<BillingInformationWorkspaceProps> =
|
|
|
48
40
|
const { patient, isLoading } = usePatient(patientUuid);
|
|
49
41
|
const { activeVisit, mutate: mutateVisit } = useVisit(patientUuid);
|
|
50
42
|
const { billingVisitAttributeTypes } = useConfig<ClinicalWorkflowConfig>();
|
|
51
|
-
const billingFormSchema = useMemo(() => createBillingFormSchema(t), [t]);
|
|
52
43
|
|
|
44
|
+
// Custom hooks for data fetching
|
|
45
|
+
const { billingTypes } = usePaymentModes();
|
|
46
|
+
|
|
47
|
+
// Form management hook
|
|
53
48
|
const {
|
|
54
49
|
control,
|
|
55
50
|
handleSubmit,
|
|
56
51
|
watch,
|
|
57
52
|
setValue,
|
|
58
53
|
formState: { errors, isDirty },
|
|
59
|
-
} =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
zone: '',
|
|
70
|
-
freeType: '',
|
|
71
|
-
},
|
|
72
|
-
});
|
|
54
|
+
} = useBillingForm(t, billingTypes);
|
|
55
|
+
|
|
56
|
+
// Watch form values
|
|
57
|
+
const billingTypeUuid = watch('billingTypeUuid');
|
|
58
|
+
const creditSubType = watch('creditSubType');
|
|
59
|
+
const freeSubType = watch('freeSubType');
|
|
60
|
+
const attributes = watch('attributes') || {};
|
|
61
|
+
|
|
62
|
+
// Billing type logic hook
|
|
63
|
+
const { selectedBillingType, isCreditType, isFreeType } = useBillingType(billingTypes, billingTypeUuid);
|
|
73
64
|
|
|
74
|
-
|
|
65
|
+
// Fetch credit companies conditionally
|
|
66
|
+
const { creditCompanies } = useCreditCompanies(isCreditType && creditSubType === 'creditCompany');
|
|
75
67
|
|
|
76
68
|
const onSubmit = async (data: BillingFormData) => {
|
|
77
69
|
try {
|
|
@@ -101,35 +93,24 @@ const BillingInformationWorkspace: React.FC<BillingInformationWorkspaceProps> =
|
|
|
101
93
|
}
|
|
102
94
|
};
|
|
103
95
|
|
|
104
|
-
const handleBillingTypeChange = (
|
|
105
|
-
// Clear
|
|
106
|
-
setValue('
|
|
107
|
-
setValue('
|
|
108
|
-
setValue('
|
|
109
|
-
setValue('
|
|
110
|
-
setValue('id', '', { shouldDirty: false });
|
|
111
|
-
setValue('expiryDate', '', { shouldDirty: false });
|
|
112
|
-
setValue('zone', '', { shouldDirty: false });
|
|
113
|
-
setValue('freeType', '', { shouldDirty: false });
|
|
96
|
+
const handleBillingTypeChange = (uuid: string) => {
|
|
97
|
+
// Clear attributes and sub-types when changing billing type
|
|
98
|
+
setValue('billingTypeUuid', uuid, { shouldDirty: true });
|
|
99
|
+
setValue('creditSubType', undefined, { shouldDirty: false });
|
|
100
|
+
setValue('freeSubType', undefined, { shouldDirty: false });
|
|
101
|
+
setValue('attributes', {}, { shouldDirty: false });
|
|
114
102
|
};
|
|
115
103
|
|
|
116
104
|
const getSelectedIndex = () => {
|
|
117
|
-
if (
|
|
118
|
-
return
|
|
105
|
+
if (!billingTypeUuid) {
|
|
106
|
+
return -1;
|
|
119
107
|
}
|
|
120
|
-
|
|
121
|
-
return 1;
|
|
122
|
-
}
|
|
123
|
-
if (billingType === 'credit') {
|
|
124
|
-
return 2;
|
|
125
|
-
}
|
|
126
|
-
return -1;
|
|
108
|
+
return billingTypes.findIndex((bt) => bt.uuid === billingTypeUuid);
|
|
127
109
|
};
|
|
128
110
|
|
|
129
111
|
const handleContentSwitcherChange = ({ index }: { index: number }) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
handleBillingTypeChange(types[index]);
|
|
112
|
+
if (index >= 0 && index < billingTypes.length) {
|
|
113
|
+
handleBillingTypeChange(billingTypes[index].uuid);
|
|
133
114
|
}
|
|
134
115
|
};
|
|
135
116
|
|
|
@@ -141,6 +122,8 @@ const BillingInformationWorkspace: React.FC<BillingInformationWorkspaceProps> =
|
|
|
141
122
|
return <InlineLoading status="active" iconDescription="Loading" description="Loading billing information..." />;
|
|
142
123
|
}
|
|
143
124
|
|
|
125
|
+
//
|
|
126
|
+
|
|
144
127
|
return (
|
|
145
128
|
<>
|
|
146
129
|
{patient && (
|
|
@@ -156,14 +139,75 @@ const BillingInformationWorkspace: React.FC<BillingInformationWorkspaceProps> =
|
|
|
156
139
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
|
157
140
|
<div className={styles.billingInformationContainer}>
|
|
158
141
|
<p className={styles.sectionTitle}>{t('paymentMethods', 'Payment Methods')}</p>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
142
|
+
|
|
143
|
+
<ContentSwitcher
|
|
144
|
+
className={styles.paymentTypeSwitcher}
|
|
145
|
+
onChange={handleContentSwitcherChange}
|
|
146
|
+
selectedIndex={getSelectedIndex()}
|
|
147
|
+
size="md">
|
|
148
|
+
{billingTypes?.map((billingType) => (
|
|
149
|
+
<Switch
|
|
150
|
+
className={styles.paymentTypeSwitch}
|
|
151
|
+
key={billingType.uuid}
|
|
152
|
+
name={billingType.uuid}
|
|
153
|
+
text={billingType.name}
|
|
154
|
+
/>
|
|
155
|
+
))}
|
|
163
156
|
</ContentSwitcher>
|
|
164
157
|
|
|
165
|
-
{
|
|
166
|
-
{
|
|
158
|
+
{/* Credit sub-type selection */}
|
|
159
|
+
{isCreditType && (
|
|
160
|
+
<CreditSubTypeSelection
|
|
161
|
+
control={control}
|
|
162
|
+
errors={errors}
|
|
163
|
+
t={t}
|
|
164
|
+
creditSubType={creditSubType}
|
|
165
|
+
setValue={setValue}
|
|
166
|
+
/>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
{/* Free sub-type selection */}
|
|
170
|
+
{isFreeType && (
|
|
171
|
+
<FreeSubTypeSelection
|
|
172
|
+
control={control}
|
|
173
|
+
errors={errors}
|
|
174
|
+
t={t}
|
|
175
|
+
freeSubType={freeSubType}
|
|
176
|
+
setValue={setValue}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
{/* Conditional fields based on Credit sub-type */}
|
|
181
|
+
{isCreditType && creditSubType && (
|
|
182
|
+
<CreditSubTypeFields
|
|
183
|
+
control={control}
|
|
184
|
+
errors={errors}
|
|
185
|
+
t={t}
|
|
186
|
+
creditSubType={creditSubType}
|
|
187
|
+
creditCompanies={creditCompanies}
|
|
188
|
+
attributes={attributes}
|
|
189
|
+
setValue={setValue}
|
|
190
|
+
/>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
{/* Conditional fields based on Free sub-type */}
|
|
194
|
+
{isFreeType && freeSubType && <FreeSubTypeFields />}
|
|
195
|
+
|
|
196
|
+
{/* Default attribute types for other billing types */}
|
|
197
|
+
{selectedBillingType &&
|
|
198
|
+
!isCreditType &&
|
|
199
|
+
!isFreeType &&
|
|
200
|
+
selectedBillingType.attributeTypes &&
|
|
201
|
+
selectedBillingType.attributeTypes.length > 0 && (
|
|
202
|
+
<BillingTypeAttributes
|
|
203
|
+
control={control}
|
|
204
|
+
errors={errors}
|
|
205
|
+
t={t}
|
|
206
|
+
attributeTypes={selectedBillingType.attributeTypes}
|
|
207
|
+
attributes={attributes}
|
|
208
|
+
setValue={setValue}
|
|
209
|
+
/>
|
|
210
|
+
)}
|
|
167
211
|
</div>
|
|
168
212
|
<ButtonSet className={styles.buttonSet}>
|
|
169
213
|
<Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
|
|
@@ -179,193 +223,3 @@ const BillingInformationWorkspace: React.FC<BillingInformationWorkspaceProps> =
|
|
|
179
223
|
};
|
|
180
224
|
|
|
181
225
|
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,106 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Controller, Control, FieldErrors } from 'react-hook-form';
|
|
3
|
+
import { TextInput, FormGroup } from '@carbon/react';
|
|
4
|
+
import { OpenmrsDatePicker } from '@openmrs/esm-framework';
|
|
5
|
+
import type { TFunction } from 'i18next';
|
|
6
|
+
import type { BillingFormData } from '../billing-information.resource';
|
|
7
|
+
import styles from '../billing-information.scss';
|
|
8
|
+
|
|
9
|
+
type BillingTypeAttributesProps = {
|
|
10
|
+
control: Control<BillingFormData>;
|
|
11
|
+
errors: FieldErrors<BillingFormData>;
|
|
12
|
+
t: TFunction;
|
|
13
|
+
attributeTypes: Array<{
|
|
14
|
+
uuid: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
format?: string;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
attributes: Record<string, any>;
|
|
21
|
+
setValue: (name: string, value: any, options?: { shouldDirty?: boolean }) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const BillingTypeAttributes: React.FC<BillingTypeAttributesProps> = ({
|
|
25
|
+
control,
|
|
26
|
+
errors,
|
|
27
|
+
t,
|
|
28
|
+
attributeTypes,
|
|
29
|
+
attributes,
|
|
30
|
+
setValue,
|
|
31
|
+
}) => {
|
|
32
|
+
const renderAttributeField = (attrType: {
|
|
33
|
+
uuid: string;
|
|
34
|
+
name: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
format?: string;
|
|
37
|
+
required?: boolean;
|
|
38
|
+
}) => {
|
|
39
|
+
const fieldName = `attributes.${attrType.uuid}` as const;
|
|
40
|
+
const fieldError = errors.attributes?.[attrType.uuid];
|
|
41
|
+
const errorMessage =
|
|
42
|
+
fieldError && typeof fieldError === 'object' && 'message' in fieldError
|
|
43
|
+
? String(fieldError.message)
|
|
44
|
+
: fieldError
|
|
45
|
+
? String(fieldError)
|
|
46
|
+
: undefined;
|
|
47
|
+
|
|
48
|
+
// Determine field type based on format
|
|
49
|
+
const isDateField =
|
|
50
|
+
attrType.format?.toLowerCase().includes('date') || attrType.format === 'org.openmrs.util.AttributableDate';
|
|
51
|
+
|
|
52
|
+
if (isDateField) {
|
|
53
|
+
return (
|
|
54
|
+
<Controller
|
|
55
|
+
key={attrType.uuid}
|
|
56
|
+
name={fieldName}
|
|
57
|
+
control={control}
|
|
58
|
+
render={({ field: { onChange, value } }) => (
|
|
59
|
+
<OpenmrsDatePicker
|
|
60
|
+
id={`attribute-${attrType.uuid}`}
|
|
61
|
+
labelText={attrType.name}
|
|
62
|
+
value={value || ''}
|
|
63
|
+
onChange={(date) => {
|
|
64
|
+
const dateValue = typeof date === 'string' ? date : date.toISOString().split('T')[0];
|
|
65
|
+
onChange(dateValue);
|
|
66
|
+
setValue(fieldName, dateValue, { shouldDirty: true });
|
|
67
|
+
}}
|
|
68
|
+
invalid={!!fieldError}
|
|
69
|
+
invalidText={errorMessage}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Default to text input
|
|
77
|
+
return (
|
|
78
|
+
<Controller
|
|
79
|
+
key={attrType.uuid}
|
|
80
|
+
name={fieldName}
|
|
81
|
+
control={control}
|
|
82
|
+
render={({ field: { onChange, value } }) => (
|
|
83
|
+
<TextInput
|
|
84
|
+
id={`attribute-${attrType.uuid}`}
|
|
85
|
+
labelText={attrType.name}
|
|
86
|
+
value={value || ''}
|
|
87
|
+
onChange={(e) => {
|
|
88
|
+
onChange(e.target.value);
|
|
89
|
+
setValue(fieldName, e.target.value, { shouldDirty: true });
|
|
90
|
+
}}
|
|
91
|
+
placeholder={attrType.description || t('enterValue', 'Enter {{name}}', { name: attrType.name })}
|
|
92
|
+
invalid={!!fieldError}
|
|
93
|
+
invalidText={errorMessage}
|
|
94
|
+
required={attrType.required}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<FormGroup className={styles.billingTypeAttributesContainer} legendText={t('billingDetails', 'Billing Details')}>
|
|
103
|
+
{attributeTypes.map((attrType) => renderAttributeField(attrType))}
|
|
104
|
+
</FormGroup>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Control, FieldErrors } from 'react-hook-form';
|
|
3
|
+
import { TextInput, Dropdown, FormGroup } from '@carbon/react';
|
|
4
|
+
import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import type { TFunction } from 'i18next';
|
|
6
|
+
import type { BillingFormData } from '../billing-information.resource';
|
|
7
|
+
import type { ClinicalWorkflowConfig } from '../../../config-schema';
|
|
8
|
+
import styles from '../billing-information.scss';
|
|
9
|
+
|
|
10
|
+
type CreditSubTypeFieldsProps = {
|
|
11
|
+
control: Control<BillingFormData>;
|
|
12
|
+
errors: FieldErrors<BillingFormData>;
|
|
13
|
+
t: TFunction;
|
|
14
|
+
creditSubType: string;
|
|
15
|
+
creditCompanies: Array<{ uuid: string; name: string }>;
|
|
16
|
+
attributes: Record<string, any>;
|
|
17
|
+
setValue: (name: string, value: any, options?: { shouldDirty?: boolean }) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const CreditSubTypeFields: React.FC<CreditSubTypeFieldsProps> = ({
|
|
21
|
+
control,
|
|
22
|
+
errors,
|
|
23
|
+
t,
|
|
24
|
+
creditSubType,
|
|
25
|
+
creditCompanies,
|
|
26
|
+
attributes,
|
|
27
|
+
setValue,
|
|
28
|
+
}) => {
|
|
29
|
+
const { billingVisitAttributeTypes } = useConfig<ClinicalWorkflowConfig>();
|
|
30
|
+
|
|
31
|
+
// Use creditTypeDetails UUID to store all credit details as a JSON object
|
|
32
|
+
const creditDetailsKey = billingVisitAttributeTypes.creditTypeDetails || 'creditTypeDetails';
|
|
33
|
+
const creditDetails = attributes[creditDetailsKey]
|
|
34
|
+
? typeof attributes[creditDetailsKey] === 'string'
|
|
35
|
+
? JSON.parse(attributes[creditDetailsKey])
|
|
36
|
+
: attributes[creditDetailsKey]
|
|
37
|
+
: {};
|
|
38
|
+
|
|
39
|
+
const updateCreditDetails = (fieldName: string, value: any) => {
|
|
40
|
+
const updatedDetails = { ...creditDetails, [fieldName]: value };
|
|
41
|
+
setValue(`attributes.${creditDetailsKey}`, JSON.stringify(updatedDetails), { shouldDirty: true });
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const renderField = (fieldName: string, label: string, isDate = false, isDropdown = false, items: any[] = []) => {
|
|
45
|
+
const fieldValue = creditDetails[fieldName] || '';
|
|
46
|
+
const creditDetailsError = errors.attributes?.[creditDetailsKey];
|
|
47
|
+
const errorMessage =
|
|
48
|
+
creditDetailsError && typeof creditDetailsError === 'object' && 'message' in creditDetailsError
|
|
49
|
+
? String(creditDetailsError.message)
|
|
50
|
+
: creditDetailsError
|
|
51
|
+
? String(creditDetailsError)
|
|
52
|
+
: undefined;
|
|
53
|
+
|
|
54
|
+
if (isDropdown) {
|
|
55
|
+
return (
|
|
56
|
+
<Dropdown
|
|
57
|
+
key={fieldName}
|
|
58
|
+
id={`attribute-${fieldName}`}
|
|
59
|
+
titleText={label}
|
|
60
|
+
label={label}
|
|
61
|
+
items={items}
|
|
62
|
+
itemToString={(item) => (item ? (typeof item === 'string' ? item : item.name || item.uuid) : '')}
|
|
63
|
+
selectedItem={
|
|
64
|
+
items.find((item) => (typeof item === 'string' ? item === fieldValue : item.uuid === fieldValue)) || null
|
|
65
|
+
}
|
|
66
|
+
onChange={({ selectedItem }) => {
|
|
67
|
+
const selectedValue = typeof selectedItem === 'string' ? selectedItem : selectedItem?.uuid || '';
|
|
68
|
+
updateCreditDetails(fieldName, selectedValue);
|
|
69
|
+
}}
|
|
70
|
+
invalid={!!creditDetailsError}
|
|
71
|
+
invalidText={errorMessage}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isDate) {
|
|
77
|
+
return (
|
|
78
|
+
<OpenmrsDatePicker
|
|
79
|
+
key={fieldName}
|
|
80
|
+
id={`attribute-${fieldName}`}
|
|
81
|
+
labelText={label}
|
|
82
|
+
value={fieldValue || ''}
|
|
83
|
+
onChange={(date) => {
|
|
84
|
+
const dateValue = typeof date === 'string' ? date : date.toISOString().split('T')[0];
|
|
85
|
+
updateCreditDetails(fieldName, dateValue);
|
|
86
|
+
}}
|
|
87
|
+
invalid={!!creditDetailsError}
|
|
88
|
+
invalidText={errorMessage}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<TextInput
|
|
95
|
+
key={fieldName}
|
|
96
|
+
id={`attribute-${fieldName}`}
|
|
97
|
+
labelText={label}
|
|
98
|
+
value={fieldValue || ''}
|
|
99
|
+
onChange={(e) => {
|
|
100
|
+
updateCreditDetails(fieldName, e.target.value);
|
|
101
|
+
}}
|
|
102
|
+
placeholder={t('enterValue', 'Enter {{name}}', { name: label })}
|
|
103
|
+
invalid={!!creditDetailsError}
|
|
104
|
+
invalidText={errorMessage}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<FormGroup className={styles.creditTypeDetailsContainer} legendText={t('creditDetails', 'Credit Details')}>
|
|
111
|
+
{creditSubType === 'cbhi' && (
|
|
112
|
+
<>
|
|
113
|
+
{renderField('cbhiId', t('cbhiId', 'CBHI ID'))}
|
|
114
|
+
{renderField('cbhiExpiryDate', t('expiryDate', 'Expiry Date'), true)}
|
|
115
|
+
</>
|
|
116
|
+
)}
|
|
117
|
+
{creditSubType === 'shi' && (
|
|
118
|
+
<>
|
|
119
|
+
{renderField('shiId', t('shiId', 'SHI ID'))}
|
|
120
|
+
{renderField('shiExpiryDate', t('expiryDate', 'Expiry Date'), true)}
|
|
121
|
+
</>
|
|
122
|
+
)}
|
|
123
|
+
{creditSubType === 'insurance' && (
|
|
124
|
+
<>
|
|
125
|
+
{renderField('insuranceName', t('insuranceName', 'Insurance Name'))}
|
|
126
|
+
{renderField('insuranceId', t('insuranceId', 'Insurance ID'))}
|
|
127
|
+
{renderField('insuranceCode', t('insuranceCode', 'Insurance Code'))}
|
|
128
|
+
{renderField('insuranceZone', t('insuranceZone', 'Insurance Zone'))}
|
|
129
|
+
{renderField('insuranceExpiryDate', t('insuranceExpiryDate', 'Insurance Expiry Date'), true)}
|
|
130
|
+
</>
|
|
131
|
+
)}
|
|
132
|
+
{creditSubType === 'creditCompany' && (
|
|
133
|
+
<>
|
|
134
|
+
{renderField(
|
|
135
|
+
'creditCompany',
|
|
136
|
+
t('creditCompany', 'Credit Company'),
|
|
137
|
+
false,
|
|
138
|
+
true,
|
|
139
|
+
creditCompanies.map((company) => ({ uuid: company.uuid, name: company.name })),
|
|
140
|
+
)}
|
|
141
|
+
</>
|
|
142
|
+
)}
|
|
143
|
+
</FormGroup>
|
|
144
|
+
);
|
|
145
|
+
};
|