@palladium-ethiopia/esm-clinical-workflow-app 5.4.2-pre.4 → 5.4.2-pre.6
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 +8 -2
- package/dist/112.js +1 -0
- package/dist/112.js.map +1 -0
- package/dist/127.js +1 -1
- package/dist/449.js +1 -1
- package/dist/449.js.map +1 -1
- package/dist/467.js +6 -0
- package/dist/467.js.map +1 -0
- package/dist/54.js +1 -0
- package/dist/54.js.map +1 -0
- package/dist/542.js +1 -1
- package/dist/542.js.map +1 -1
- package/dist/{532.js → 813.js} +18 -18
- package/dist/813.js.map +1 -0
- package/dist/834.js +1 -0
- package/dist/834.js.map +1 -0
- package/dist/916.js +1 -1
- package/dist/ethiopia-esm-clinical-workflow-app.js +3 -3
- package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +103 -122
- package/dist/ethiopia-esm-clinical-workflow-app.js.map +1 -1
- package/dist/main.js +10 -5
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +16 -0
- package/src/index.ts +15 -0
- package/src/mru/billing-information/billing-information.resource.ts +137 -0
- package/src/mru/billing-information/billing-information.scss +55 -0
- package/src/mru/billing-information/billing-information.workspace.tsx +369 -0
- package/src/mru/dashboard.component.tsx +18 -0
- package/src/mru/mru.component.tsx +106 -0
- package/src/mru/mru.scss +28 -0
- package/src/routes.json +25 -1
- package/translations/am.json +36 -1
- package/translations/en.json +36 -1
- package/dist/40.js +0 -1
- package/dist/532.js.map +0 -1
- package/dist/593.js +0 -6
- package/dist/593.js.map +0 -1
- package/dist/77.js +0 -1
- package/dist/77.js.map +0 -1
- package/dist/972.js +0 -1
- package/dist/972.js.map +0 -1
- package/translations/sw.json +0 -19
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[],"extensions":[{"name":"triage-dashboard-link","component":"triageDashboardLink","slot":"homepage-dashboard-slot","order":0,"meta":{"name":"triage","slot":"triage-dashboard-slot","title":"triage"}},{"name":"triage-dashboard","component":"triageDashboard","slot":"triage-dashboard-slot"}],"workspaces":[{"name":"triage-workspace","component":"triageWorkspace","title":"Triage Form","canMaximize":true,"type":"clinical-form"},{"name":"patient-registration-workspace","component":"patientRegistrationWorkspace","title":"Patient Registration","canMaximize":true,"type":"registration-form"}],"modals":[],"workspaceGroups":[],"version":"5.4.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[],"extensions":[{"name":"triage-dashboard-link","component":"triageDashboardLink","slot":"homepage-dashboard-slot","order":0,"meta":{"name":"triage","slot":"triage-dashboard-slot","title":"triage"}},{"name":"triage-dashboard","component":"triageDashboard","slot":"triage-dashboard-slot"},{"name":"ewf-mru-dashboard-link","component":"mruLeftPanelLink","slot":"homepage-dashboard-slot","meta":{"name":"mru","title":"MRU","path":"mru","slot":"mru-dashboard-slot"}},{"name":"ewf-mru-dashboard","component":"mruDashboard","slot":"mru-dashboard-slot","online":true,"offline":false}],"workspaces":[{"name":"triage-workspace","component":"triageWorkspace","title":"Triage Form","canMaximize":true,"type":"clinical-form"},{"name":"patient-registration-workspace","component":"patientRegistrationWorkspace","title":"Patient Registration","canMaximize":true,"type":"registration-form"},{"name":"billing-information-workspace","component":"billingInformationWorkspace","title":"Billing Information Workspace","type":"clinical-form"}],"modals":[],"workspaceGroups":[],"version":"5.4.2-pre.6"}
|
package/package.json
CHANGED
package/src/config-schema.ts
CHANGED
|
@@ -7,8 +7,24 @@ export const configSchema = {
|
|
|
7
7
|
_elements: { _type: Type.String },
|
|
8
8
|
_default: ['Central Triage', 'Pediatrics Triage', 'Emergency Triage'],
|
|
9
9
|
},
|
|
10
|
+
billingVisitAttributeTypes: {
|
|
11
|
+
_type: Type.Object,
|
|
12
|
+
_description: 'Visit attribute type UUIDs for billing information',
|
|
13
|
+
_default: {
|
|
14
|
+
paymentMethod: 'e6cb0c3b-04b0-4117-9bc6-ce24adbda802',
|
|
15
|
+
creditType: '5cd1eb62-e006-4146-bd22-80bc4d5bd2f7',
|
|
16
|
+
creditTypeDetails: 'd824aa96-d2c7-4a52-aa8d-03f60a516083',
|
|
17
|
+
freeType: '7523ecfe-b8f1-4e7f-80a7-1a495b15ace4',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
10
20
|
};
|
|
11
21
|
|
|
12
22
|
export type ClinicalWorkflowConfig = {
|
|
13
23
|
triageServices: Array<string>;
|
|
24
|
+
billingVisitAttributeTypes: {
|
|
25
|
+
paymentMethod: string;
|
|
26
|
+
creditType: string;
|
|
27
|
+
creditTypeDetails: string;
|
|
28
|
+
freeType: string;
|
|
29
|
+
};
|
|
14
30
|
};
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { defineConfigSchema, getAsyncLifecycle, getSyncLifecycle } from '@openmr
|
|
|
2
2
|
import { createDashboardLink } from './createDashboardLink';
|
|
3
3
|
import { configSchema } from './config-schema';
|
|
4
4
|
import { dashboardMeta } from './dashboard.meta';
|
|
5
|
+
import MRUDashboard from './mru/dashboard.component';
|
|
6
|
+
import { spaBasePath } from './constants';
|
|
7
|
+
import BillingInformationWorkspace from './mru/billing-information/billing-information.workspace';
|
|
5
8
|
|
|
6
9
|
const moduleName = '@ethiopia/esm-clinical-workflow-app';
|
|
7
10
|
|
|
@@ -24,3 +27,15 @@ export const patientRegistrationWorkspace = getAsyncLifecycle(
|
|
|
24
27
|
() => import('./patient-registration/patient.registration.workspace'),
|
|
25
28
|
options,
|
|
26
29
|
);
|
|
30
|
+
|
|
31
|
+
export const mruDashboard = getSyncLifecycle(MRUDashboard, options);
|
|
32
|
+
export const mruLeftPanelLink = getSyncLifecycle(
|
|
33
|
+
createDashboardLink({
|
|
34
|
+
path: 'mru',
|
|
35
|
+
title: 'MRU',
|
|
36
|
+
basePath: spaBasePath,
|
|
37
|
+
}),
|
|
38
|
+
options,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export const billingInformationWorkspace = getSyncLifecycle(BillingInformationWorkspace, options);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
// Create the billing form schema factory with conditional validation based on skip logic
|
|
4
|
+
export const createBillingFormSchema = (t: (key: string, defaultValue?: string) => string) => {
|
|
5
|
+
return z
|
|
6
|
+
.object({
|
|
7
|
+
billingType: z.enum(['credit', 'free', 'cash']).optional(),
|
|
8
|
+
creditType: z.string().optional(),
|
|
9
|
+
name: z.string().optional(),
|
|
10
|
+
code: z.string().optional(),
|
|
11
|
+
id: z.string().optional(),
|
|
12
|
+
expiryDate: z.string().optional(),
|
|
13
|
+
zone: z.string().optional(),
|
|
14
|
+
freeType: z.string().optional(),
|
|
15
|
+
})
|
|
16
|
+
.superRefine((data, ctx) => {
|
|
17
|
+
// Billing type is required on submit
|
|
18
|
+
if (!data.billingType) {
|
|
19
|
+
ctx.addIssue({
|
|
20
|
+
code: z.ZodIssueCode.custom,
|
|
21
|
+
message: t('billingTypeRequired', 'Billing type is required'),
|
|
22
|
+
path: ['billingType'],
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Credit billing type validation
|
|
28
|
+
if (data.billingType === 'credit') {
|
|
29
|
+
// Credit type is required
|
|
30
|
+
if (!data.creditType || data.creditType.trim() === '') {
|
|
31
|
+
ctx.addIssue({
|
|
32
|
+
code: z.ZodIssueCode.custom,
|
|
33
|
+
message: t('creditTypeRequired', 'Credit type is required'),
|
|
34
|
+
path: ['creditType'],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// If creditType is insurance, require additional fields
|
|
39
|
+
if (data.creditType === 'insurance') {
|
|
40
|
+
if (!data.id || data.id.trim() === '') {
|
|
41
|
+
ctx.addIssue({
|
|
42
|
+
code: z.ZodIssueCode.custom,
|
|
43
|
+
message: t('idRequired', 'ID is required'),
|
|
44
|
+
path: ['id'],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Free billing type validation
|
|
51
|
+
if (data.billingType === 'free') {
|
|
52
|
+
if (!data.freeType || data.freeType.trim() === '') {
|
|
53
|
+
ctx.addIssue({
|
|
54
|
+
code: z.ZodIssueCode.custom,
|
|
55
|
+
message: t('freeTypeRequired', 'Free type is required'),
|
|
56
|
+
path: ['freeType'],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type BillingFormData = z.infer<ReturnType<typeof createBillingFormSchema>>;
|
|
64
|
+
|
|
65
|
+
const transformFormObjectToVisitAttributes = (
|
|
66
|
+
formObject: Record<string, any>,
|
|
67
|
+
visitAttributeTypeUuidsMap: Record<string, string>,
|
|
68
|
+
) => {
|
|
69
|
+
return Object.entries(formObject)
|
|
70
|
+
.map(([key, value]) => ({
|
|
71
|
+
attributeType: visitAttributeTypeUuidsMap[key],
|
|
72
|
+
value: value,
|
|
73
|
+
}))
|
|
74
|
+
.filter((item) => {
|
|
75
|
+
// Filter out undefined, null, or empty string values
|
|
76
|
+
if (item.value === undefined || item.value === null || item.value === '') {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// Filter out empty creditTypeDetails object
|
|
80
|
+
if (item.value && typeof item.value === 'object' && !Array.isArray(item.value)) {
|
|
81
|
+
const creditDetails = item.value as Record<string, any>;
|
|
82
|
+
const hasAnyValue = Object.values(creditDetails).some((val) => val !== undefined && val !== null && val !== '');
|
|
83
|
+
if (!hasAnyValue) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
})
|
|
89
|
+
.map((item) => ({
|
|
90
|
+
attributeType: item.attributeType,
|
|
91
|
+
value: item.value && typeof item.value === 'object' ? JSON.stringify(item.value) : item.value,
|
|
92
|
+
}));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const createBillingInformationVisitAttribute = (
|
|
96
|
+
billingFormData: BillingFormData,
|
|
97
|
+
visitAttributeTypeUuidsMap: {
|
|
98
|
+
paymentMethod: string;
|
|
99
|
+
creditType: string;
|
|
100
|
+
creditTypeDetails: string;
|
|
101
|
+
freeType: string;
|
|
102
|
+
},
|
|
103
|
+
) => {
|
|
104
|
+
const { billingType, creditType, name, code, id, expiryDate, zone, freeType } = billingFormData;
|
|
105
|
+
|
|
106
|
+
const formObject = {
|
|
107
|
+
paymentMethod: billingType,
|
|
108
|
+
creditType: creditType,
|
|
109
|
+
creditTypeDetails: {
|
|
110
|
+
name: name,
|
|
111
|
+
code: code,
|
|
112
|
+
id: id,
|
|
113
|
+
expiryDate: expiryDate,
|
|
114
|
+
zone: zone,
|
|
115
|
+
},
|
|
116
|
+
freeType: freeType,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const visitAttributePayload = transformFormObjectToVisitAttributes(formObject, visitAttributeTypeUuidsMap);
|
|
120
|
+
|
|
121
|
+
return visitAttributePayload;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const updateVisitWithBillingInformation = (
|
|
125
|
+
visitAttributePayload: Array<{ attributeType: string; value: string }>,
|
|
126
|
+
visitUuid: string,
|
|
127
|
+
) => {
|
|
128
|
+
return openmrsFetch(`${restBaseUrl}/visit/${visitUuid}`, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-type': 'application/json',
|
|
132
|
+
},
|
|
133
|
+
body: {
|
|
134
|
+
attributes: visitAttributePayload,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/spacing';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/layout';
|
|
4
|
+
@use '@carbon/colors';
|
|
5
|
+
|
|
6
|
+
.sectionTitle {
|
|
7
|
+
@include type.type-style('label-01');
|
|
8
|
+
color: colors.$gray-70;
|
|
9
|
+
margin-bottom: spacing.$spacing-03;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.buttonSet {
|
|
13
|
+
width: 100%;
|
|
14
|
+
padding-top: spacing.$spacing-05;
|
|
15
|
+
background-color: colors.$white-0;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
bottom: 0;
|
|
18
|
+
position: absolute;
|
|
19
|
+
& > button {
|
|
20
|
+
max-width: 50% !important;
|
|
21
|
+
width: 50%;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.billingInformationContainer {
|
|
26
|
+
margin: spacing.$spacing-05;
|
|
27
|
+
height: 100%;
|
|
28
|
+
overflow-y: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.creditDetailsContainer {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: spacing.$spacing-05;
|
|
35
|
+
margin-top: spacing.$spacing-03;
|
|
36
|
+
min-height: 45rem;
|
|
37
|
+
border: 1px solid colors.$gray-20;
|
|
38
|
+
padding: spacing.$spacing-03;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.freeDetailsContainer {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: spacing.$spacing-05;
|
|
45
|
+
margin-top: spacing.$spacing-03;
|
|
46
|
+
min-height: 20rem;
|
|
47
|
+
border: 1px solid colors.$gray-20;
|
|
48
|
+
padding: spacing.$spacing-03;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.creditTypeDetailsContainer {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: spacing.$spacing-03;
|
|
55
|
+
}
|
|
@@ -0,0 +1,369 @@
|
|
|
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 { useForm, Controller, Control, FieldErrors, useWatch } from 'react-hook-form';
|
|
24
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
type BillingFormData,
|
|
28
|
+
createBillingFormSchema,
|
|
29
|
+
createBillingInformationVisitAttribute,
|
|
30
|
+
updateVisitWithBillingInformation,
|
|
31
|
+
} from './billing-information.resource';
|
|
32
|
+
import type { ClinicalWorkflowConfig } from '../../config-schema';
|
|
33
|
+
import styles from './billing-information.scss';
|
|
34
|
+
|
|
35
|
+
type BillingInformationWorkspaceProps = DefaultWorkspaceProps & {
|
|
36
|
+
patientUuid: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const BillingInformationWorkspace: React.FC<BillingInformationWorkspaceProps> = ({
|
|
40
|
+
patientUuid,
|
|
41
|
+
closeWorkspace,
|
|
42
|
+
promptBeforeClosing,
|
|
43
|
+
closeWorkspaceWithSavedChanges,
|
|
44
|
+
}) => {
|
|
45
|
+
const { t } = useTranslation();
|
|
46
|
+
const { patient, isLoading } = usePatient(patientUuid);
|
|
47
|
+
const { activeVisit, mutate: mutateVisit } = useVisit(patientUuid);
|
|
48
|
+
const { billingVisitAttributeTypes } = useConfig<ClinicalWorkflowConfig>();
|
|
49
|
+
const billingFormSchema = useMemo(() => createBillingFormSchema(t), [t]);
|
|
50
|
+
|
|
51
|
+
const {
|
|
52
|
+
control,
|
|
53
|
+
handleSubmit,
|
|
54
|
+
watch,
|
|
55
|
+
setValue,
|
|
56
|
+
formState: { errors, isDirty },
|
|
57
|
+
} = useForm<BillingFormData>({
|
|
58
|
+
resolver: zodResolver(billingFormSchema),
|
|
59
|
+
mode: 'onTouched',
|
|
60
|
+
defaultValues: {
|
|
61
|
+
billingType: undefined,
|
|
62
|
+
creditType: '',
|
|
63
|
+
name: '',
|
|
64
|
+
code: '',
|
|
65
|
+
id: '',
|
|
66
|
+
expiryDate: '',
|
|
67
|
+
zone: '',
|
|
68
|
+
freeType: '',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const billingType = watch('billingType');
|
|
73
|
+
|
|
74
|
+
const onSubmit = async (data: BillingFormData) => {
|
|
75
|
+
try {
|
|
76
|
+
const visitAttributePayload = createBillingInformationVisitAttribute(data, billingVisitAttributeTypes);
|
|
77
|
+
const response = await updateVisitWithBillingInformation(visitAttributePayload, activeVisit?.uuid);
|
|
78
|
+
if (response.status === 200) {
|
|
79
|
+
showSnackbar({
|
|
80
|
+
title: t('updateVisitWithBillingInfo', 'Update Visit With Billing Information'),
|
|
81
|
+
subtitle: t('updateVisitWithBillingInfoSuccess', 'Update Visit With Billing Information Success'),
|
|
82
|
+
kind: 'success',
|
|
83
|
+
isLowContrast: true,
|
|
84
|
+
timeoutInMs: 5000,
|
|
85
|
+
});
|
|
86
|
+
mutateVisit();
|
|
87
|
+
closeWorkspaceWithSavedChanges();
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
showSnackbar({
|
|
91
|
+
title: t('error', 'Error'),
|
|
92
|
+
subtitle: t('errorUpdatingBillingInformation', 'Error updating billing information, {{error}}', {
|
|
93
|
+
error: error.message,
|
|
94
|
+
}),
|
|
95
|
+
kind: 'error',
|
|
96
|
+
isLowContrast: true,
|
|
97
|
+
timeoutInMs: 5000,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleBillingTypeChange = (type: 'credit' | 'free' | 'cash') => {
|
|
103
|
+
// Clear form fields to avoid carrying over values from previous billing type
|
|
104
|
+
setValue('billingType', type, { shouldDirty: true });
|
|
105
|
+
setValue('creditType', '', { shouldDirty: false });
|
|
106
|
+
setValue('name', '', { shouldDirty: false });
|
|
107
|
+
setValue('code', '', { shouldDirty: false });
|
|
108
|
+
setValue('id', '', { shouldDirty: false });
|
|
109
|
+
setValue('expiryDate', '', { shouldDirty: false });
|
|
110
|
+
setValue('zone', '', { shouldDirty: false });
|
|
111
|
+
setValue('freeType', '', { shouldDirty: false });
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const getSelectedIndex = () => {
|
|
115
|
+
if (billingType === 'cash') {
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
if (billingType === 'free') {
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
if (billingType === 'credit') {
|
|
122
|
+
return 2;
|
|
123
|
+
}
|
|
124
|
+
return -1;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const handleContentSwitcherChange = ({ index }: { index: number }) => {
|
|
128
|
+
const types: ('cash' | 'free' | 'credit')[] = ['cash', 'free', 'credit'];
|
|
129
|
+
if (index >= 0 && index < types.length) {
|
|
130
|
+
handleBillingTypeChange(types[index]);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
promptBeforeClosing(() => isDirty);
|
|
136
|
+
}, [promptBeforeClosing, isDirty]);
|
|
137
|
+
|
|
138
|
+
if (isLoading) {
|
|
139
|
+
return <InlineLoading status="active" iconDescription="Loading" description="Loading billing information..." />;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<>
|
|
144
|
+
{patient && (
|
|
145
|
+
<ExtensionSlot
|
|
146
|
+
name="patient-header-slot"
|
|
147
|
+
state={{
|
|
148
|
+
patient,
|
|
149
|
+
patientUuid: patientUuid,
|
|
150
|
+
hideActionsOverflow: true,
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
)}
|
|
154
|
+
<Form onSubmit={handleSubmit(onSubmit)}>
|
|
155
|
+
<div className={styles.billingInformationContainer}>
|
|
156
|
+
<p className={styles.sectionTitle}>{t('paymentMethods', 'Payment Methods')}</p>
|
|
157
|
+
<ContentSwitcher onChange={handleContentSwitcherChange} selectedIndex={getSelectedIndex()} size="md">
|
|
158
|
+
<Switch name="cash" text={t('cash', 'Cash')} />
|
|
159
|
+
<Switch name="free" text={t('free', 'Free')} />
|
|
160
|
+
<Switch name="credit" text={t('credit', 'Credit')} />
|
|
161
|
+
</ContentSwitcher>
|
|
162
|
+
|
|
163
|
+
{billingType === 'credit' && <CreditDetails control={control} errors={errors} t={t} />}
|
|
164
|
+
{billingType === 'free' && <FreeDetails control={control} errors={errors} t={t} />}
|
|
165
|
+
</div>
|
|
166
|
+
<ButtonSet className={styles.buttonSet}>
|
|
167
|
+
<Button className={styles.button} kind="secondary" onClick={() => closeWorkspace()}>
|
|
168
|
+
{t('discard', 'Discard')}
|
|
169
|
+
</Button>
|
|
170
|
+
<Button disabled={!isDirty} className={styles.button} kind="primary" type="submit">
|
|
171
|
+
{t('saveAndClose', 'Save & Close')}
|
|
172
|
+
</Button>
|
|
173
|
+
</ButtonSet>
|
|
174
|
+
</Form>
|
|
175
|
+
</>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export default BillingInformationWorkspace;
|
|
180
|
+
|
|
181
|
+
type CreditDetailsProps = {
|
|
182
|
+
control: Control<BillingFormData>;
|
|
183
|
+
errors: FieldErrors<BillingFormData>;
|
|
184
|
+
t: (key: string, defaultValue?: string) => string;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const CreditDetails: React.FC<CreditDetailsProps> = ({ control, errors, t }) => {
|
|
188
|
+
const creditType = useWatch({
|
|
189
|
+
control,
|
|
190
|
+
name: 'creditType',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const creditTypeOptions = [
|
|
194
|
+
{
|
|
195
|
+
id: 'chbi',
|
|
196
|
+
text: t('chbi', 'CHBI'),
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 'shi',
|
|
200
|
+
text: t('shi', 'SHI'),
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'creditCompanies',
|
|
204
|
+
text: t('creditCompanies', 'Credit Companies'),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'insurance',
|
|
208
|
+
text: t('insurance', 'Insurance'),
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
const showCreditCompanyFields = creditType === 'creditCompanies' || creditType === 'insurance';
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<FormGroup className={styles.creditDetailsContainer} legendText={t('creditDetails', 'Credit Details')}>
|
|
216
|
+
<Controller
|
|
217
|
+
name="creditType"
|
|
218
|
+
control={control}
|
|
219
|
+
render={({ field: { onChange, value } }) => (
|
|
220
|
+
<Dropdown
|
|
221
|
+
id="credit-type"
|
|
222
|
+
invalid={!!errors.creditType}
|
|
223
|
+
invalidText={errors.creditType?.message || 'invalid selection'}
|
|
224
|
+
itemToString={(item) => item?.text ?? ''}
|
|
225
|
+
items={creditTypeOptions}
|
|
226
|
+
label="Credit"
|
|
227
|
+
titleText="Credit"
|
|
228
|
+
type="default"
|
|
229
|
+
selectedItem={creditTypeOptions.find((item) => item.id === value) || null}
|
|
230
|
+
onChange={({ selectedItem }) => onChange(selectedItem?.id || '')}
|
|
231
|
+
/>
|
|
232
|
+
)}
|
|
233
|
+
/>
|
|
234
|
+
|
|
235
|
+
{showCreditCompanyFields && (
|
|
236
|
+
<FormGroup
|
|
237
|
+
legendText={t('creditTypeDetails', 'Credit Type Details')}
|
|
238
|
+
className={styles.creditTypeDetailsContainer}>
|
|
239
|
+
<Controller
|
|
240
|
+
name="id"
|
|
241
|
+
control={control}
|
|
242
|
+
render={({ field: { onChange, value } }) => (
|
|
243
|
+
<TextInput
|
|
244
|
+
id="credit-id"
|
|
245
|
+
labelText={t('id', 'ID')}
|
|
246
|
+
value={value || ''}
|
|
247
|
+
onChange={(e) => onChange(e.target.value)}
|
|
248
|
+
placeholder={t('enterId', 'Enter ID')}
|
|
249
|
+
invalid={!!errors.id}
|
|
250
|
+
invalidText={errors.id?.message}
|
|
251
|
+
/>
|
|
252
|
+
)}
|
|
253
|
+
/>
|
|
254
|
+
|
|
255
|
+
<Controller
|
|
256
|
+
name="name"
|
|
257
|
+
control={control}
|
|
258
|
+
render={({ field: { onChange, value } }) => (
|
|
259
|
+
<TextInput
|
|
260
|
+
id="credit-name"
|
|
261
|
+
labelText={t('name', 'Name')}
|
|
262
|
+
value={value || ''}
|
|
263
|
+
onChange={(e) => onChange(e.target.value)}
|
|
264
|
+
placeholder={t('enterName', 'Enter name')}
|
|
265
|
+
invalid={!!errors.name}
|
|
266
|
+
invalidText={errors.name?.message}
|
|
267
|
+
/>
|
|
268
|
+
)}
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
<Controller
|
|
272
|
+
name="code"
|
|
273
|
+
control={control}
|
|
274
|
+
render={({ field: { onChange, value } }) => (
|
|
275
|
+
<TextInput
|
|
276
|
+
id="credit-code"
|
|
277
|
+
labelText={t('code', 'Code')}
|
|
278
|
+
value={value || ''}
|
|
279
|
+
onChange={(e) => onChange(e.target.value)}
|
|
280
|
+
placeholder={t('enterCode', 'Enter code')}
|
|
281
|
+
invalid={!!errors.code}
|
|
282
|
+
invalidText={errors.code?.message}
|
|
283
|
+
/>
|
|
284
|
+
)}
|
|
285
|
+
/>
|
|
286
|
+
|
|
287
|
+
<Controller
|
|
288
|
+
name="expiryDate"
|
|
289
|
+
control={control}
|
|
290
|
+
render={({ field: { onChange, value } }) => (
|
|
291
|
+
<OpenmrsDatePicker
|
|
292
|
+
id="credit-expiry-date"
|
|
293
|
+
labelText={t('expiryDate', 'Expiry Date')}
|
|
294
|
+
minDate={new Date()}
|
|
295
|
+
value={value || ''}
|
|
296
|
+
onChange={(date) => {
|
|
297
|
+
const dateValue = typeof date === 'string' ? date : date.toISOString().split('T')[0];
|
|
298
|
+
onChange(dateValue);
|
|
299
|
+
}}
|
|
300
|
+
/>
|
|
301
|
+
)}
|
|
302
|
+
/>
|
|
303
|
+
<Controller
|
|
304
|
+
name="zone"
|
|
305
|
+
control={control}
|
|
306
|
+
render={({ field: { onChange, value } }) => (
|
|
307
|
+
<TextInput
|
|
308
|
+
id="credit-zone"
|
|
309
|
+
labelText={t('zone', 'Zone')}
|
|
310
|
+
value={value || ''}
|
|
311
|
+
onChange={(e) => onChange(e.target.value)}
|
|
312
|
+
placeholder={t('enterZone', 'Enter zone')}
|
|
313
|
+
invalid={!!errors.zone}
|
|
314
|
+
invalidText={errors.zone?.message}
|
|
315
|
+
/>
|
|
316
|
+
)}
|
|
317
|
+
/>
|
|
318
|
+
</FormGroup>
|
|
319
|
+
)}
|
|
320
|
+
</FormGroup>
|
|
321
|
+
);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
type FreeDetailsProps = {
|
|
325
|
+
control: Control<BillingFormData>;
|
|
326
|
+
errors: FieldErrors<BillingFormData>;
|
|
327
|
+
t: (key: string, defaultValue?: string) => string;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const FreeDetails: React.FC<FreeDetailsProps> = ({ control, errors, t }) => {
|
|
331
|
+
const items = [
|
|
332
|
+
{
|
|
333
|
+
id: 'staff',
|
|
334
|
+
text: t('staff', 'Staff'),
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: '24Hours',
|
|
338
|
+
text: t('24Hours', '24 Hours'),
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: 'exempted',
|
|
342
|
+
text: t('exempted', 'Exempted'),
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<FormGroup className={styles.freeDetailsContainer} legendText={t('freeDetails', 'Free Details')}>
|
|
348
|
+
<Controller
|
|
349
|
+
name="freeType"
|
|
350
|
+
control={control}
|
|
351
|
+
render={({ field: { onChange, value } }) => (
|
|
352
|
+
<Dropdown
|
|
353
|
+
id="free-type"
|
|
354
|
+
hideLabel={true}
|
|
355
|
+
invalid={!!errors.freeType}
|
|
356
|
+
invalidText={errors.freeType?.message || 'invalid selection'}
|
|
357
|
+
itemToString={(item) => item?.text ?? ''}
|
|
358
|
+
items={items}
|
|
359
|
+
label={t('selectOption', 'Select a free type')}
|
|
360
|
+
titleText={t('selectOption', 'Select a free type')}
|
|
361
|
+
type="default"
|
|
362
|
+
selectedItem={items.find((item) => item.id === value) || null}
|
|
363
|
+
onChange={({ selectedItem }) => onChange(selectedItem?.id || '')}
|
|
364
|
+
/>
|
|
365
|
+
)}
|
|
366
|
+
/>
|
|
367
|
+
</FormGroup>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
@@ -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;
|