@digihmis/esm-administration-app 1.0.0 → 1.0.1
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/package.json +1 -1
- package/release-version.js +4 -4
- package/src/billing-management/billing-charge-items/billing-charge-items-modal/billing-charge-items.resource.ts +2 -1
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-items.resource.ts +30 -7
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-service.workspace.tsx +79 -107
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/price.component.scss +16 -0
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/price.component.tsx +30 -20
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/schemas/form-schemas.ts +19 -14
- package/src/billing-management/billing-charge-items/billing-charge-items.component.tsx +26 -35
- package/src/billing-management/billing-charge-items/billing-charge-items.resource.ts +1 -1
- package/src/billing-management/billing-charge-items/type/index.ts +10 -10
- package/src/billing-management/billing-charge-items/utils/index.ts +32 -74
- package/src/billing-management/billing-payment-modes/billing-payment-modes.component.tsx +24 -166
- package/src/billing-management/billing-payment-modes/billing-payment-modes.resource.ts +24 -14
- package/src/billing-management/billing-payment-points/billing-payment-point-modal/billing-delete-payment-point.modal.tsx +1 -1
- package/src/billing-management/billing-payment-points/billing-payment-point.resource.ts +1 -1
- package/src/billing-management/billing-payment-points/billing-payment-point.scss +47 -0
- package/src/billing-management/billing-payment-points/billing-payment-points-workspace/billing-payment-point.resource.ts +18 -23
- package/src/billing-management/billing-payment-points/billing-payment-points-workspace/billing-payment-points.workspace.tsx +1 -1
- package/src/config-schema.ts +17 -0
- package/src/dashboard.meta.ts +8 -0
- package/src/document-branding/document-branding-workspace/document-branding.workspace.scss +34 -0
- package/src/document-branding/document-branding-workspace/document-branding.workspace.tsx +160 -0
- package/src/document-branding/document-branding.component.tsx +185 -0
- package/src/document-branding/document-branding.resource.ts +54 -0
- package/src/document-branding/document-branding.scss +69 -0
- package/src/index.ts +15 -9
- package/src/routes.json +34 -13
- package/dist/101.js +0 -1
- package/dist/101.js.map +0 -1
- package/dist/117.js +0 -1
- package/dist/117.js.map +0 -1
- package/dist/132.js +0 -1
- package/dist/132.js.map +0 -1
- package/dist/15.js +0 -1
- package/dist/15.js.map +0 -1
- package/dist/152.js +0 -1
- package/dist/152.js.map +0 -1
- package/dist/153.js +0 -1
- package/dist/153.js.map +0 -1
- package/dist/190.js +0 -1
- package/dist/190.js.map +0 -1
- package/dist/209.js +0 -1
- package/dist/209.js.map +0 -1
- package/dist/266.js +0 -1
- package/dist/266.js.map +0 -1
- package/dist/317.js +0 -1
- package/dist/317.js.map +0 -1
- package/dist/349.js +0 -1
- package/dist/349.js.map +0 -1
- package/dist/371.js +0 -1
- package/dist/371.js.map +0 -1
- package/dist/378.js +0 -1
- package/dist/378.js.map +0 -1
- package/dist/394.js +0 -1
- package/dist/394.js.map +0 -1
- package/dist/442.js +0 -1
- package/dist/442.js.map +0 -1
- package/dist/45.js +0 -1
- package/dist/45.js.map +0 -1
- package/dist/454.js +0 -1
- package/dist/454.js.map +0 -1
- package/dist/466.js +0 -1
- package/dist/466.js.map +0 -1
- package/dist/482.js +0 -1
- package/dist/482.js.map +0 -1
- package/dist/501.js +0 -1
- package/dist/501.js.map +0 -1
- package/dist/508.js +0 -1
- package/dist/508.js.map +0 -1
- package/dist/578.js +0 -1
- package/dist/578.js.map +0 -1
- package/dist/598.js +0 -1
- package/dist/598.js.map +0 -1
- package/dist/606.js +0 -1
- package/dist/606.js.map +0 -1
- package/dist/61.js +0 -1
- package/dist/61.js.map +0 -1
- package/dist/640.js +0 -1
- package/dist/640.js.map +0 -1
- package/dist/685.js +0 -1
- package/dist/685.js.map +0 -1
- package/dist/689.js +0 -1
- package/dist/689.js.map +0 -1
- package/dist/709.js +0 -1
- package/dist/709.js.map +0 -1
- package/dist/712.js +0 -1
- package/dist/712.js.map +0 -1
- package/dist/720.js +0 -1
- package/dist/720.js.map +0 -1
- package/dist/737.js +0 -12
- package/dist/737.js.map +0 -1
- package/dist/742.js +0 -1
- package/dist/742.js.map +0 -1
- package/dist/747.js +0 -1
- package/dist/747.js.map +0 -1
- package/dist/771.js +0 -1
- package/dist/771.js.map +0 -1
- package/dist/806.js +0 -1
- package/dist/806.js.map +0 -1
- package/dist/912.js +0 -1
- package/dist/912.js.map +0 -1
- package/dist/913.js +0 -1
- package/dist/913.js.map +0 -1
- package/dist/916.js +0 -15
- package/dist/916.js.map +0 -1
- package/dist/940.js +0 -1
- package/dist/940.js.map +0 -1
- package/dist/digihmis-esm-administration-app.js +0 -6
- package/dist/digihmis-esm-administration-app.js.buildmanifest.json +0 -1100
- package/dist/digihmis-esm-administration-app.js.map +0 -1
- package/dist/main.js +0 -17
- package/dist/main.js.map +0 -1
- package/dist/routes.json +0 -1
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-item.workspace.tsx +0 -212
- package/src/billing-management/billing-charge-items/billing-charge-items-workspace/components/stock-search.component.tsx +0 -107
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
|
|
4
|
+
.form {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
height: 100%;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.formContainer {
|
|
12
|
+
margin: layout.$spacing-05;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.tablet {
|
|
16
|
+
padding: layout.$spacing-06 layout.$spacing-05;
|
|
17
|
+
background-color: colors.$white;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.desktop {
|
|
21
|
+
padding: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.logoPreview {
|
|
25
|
+
display: block;
|
|
26
|
+
max-height: 4rem;
|
|
27
|
+
max-width: 12rem;
|
|
28
|
+
margin-bottom: layout.$spacing-03;
|
|
29
|
+
object-fit: contain;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.loading {
|
|
33
|
+
margin: layout.$spacing-07;
|
|
34
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, ButtonSet, Form, FormGroup, FileUploader, InlineLoading, Stack, TextArea, TextInput } from '@carbon/react';
|
|
4
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
5
|
+
import classNames from 'classnames';
|
|
6
|
+
import { mutate } from 'swr';
|
|
7
|
+
import { type DefaultWorkspaceProps, ResponsiveWrapper, showSnackbar, useLayoutType } from '@openmrs/esm-framework';
|
|
8
|
+
import { facilityInfoUrl, type FacilityInfo, saveFacilityInfo, useFacilityInfo } from '../document-branding.resource';
|
|
9
|
+
import styles from './document-branding.workspace.scss';
|
|
10
|
+
|
|
11
|
+
const DocumentBrandingWorkspace: React.FC<DefaultWorkspaceProps> = ({
|
|
12
|
+
closeWorkspace,
|
|
13
|
+
closeWorkspaceWithSavedChanges,
|
|
14
|
+
promptBeforeClosing,
|
|
15
|
+
}) => {
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
18
|
+
const { info, settingUuid, isLoading } = useFacilityInfo();
|
|
19
|
+
|
|
20
|
+
const { control, handleSubmit, reset, setValue, watch, formState: { isDirty, isSubmitting } } = useForm<FacilityInfo>({
|
|
21
|
+
defaultValues: {},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Prefill once the stored value has loaded.
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!isLoading) {
|
|
27
|
+
reset(info);
|
|
28
|
+
}
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
}, [isLoading, settingUuid]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
promptBeforeClosing(() => isDirty);
|
|
34
|
+
}, [isDirty, promptBeforeClosing]);
|
|
35
|
+
|
|
36
|
+
const logoData = watch('logoData');
|
|
37
|
+
|
|
38
|
+
const onLogo = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
39
|
+
const file = event.target.files?.[0];
|
|
40
|
+
if (!file) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const reader = new FileReader();
|
|
44
|
+
reader.onload = () => setValue('logoData', String(reader.result), { shouldDirty: true });
|
|
45
|
+
reader.readAsDataURL(file);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const onSubmit = async (data: FacilityInfo) => {
|
|
49
|
+
if (!settingUuid) {
|
|
50
|
+
showSnackbar({ kind: 'error', title: t('settingMissing', 'Print module setting not found — is it installed?') });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await saveFacilityInfo(settingUuid, data);
|
|
55
|
+
await mutate(facilityInfoUrl);
|
|
56
|
+
showSnackbar({ kind: 'success', isLowContrast: true, title: t('brandingSaved', 'Document letterhead saved') });
|
|
57
|
+
closeWorkspaceWithSavedChanges();
|
|
58
|
+
} catch (err) {
|
|
59
|
+
showSnackbar({
|
|
60
|
+
kind: 'error',
|
|
61
|
+
title: t('brandingError', 'Could not save the letterhead'),
|
|
62
|
+
subtitle: err?.responseBody?.error?.message ?? err?.message,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (isLoading) {
|
|
68
|
+
return <InlineLoading className={styles.loading} description={t('loading', 'Loading…')} />;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
|
|
73
|
+
<div className={styles.formContainer}>
|
|
74
|
+
<Stack gap={5}>
|
|
75
|
+
<Controller
|
|
76
|
+
name="facilityName"
|
|
77
|
+
control={control}
|
|
78
|
+
render={({ field }) => (
|
|
79
|
+
<ResponsiveWrapper>
|
|
80
|
+
<TextInput id="facilityName" labelText={t('facilityName', 'Facility name')} {...field} value={field.value ?? ''} />
|
|
81
|
+
</ResponsiveWrapper>
|
|
82
|
+
)}
|
|
83
|
+
/>
|
|
84
|
+
<Controller
|
|
85
|
+
name="facilityTagline"
|
|
86
|
+
control={control}
|
|
87
|
+
render={({ field }) => (
|
|
88
|
+
<ResponsiveWrapper>
|
|
89
|
+
<TextInput id="facilityTagline" labelText={t('tagline', 'Tagline')} {...field} value={field.value ?? ''} />
|
|
90
|
+
</ResponsiveWrapper>
|
|
91
|
+
)}
|
|
92
|
+
/>
|
|
93
|
+
<Controller
|
|
94
|
+
name="phone"
|
|
95
|
+
control={control}
|
|
96
|
+
render={({ field }) => (
|
|
97
|
+
<ResponsiveWrapper>
|
|
98
|
+
<TextInput id="phone" labelText={t('phone', 'Phone')} {...field} value={field.value ?? ''} />
|
|
99
|
+
</ResponsiveWrapper>
|
|
100
|
+
)}
|
|
101
|
+
/>
|
|
102
|
+
<Controller
|
|
103
|
+
name="email"
|
|
104
|
+
control={control}
|
|
105
|
+
render={({ field }) => (
|
|
106
|
+
<ResponsiveWrapper>
|
|
107
|
+
<TextInput id="email" labelText={t('email', 'Email')} {...field} value={field.value ?? ''} />
|
|
108
|
+
</ResponsiveWrapper>
|
|
109
|
+
)}
|
|
110
|
+
/>
|
|
111
|
+
<Controller
|
|
112
|
+
name="website"
|
|
113
|
+
control={control}
|
|
114
|
+
render={({ field }) => (
|
|
115
|
+
<ResponsiveWrapper>
|
|
116
|
+
<TextInput id="website" labelText={t('website', 'Website')} {...field} value={field.value ?? ''} />
|
|
117
|
+
</ResponsiveWrapper>
|
|
118
|
+
)}
|
|
119
|
+
/>
|
|
120
|
+
<Controller
|
|
121
|
+
name="address"
|
|
122
|
+
control={control}
|
|
123
|
+
render={({ field }) => (
|
|
124
|
+
<ResponsiveWrapper>
|
|
125
|
+
<TextArea id="address" labelText={t('address', 'Address')} rows={2} {...field} value={field.value ?? ''} />
|
|
126
|
+
</ResponsiveWrapper>
|
|
127
|
+
)}
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
<FormGroup legendText={t('logo', 'Logo')}>
|
|
131
|
+
{logoData && <img src={logoData} alt={t('logoPreview', 'Logo preview')} className={styles.logoPreview} />}
|
|
132
|
+
<FileUploader
|
|
133
|
+
accept={['.png', '.jpg', '.jpeg']}
|
|
134
|
+
buttonLabel={t('uploadLogo', 'Upload logo')}
|
|
135
|
+
labelDescription={t('logoHint', 'PNG or JPG; shown top-left on documents.')}
|
|
136
|
+
filenameStatus="edit"
|
|
137
|
+
onChange={onLogo}
|
|
138
|
+
onDelete={() => setValue('logoData', undefined, { shouldDirty: true })}
|
|
139
|
+
/>
|
|
140
|
+
</FormGroup>
|
|
141
|
+
</Stack>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
|
|
145
|
+
<Button style={{ maxWidth: '50%' }} kind="secondary" onClick={() => closeWorkspace()}>
|
|
146
|
+
{t('cancel', 'Cancel')}
|
|
147
|
+
</Button>
|
|
148
|
+
<Button style={{ maxWidth: '50%' }} kind="primary" type="submit" disabled={isSubmitting || !isDirty}>
|
|
149
|
+
{isSubmitting ? (
|
|
150
|
+
<InlineLoading description={t('saving', 'Saving…')} />
|
|
151
|
+
) : (
|
|
152
|
+
t('saveAndClose', 'Save & close')
|
|
153
|
+
)}
|
|
154
|
+
</Button>
|
|
155
|
+
</ButtonSet>
|
|
156
|
+
</Form>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default DocumentBrandingWorkspace;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
DataTable,
|
|
5
|
+
DataTableSkeleton,
|
|
6
|
+
Layer,
|
|
7
|
+
Pagination,
|
|
8
|
+
Table,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableContainer,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableHeader,
|
|
14
|
+
TableRow,
|
|
15
|
+
Tile,
|
|
16
|
+
} from '@carbon/react';
|
|
17
|
+
import { Edit } from '@carbon/react/icons';
|
|
18
|
+
import { launchWorkspace, useLayoutType, useConfig, ErrorState } from '@openmrs/esm-framework';
|
|
19
|
+
import { useTranslation } from 'react-i18next';
|
|
20
|
+
import { CardHeader } from '@openmrs/esm-patient-common-lib';
|
|
21
|
+
import { type ConfigObject } from '../config-schema';
|
|
22
|
+
import { CustomPageHeader } from '../components/custom-page-header/custom-page-header.component';
|
|
23
|
+
import styles from './document-branding.scss';
|
|
24
|
+
import { useFacilityInfo } from './document-branding.resource';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Admin page for the document letterhead — the facility identity (name, tagline, contacts, address)
|
|
28
|
+
* and logo shown on every generated PDF. It lists the stored value and launches a workspace to edit it,
|
|
29
|
+
* writing to the print module's global property so no one has to hand-edit JSON.
|
|
30
|
+
*/
|
|
31
|
+
const DocumentBrandingDashboard = () => {
|
|
32
|
+
const { t } = useTranslation();
|
|
33
|
+
const layout = useLayoutType();
|
|
34
|
+
const isTablet = layout === 'tablet';
|
|
35
|
+
const responsiveSize = isTablet ? 'lg' : 'sm';
|
|
36
|
+
const { documentBrandingIcon } = useConfig<ConfigObject>();
|
|
37
|
+
|
|
38
|
+
const { info, isLoading, error } = useFacilityInfo();
|
|
39
|
+
|
|
40
|
+
const headers = [
|
|
41
|
+
{ key: 'field', header: t('field', 'Field') },
|
|
42
|
+
{ key: 'value', header: t('value', 'Value') },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const fields = useMemo(
|
|
46
|
+
() => [
|
|
47
|
+
{ key: 'facilityName', label: t('facilityName', 'Facility name'), value: info.facilityName },
|
|
48
|
+
{ key: 'facilityTagline', label: t('tagline', 'Tagline'), value: info.facilityTagline },
|
|
49
|
+
{ key: 'phone', label: t('phone', 'Phone'), value: info.phone },
|
|
50
|
+
{ key: 'email', label: t('email', 'Email'), value: info.email },
|
|
51
|
+
{ key: 'website', label: t('website', 'Website'), value: info.website },
|
|
52
|
+
{ key: 'address', label: t('address', 'Address'), value: info.address },
|
|
53
|
+
],
|
|
54
|
+
[info, t],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const hasLogo = Boolean(info.logoData || info.logoPath);
|
|
58
|
+
|
|
59
|
+
const tableRows = useMemo(() => {
|
|
60
|
+
const rows = fields
|
|
61
|
+
.filter((field) => field.value)
|
|
62
|
+
.map((field) => ({ id: field.key, field: field.label, value: field.value as React.ReactNode }));
|
|
63
|
+
if (hasLogo) {
|
|
64
|
+
rows.push({
|
|
65
|
+
id: 'logo',
|
|
66
|
+
field: t('logo', 'Logo'),
|
|
67
|
+
value: (
|
|
68
|
+
<img
|
|
69
|
+
src={info.logoData ?? info.logoPath}
|
|
70
|
+
alt={t('logoPreview', 'Logo preview')}
|
|
71
|
+
className={styles.logoPreview}
|
|
72
|
+
/>
|
|
73
|
+
),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return rows;
|
|
77
|
+
}, [fields, hasLogo, info.logoData, info.logoPath, t]);
|
|
78
|
+
|
|
79
|
+
const hasBranding = tableRows.length > 0;
|
|
80
|
+
|
|
81
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
82
|
+
const [currentPageSize, setCurrentPageSize] = useState(10);
|
|
83
|
+
const pageSizes = [10, 20, 30, 40, 50];
|
|
84
|
+
|
|
85
|
+
const paginatedRows = useMemo(() => {
|
|
86
|
+
const startIndex = (currentPage - 1) * currentPageSize;
|
|
87
|
+
return tableRows.slice(startIndex, startIndex + currentPageSize);
|
|
88
|
+
}, [tableRows, currentPage, currentPageSize]);
|
|
89
|
+
|
|
90
|
+
const handleConfigure = () => {
|
|
91
|
+
launchWorkspace('document-branding-workspace', {
|
|
92
|
+
workspaceTitle: t('configureLetterhead', 'Configure letterhead'),
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const renderBody = () => {
|
|
97
|
+
if (isLoading) {
|
|
98
|
+
return <DataTableSkeleton role="progressbar" compact={!isTablet} zebra />;
|
|
99
|
+
}
|
|
100
|
+
if (error) {
|
|
101
|
+
return <ErrorState error={error} headerTitle={t('facilityLetterhead', 'Facility letterhead')} />;
|
|
102
|
+
}
|
|
103
|
+
return (
|
|
104
|
+
<Layer>
|
|
105
|
+
<DataTable rows={paginatedRows} headers={headers} size={responsiveSize} useZebraStyles>
|
|
106
|
+
{({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => (
|
|
107
|
+
<TableContainer>
|
|
108
|
+
<Table {...getTableProps()} aria-label={t('facilityLetterheadTable', 'Facility letterhead table')}>
|
|
109
|
+
<TableHead>
|
|
110
|
+
<TableRow>
|
|
111
|
+
{headers.map((header) => (
|
|
112
|
+
<TableHeader key={header.key} {...getHeaderProps({ header })}>
|
|
113
|
+
{header.header}
|
|
114
|
+
</TableHeader>
|
|
115
|
+
))}
|
|
116
|
+
</TableRow>
|
|
117
|
+
</TableHead>
|
|
118
|
+
<TableBody>
|
|
119
|
+
{rows.map((row) => (
|
|
120
|
+
<TableRow key={row.id} {...getRowProps({ row })}>
|
|
121
|
+
{row.cells.map((cell) => (
|
|
122
|
+
<TableCell key={cell.id}>{cell.value}</TableCell>
|
|
123
|
+
))}
|
|
124
|
+
</TableRow>
|
|
125
|
+
))}
|
|
126
|
+
</TableBody>
|
|
127
|
+
</Table>
|
|
128
|
+
{!hasBranding ? (
|
|
129
|
+
<div className={styles.tileContainer}>
|
|
130
|
+
<Tile className={styles.tile}>
|
|
131
|
+
<div className={styles.tileContent}>
|
|
132
|
+
<p className={styles.content}>{t('noLetterhead', 'No Facility letterhead configured yet')}</p>
|
|
133
|
+
<p className={styles.emptyStateHelperText}>
|
|
134
|
+
{t(
|
|
135
|
+
'noLetterheadHelp',
|
|
136
|
+
'Add the facility name, contacts and logo to brand generated documents.',
|
|
137
|
+
)}
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
</Tile>
|
|
141
|
+
</div>
|
|
142
|
+
) : null}
|
|
143
|
+
{hasBranding && (
|
|
144
|
+
<Pagination
|
|
145
|
+
forwardText={t('nextPage', 'Next page')}
|
|
146
|
+
backwardText={t('previousPage', 'Previous page')}
|
|
147
|
+
page={currentPage}
|
|
148
|
+
pageSize={currentPageSize}
|
|
149
|
+
pageSizes={pageSizes}
|
|
150
|
+
totalItems={tableRows.length}
|
|
151
|
+
className={styles.pagination}
|
|
152
|
+
onChange={({ pageSize, page }) => {
|
|
153
|
+
if (pageSize !== currentPageSize) {
|
|
154
|
+
setCurrentPageSize(pageSize);
|
|
155
|
+
setCurrentPage(1);
|
|
156
|
+
}
|
|
157
|
+
if (page !== currentPage) {
|
|
158
|
+
setCurrentPage(page);
|
|
159
|
+
}
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
</TableContainer>
|
|
164
|
+
)}
|
|
165
|
+
</DataTable>
|
|
166
|
+
</Layer>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<>
|
|
172
|
+
<CustomPageHeader subTitle={t('facilityBranding', 'Facility branding')} iconSrc={documentBrandingIcon?.src} />
|
|
173
|
+
<div className={styles.section}>
|
|
174
|
+
<CardHeader title={t('facilityLetterhead', 'Facility letterhead')}>
|
|
175
|
+
<Button kind="ghost" renderIcon={(props) => <Edit size={16} {...props} />} onClick={handleConfigure}>
|
|
176
|
+
{hasBranding ? t('editLetterhead', 'Edit letterhead') : t('configureLetterhead', 'Configure letterhead')}
|
|
177
|
+
</Button>
|
|
178
|
+
</CardHeader>
|
|
179
|
+
{renderBody()}
|
|
180
|
+
</div>
|
|
181
|
+
</>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export default DocumentBrandingDashboard;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
|
|
4
|
+
/** The global property the print module reads for the document letterhead. */
|
|
5
|
+
export const facilityInfoGp = 'digihmisprint.facilityInformation';
|
|
6
|
+
|
|
7
|
+
/** SWR key for the stored letterhead — exported so the workspace can revalidate the dashboard after saving. */
|
|
8
|
+
export const facilityInfoUrl = `${restBaseUrl}/systemsetting/${facilityInfoGp}?v=custom:(uuid,value)`;
|
|
9
|
+
|
|
10
|
+
export interface FacilityInfo {
|
|
11
|
+
facilityName?: string;
|
|
12
|
+
facilityTagline?: string;
|
|
13
|
+
phone?: string;
|
|
14
|
+
email?: string;
|
|
15
|
+
website?: string;
|
|
16
|
+
address?: string;
|
|
17
|
+
/** Logo as a base64 / data-URI string (from the upload). */
|
|
18
|
+
logoData?: string;
|
|
19
|
+
/** Or a server file path, as an alternative to an uploaded logo. */
|
|
20
|
+
logoPath?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The stored letterhead (parsed from the GP JSON) plus the setting uuid needed to save it. */
|
|
24
|
+
export const useFacilityInfo = () => {
|
|
25
|
+
const { data, error, isLoading, mutate } = useSWR<{ data: { uuid: string; value?: string } }>(
|
|
26
|
+
facilityInfoUrl,
|
|
27
|
+
openmrsFetch,
|
|
28
|
+
);
|
|
29
|
+
let info: FacilityInfo = {};
|
|
30
|
+
const raw = data?.data?.value;
|
|
31
|
+
if (raw) {
|
|
32
|
+
try {
|
|
33
|
+
info = JSON.parse(raw);
|
|
34
|
+
} catch {
|
|
35
|
+
info = {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { info, settingUuid: data?.data?.uuid, isLoading, error, mutate };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Save the letterhead back to the global property as JSON (dropping blank fields). */
|
|
42
|
+
export const saveFacilityInfo = (settingUuid: string, info: FacilityInfo) => {
|
|
43
|
+
const cleaned: Record<string, string> = {};
|
|
44
|
+
Object.entries(info).forEach(([key, value]) => {
|
|
45
|
+
if (value != null && String(value).trim() !== '') {
|
|
46
|
+
cleaned[key] = String(value).trim();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return openmrsFetch(`${restBaseUrl}/systemsetting/${settingUuid}`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: { value: JSON.stringify(cleaned) },
|
|
53
|
+
});
|
|
54
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.dashboardContainer {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.section {
|
|
11
|
+
margin-bottom: layout.$spacing-07;
|
|
12
|
+
padding: layout.$spacing-05;
|
|
13
|
+
background-color: colors.$gray-10;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
&:last-child {
|
|
17
|
+
margin-bottom: 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.subhead {
|
|
22
|
+
@include type.type-style('body-01');
|
|
23
|
+
color: colors.$gray-70;
|
|
24
|
+
margin-bottom: layout.$spacing-05;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.logoPreview {
|
|
28
|
+
display: block;
|
|
29
|
+
max-height: 4rem;
|
|
30
|
+
max-width: 12rem;
|
|
31
|
+
margin-bottom: layout.$spacing-05;
|
|
32
|
+
object-fit: contain;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tileContainer {
|
|
36
|
+
border-top: 1px solid colors.$gray-20;
|
|
37
|
+
padding: 3rem 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.tile {
|
|
41
|
+
margin: auto;
|
|
42
|
+
width: fit-content;
|
|
43
|
+
background: colors.$gray-20;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.tileContent {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
align-items: center;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.content {
|
|
53
|
+
@include type.type-style('heading-compact-02');
|
|
54
|
+
color: colors.$gray-70;
|
|
55
|
+
margin-bottom: layout.$spacing-03;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.emptyStateHelperText {
|
|
59
|
+
@include type.type-style('body-compact-01');
|
|
60
|
+
color: colors.$gray-70;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.pagination {
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
|
|
66
|
+
&:global(.cds--pagination) {
|
|
67
|
+
border-top: none;
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createDashboardLink } from './left-panel/left-panel.component';
|
|
|
4
4
|
import {
|
|
5
5
|
bedManagementDashboardMeta,
|
|
6
6
|
billingManagementDashboardMeta,
|
|
7
|
+
documentBrandingDashboardMeta,
|
|
7
8
|
formBuilderDashboardMeta,
|
|
8
9
|
locationManagementDashboardMeta,
|
|
9
10
|
queueManagementDashboardMeta,
|
|
@@ -15,6 +16,7 @@ import BedManagementDashboard from './bed-management/bed-management.component';
|
|
|
15
16
|
import FormBuilderDashboard from './form-builder/form-builder.component';
|
|
16
17
|
import BillingManagementDashboard from './billing-management/billing-management.component';
|
|
17
18
|
import SystemInfoDashboard from './system-info/system-info.component';
|
|
19
|
+
import DocumentBrandingDashboard from './document-branding/document-branding.component';
|
|
18
20
|
import DeletePaymentModeModal from './billing-management/billing-payment-modes/billing-payment-modes-modal/billing-delete-payment-modes.modal';
|
|
19
21
|
import DeletePaymentPointModal from './billing-management/billing-payment-points/billing-payment-point-modal/billing-delete-payment-point.modal';
|
|
20
22
|
import DeleteBillableServiceModal from './billing-management/billing-charge-items/billing-charge-items-modal/billing-charge-items.modal';
|
|
@@ -35,6 +37,10 @@ export const bedLeftPanelLink = getSyncLifecycle(createDashboardLink(bedManageme
|
|
|
35
37
|
export const formBuilderLeftPanelLink = getSyncLifecycle(createDashboardLink(formBuilderDashboardMeta), options);
|
|
36
38
|
export const billingLeftPanelLink = getSyncLifecycle(createDashboardLink(billingManagementDashboardMeta), options);
|
|
37
39
|
export const systemInfoLeftPanelLink = getSyncLifecycle(createDashboardLink(systemInfoDashboardMeta), options);
|
|
40
|
+
export const documentBrandingLeftPanelLink = getSyncLifecycle(
|
|
41
|
+
createDashboardLink(documentBrandingDashboardMeta),
|
|
42
|
+
options,
|
|
43
|
+
);
|
|
38
44
|
|
|
39
45
|
export const queueManagementDashboard = getSyncLifecycle(QueueManagementDashboard, options);
|
|
40
46
|
export const locationManagementDashboard = getSyncLifecycle(LocationManagementDashboard, options);
|
|
@@ -42,6 +48,7 @@ export const bedManagementDashboard = getSyncLifecycle(BedManagementDashboard, o
|
|
|
42
48
|
export const formBuilderDashboard = getSyncLifecycle(FormBuilderDashboard, options);
|
|
43
49
|
export const billingManagementDashboard = getSyncLifecycle(BillingManagementDashboard, options);
|
|
44
50
|
export const systemInfoDashboard = getSyncLifecycle(SystemInfoDashboard, options);
|
|
51
|
+
export const documentBrandingDashboard = getSyncLifecycle(DocumentBrandingDashboard, options);
|
|
45
52
|
|
|
46
53
|
// t('addNewQueueService', 'Add New Queue Service')
|
|
47
54
|
export const addNewQueueServiceWorkspace = getAsyncLifecycle(
|
|
@@ -103,22 +110,21 @@ export const paymentPointWorkspace = getAsyncLifecycle(
|
|
|
103
110
|
},
|
|
104
111
|
);
|
|
105
112
|
|
|
106
|
-
// t('
|
|
107
|
-
export const
|
|
113
|
+
// t('addServiceItem', 'Add Charge Service')
|
|
114
|
+
export const billableServiceForm = getAsyncLifecycle(
|
|
108
115
|
() =>
|
|
109
|
-
import('./billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-
|
|
116
|
+
import('./billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-service.workspace'),
|
|
110
117
|
{
|
|
111
|
-
featureName: 'billing-charge-
|
|
118
|
+
featureName: 'billing-charge-service-workspace',
|
|
112
119
|
moduleName,
|
|
113
120
|
},
|
|
114
121
|
);
|
|
115
122
|
|
|
116
|
-
// t('
|
|
117
|
-
export const
|
|
118
|
-
() =>
|
|
119
|
-
import('./billing-management/billing-charge-items/billing-charge-items-workspace/billing-charge-service.workspace'),
|
|
123
|
+
// t('configureLetterhead', 'Configure letterhead')
|
|
124
|
+
export const documentBrandingWorkspace = getAsyncLifecycle(
|
|
125
|
+
() => import('./document-branding/document-branding-workspace/document-branding.workspace'),
|
|
120
126
|
{
|
|
121
|
-
featureName: '
|
|
127
|
+
featureName: 'document-branding-workspace',
|
|
122
128
|
moduleName,
|
|
123
129
|
},
|
|
124
130
|
);
|
package/src/routes.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.openmrs.org/routes.schema.json",
|
|
3
3
|
"backendDependencies": {
|
|
4
4
|
"fhir2": ">=1.2",
|
|
5
|
-
"webservices.rest": "
|
|
5
|
+
"webservices.rest": ">=2.24.0"
|
|
6
6
|
},
|
|
7
7
|
"extensions": [
|
|
8
8
|
{
|
|
@@ -65,6 +65,16 @@
|
|
|
65
65
|
"title": "Billing Management"
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
"name": "document-branding-dashboard-link",
|
|
70
|
+
"slot": "administration-dashboard-slot",
|
|
71
|
+
"component": "documentBrandingLeftPanelLink",
|
|
72
|
+
"meta": {
|
|
73
|
+
"name": "document-branding",
|
|
74
|
+
"slot": "document-branding-homepage-dashboard-slot",
|
|
75
|
+
"title": "Document Branding"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
68
78
|
{
|
|
69
79
|
"name": "queue-management-dashboard-content",
|
|
70
80
|
"slot": "queue-management-homepage-dashboard-slot",
|
|
@@ -94,10 +104,15 @@
|
|
|
94
104
|
"name": "system-info-management-dashboard-content",
|
|
95
105
|
"slot": "system-info-management-homepage-dashboard-slot",
|
|
96
106
|
"component": "systemInfoDashboard"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "document-branding-dashboard-content",
|
|
110
|
+
"slot": "document-branding-homepage-dashboard-slot",
|
|
111
|
+
"component": "documentBrandingDashboard"
|
|
97
112
|
}
|
|
98
113
|
],
|
|
99
114
|
"workspaces": [
|
|
100
|
-
|
|
115
|
+
{
|
|
101
116
|
"name": "service-queues-service-form",
|
|
102
117
|
"title": "addNewQueueService",
|
|
103
118
|
"component": "addNewQueueServiceWorkspace",
|
|
@@ -108,36 +123,42 @@
|
|
|
108
123
|
"title": "addNewQueueServiceRoom",
|
|
109
124
|
"component": "addNewQueueServiceRoomWorkspace",
|
|
110
125
|
"type": "service-queues"
|
|
111
|
-
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
112
128
|
"name": "add-location-workspace",
|
|
113
129
|
"title": "addNewLocationWorkspace",
|
|
114
130
|
"component": "addNewLocationWorkspace",
|
|
115
131
|
"type": "location-management"
|
|
116
|
-
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
117
134
|
"name": "search-location-workspace",
|
|
118
135
|
"title": "searchLocationWorkspace",
|
|
119
136
|
"component": "searchLocationWorkspace",
|
|
120
137
|
"type": "location-management"
|
|
121
|
-
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
122
140
|
"name": "payment-mode-workspace",
|
|
123
141
|
"title": "paymentModeWorkspace",
|
|
124
142
|
"component": "paymentModeWorkspace",
|
|
125
143
|
"type": "billing-management"
|
|
126
|
-
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
127
146
|
"name": "payment-point-workspace",
|
|
128
147
|
"title": "paymentPointWorkspace",
|
|
129
148
|
"component": "paymentPointWorkspace",
|
|
130
149
|
"type": "billing-management"
|
|
131
|
-
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
132
152
|
"name": "billing-charge-service-workspace",
|
|
133
153
|
"title": "addServiceItem",
|
|
134
154
|
"component": "billableServiceForm",
|
|
135
155
|
"type": "billing-management"
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "document-branding-workspace",
|
|
159
|
+
"title": "configureLetterhead",
|
|
160
|
+
"component": "documentBrandingWorkspace",
|
|
161
|
+
"type": "document-branding"
|
|
141
162
|
}
|
|
142
163
|
],
|
|
143
164
|
"modals": [
|
|
@@ -154,4 +175,4 @@
|
|
|
154
175
|
"component": "deleteBillableService"
|
|
155
176
|
}
|
|
156
177
|
]
|
|
157
|
-
}
|
|
178
|
+
}
|