@kenyaemr/esm-admin-app 5.4.4-pre.297 → 5.4.4-pre.3
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 +1 -1
- package/dist/100.js +1 -0
- package/dist/100.js.map +1 -0
- package/dist/1442.js +1 -0
- package/dist/1442.js.map +1 -0
- package/dist/1462.js +1 -1
- package/dist/1972.js +1 -1
- package/dist/2016.js +1 -1
- package/dist/2080.js +1 -1
- package/dist/2080.js.map +1 -1
- package/dist/2096.js +1 -1
- package/dist/2096.js.map +1 -1
- package/dist/2270.js +1 -1
- package/dist/2270.js.map +1 -1
- package/dist/2294.js +1 -1
- package/dist/2294.js.map +1 -1
- package/dist/2467.js +1 -0
- package/dist/2467.js.map +1 -0
- package/dist/257.js +1 -1
- package/dist/257.js.map +1 -1
- package/dist/2948.js +1 -1
- package/dist/3380.js +1 -0
- package/dist/3380.js.map +1 -0
- package/dist/3548.js +1 -1
- package/dist/3816.js +1 -1
- package/dist/4337.js +1 -1
- package/dist/4687.js +1 -0
- package/dist/4687.js.map +1 -0
- package/dist/4858.js +1 -1
- package/dist/487.js +1 -1
- package/dist/5297.js +1 -0
- package/dist/5297.js.map +1 -0
- package/dist/5910.js +1 -0
- package/dist/5910.js.map +1 -0
- package/dist/5940.js +1 -1
- package/dist/5940.js.map +1 -1
- package/dist/6253.js +1 -0
- package/dist/6253.js.map +1 -0
- package/dist/6455.js +1 -0
- package/dist/6455.js.map +1 -0
- package/dist/6456.js +1 -1
- package/dist/6925.js +1 -0
- package/dist/6925.js.map +1 -0
- package/dist/7607.js +1 -1
- package/dist/7737.js +1 -1
- package/dist/7739.js +1 -1
- package/dist/7866.js +1 -1
- package/dist/7866.js.map +1 -1
- package/dist/8206.js +1 -0
- package/dist/8206.js.map +1 -0
- package/dist/8376.js +1 -1
- package/dist/846.js +17 -0
- package/dist/846.js.map +1 -0
- package/dist/{4047.js → 8487.js} +1 -1
- package/dist/8487.js.map +1 -0
- package/dist/8528.js +1 -0
- package/dist/8528.js.map +1 -0
- package/dist/8727.js +1 -1
- package/dist/9182.js +1 -1
- package/dist/9446.js +1 -1
- package/dist/9585.js +1 -0
- package/dist/9585.js.map +1 -0
- package/dist/9647.js +1 -0
- package/dist/9647.js.map +1 -0
- package/dist/kenyaemr-esm-admin-app.js +3 -3
- package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +329 -306
- package/dist/kenyaemr-esm-admin-app.js.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/components/confirm-modal/confirmation-operation.test.tsx +8 -19
- package/src/components/dashboard/dashboard.component.tsx +4 -1
- package/src/components/empty-state/empty-state-log.test.tsx +3 -4
- package/src/components/global-property/dashboard/global-property-dashboard.component.tsx +23 -0
- package/src/components/global-property/dashboard/global-property-dashboard.scss +6 -0
- package/src/components/global-property/hooks/useGlobalProperty.ts +64 -0
- package/src/components/global-property/index.ts +14 -0
- package/src/components/global-property/modal/delete-global-property-modal.component.tsx +71 -0
- package/src/components/global-property/modal/delete-global-property-modal.test.tsx +131 -0
- package/src/components/global-property/table/global-property-table.component.tsx +249 -0
- package/src/components/global-property/table/global-property-table.scss +34 -0
- package/src/components/global-property/table/global-property-table.test.tsx +198 -0
- package/src/components/global-property/workspace/global-property-form-schema.ts +32 -0
- package/src/components/global-property/workspace/global-property.workspace.scss +40 -0
- package/src/components/global-property/workspace/global-property.workspace.test.tsx +172 -0
- package/src/components/global-property/workspace/global-property.workspace.tsx +260 -0
- package/src/components/logs-table/operation-log-table.component.tsx +87 -75
- package/src/components/logs-table/operation-log.test.tsx +134 -28
- package/src/index.ts +9 -1
- package/src/root.component.tsx +2 -0
- package/src/routes.json +20 -1
- package/src/types/index.ts +11 -2
- package/translations/am.json +39 -2
- package/translations/en.json +62 -24
- package/translations/fr.json +167 -128
- package/translations/sw.json +311 -275
- package/dist/12.js +0 -17
- package/dist/12.js.map +0 -1
- package/dist/1201.js +0 -1
- package/dist/1201.js.map +0 -1
- package/dist/2402.js +0 -1
- package/dist/2402.js.map +0 -1
- package/dist/3366.js +0 -1
- package/dist/3366.js.map +0 -1
- package/dist/3852.js +0 -1
- package/dist/3852.js.map +0 -1
- package/dist/4047.js.map +0 -1
- package/dist/552.js +0 -1
- package/dist/552.js.map +0 -1
- package/dist/6092.js +0 -1
- package/dist/6092.js.map +0 -1
- package/dist/6399.js +0 -1
- package/dist/6399.js.map +0 -1
- package/dist/6676.js +0 -1
- package/dist/6676.js.map +0 -1
- package/dist/6976.js +0 -1
- package/dist/6976.js.map +0 -1
- package/dist/7916.js +0 -1
- package/dist/7916.js.map +0 -1
- package/dist/9406.js +0 -1
- package/dist/9406.js.map +0 -1
- package/dist/9711.js +0 -1
- package/dist/9711.js.map +0 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ResponsiveWrapper,
|
|
4
|
+
Workspace2,
|
|
5
|
+
showSnackbar,
|
|
6
|
+
useLayoutType,
|
|
7
|
+
type Workspace2DefinitionProps,
|
|
8
|
+
} from '@openmrs/esm-framework';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
10
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
11
|
+
import { Button, ButtonSet, ComboBox, Form, FormGroup, InlineLoading, Stack, TextArea, TextInput } from '@carbon/react';
|
|
12
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
13
|
+
import classNames from 'classnames';
|
|
14
|
+
|
|
15
|
+
import styles from './global-property.workspace.scss';
|
|
16
|
+
import { type SystemSettings } from '../../../types';
|
|
17
|
+
import { saveOrUpdateGlobalProperty } from '../hooks/useGlobalProperty';
|
|
18
|
+
import {
|
|
19
|
+
createGlobalPropertyFormSchema,
|
|
20
|
+
openmrsCustomDatatypes,
|
|
21
|
+
type GlobalPropertyFormType,
|
|
22
|
+
} from './global-property-form-schema';
|
|
23
|
+
|
|
24
|
+
type GlobalPropertyWorkspaceProps = {
|
|
25
|
+
systemSetting?: SystemSettings | null;
|
|
26
|
+
mutateGlobalProperty: () => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const GlobalPropertyWorkspace: React.FC<Workspace2DefinitionProps<GlobalPropertyWorkspaceProps, {}, {}>> = ({
|
|
30
|
+
closeWorkspace,
|
|
31
|
+
workspaceProps: { systemSetting, mutateGlobalProperty },
|
|
32
|
+
}) => {
|
|
33
|
+
const { t } = useTranslation();
|
|
34
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
35
|
+
const isEditMode = Boolean(systemSetting?.uuid);
|
|
36
|
+
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
handleSubmit,
|
|
40
|
+
control,
|
|
41
|
+
formState: { isSubmitting, isDirty, errors },
|
|
42
|
+
} = useForm<GlobalPropertyFormType>({
|
|
43
|
+
resolver: zodResolver(createGlobalPropertyFormSchema(t)),
|
|
44
|
+
defaultValues: {
|
|
45
|
+
property: systemSetting?.property ?? '',
|
|
46
|
+
description: systemSetting?.description ?? '',
|
|
47
|
+
datatypeClassname: systemSetting?.datatypeClassname ?? '',
|
|
48
|
+
datatypeConfig: systemSetting?.datatypeConfig ?? '',
|
|
49
|
+
preferredHandlerClassname: systemSetting?.preferredHandlerClassname ?? '',
|
|
50
|
+
handlerConfig: systemSetting?.handlerConfig ?? '',
|
|
51
|
+
value: systemSetting?.value ?? '',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
setHasUnsavedChanges(isDirty);
|
|
57
|
+
}, [isDirty]);
|
|
58
|
+
|
|
59
|
+
const onSubmit = async (data: GlobalPropertyFormType) => {
|
|
60
|
+
const payload = {
|
|
61
|
+
property: data.property,
|
|
62
|
+
value: data.value,
|
|
63
|
+
description: data.description,
|
|
64
|
+
datatypeClassname: data.datatypeClassname,
|
|
65
|
+
datatypeConfig: data.datatypeConfig,
|
|
66
|
+
preferredHandlerClassname: data.preferredHandlerClassname,
|
|
67
|
+
handlerConfig: data.handlerConfig,
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
await saveOrUpdateGlobalProperty(payload, systemSetting?.uuid);
|
|
71
|
+
|
|
72
|
+
showSnackbar({
|
|
73
|
+
title: t('success', 'Success'),
|
|
74
|
+
kind: 'success',
|
|
75
|
+
subtitle: isEditMode
|
|
76
|
+
? t('gpUpdated', 'Global property {{property}} was updated successfully.', { property: data.property })
|
|
77
|
+
: t('gpCreated', 'Global property {{property}} was created successfully.', { property: data.property }),
|
|
78
|
+
});
|
|
79
|
+
mutateGlobalProperty();
|
|
80
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
81
|
+
} catch (error: any) {
|
|
82
|
+
showSnackbar({
|
|
83
|
+
title: t('error', 'Error'),
|
|
84
|
+
kind: 'error',
|
|
85
|
+
subtitle: error?.message ?? t('gpSaveError', 'Error saving global property'),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const title = isEditMode
|
|
91
|
+
? t('editGlobalProperty', 'Edit global property')
|
|
92
|
+
: t('addGlobalProperty', 'Add global property');
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Workspace2 title={title} hasUnsavedChanges={hasUnsavedChanges}>
|
|
96
|
+
<Form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
|
|
97
|
+
<div className={styles.formContainer}>
|
|
98
|
+
<Stack gap={4}>
|
|
99
|
+
<ResponsiveWrapper>
|
|
100
|
+
<FormGroup legendText="">
|
|
101
|
+
<Controller
|
|
102
|
+
control={control}
|
|
103
|
+
name="property"
|
|
104
|
+
render={({ field }) => (
|
|
105
|
+
<TextInput
|
|
106
|
+
id="gpProperty"
|
|
107
|
+
labelText={t('property', 'Property (required)')}
|
|
108
|
+
placeholder={t('gpPropertyPlaceholder', 'e.g. setting.name')}
|
|
109
|
+
value={field.value}
|
|
110
|
+
onChange={field.onChange}
|
|
111
|
+
invalid={!!errors.property}
|
|
112
|
+
invalidText={errors.property?.message}
|
|
113
|
+
disabled={isEditMode}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
117
|
+
</FormGroup>
|
|
118
|
+
</ResponsiveWrapper>
|
|
119
|
+
|
|
120
|
+
<ResponsiveWrapper>
|
|
121
|
+
<FormGroup legendText="">
|
|
122
|
+
<Controller
|
|
123
|
+
control={control}
|
|
124
|
+
name="value"
|
|
125
|
+
render={({ field }) => (
|
|
126
|
+
<TextArea
|
|
127
|
+
id="gpValue"
|
|
128
|
+
labelText={t('value', 'Value (required)')}
|
|
129
|
+
placeholder={t('gpValuePlaceholder', 'Enter value')}
|
|
130
|
+
value={field.value}
|
|
131
|
+
onChange={field.onChange}
|
|
132
|
+
invalid={!!errors.value}
|
|
133
|
+
invalidText={errors.value?.message}
|
|
134
|
+
rows={3}
|
|
135
|
+
/>
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
138
|
+
</FormGroup>
|
|
139
|
+
</ResponsiveWrapper>
|
|
140
|
+
|
|
141
|
+
<ResponsiveWrapper>
|
|
142
|
+
<FormGroup legendText="">
|
|
143
|
+
<Controller
|
|
144
|
+
control={control}
|
|
145
|
+
name="description"
|
|
146
|
+
render={({ field }) => (
|
|
147
|
+
<TextArea
|
|
148
|
+
id="gpDescription"
|
|
149
|
+
labelText={t('description', 'Description')}
|
|
150
|
+
placeholder={t('gpDescriptionPlaceholder', 'Optional description')}
|
|
151
|
+
value={field.value ?? ''}
|
|
152
|
+
onChange={field.onChange}
|
|
153
|
+
rows={2}
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
156
|
+
/>
|
|
157
|
+
</FormGroup>
|
|
158
|
+
</ResponsiveWrapper>
|
|
159
|
+
|
|
160
|
+
<ResponsiveWrapper>
|
|
161
|
+
<FormGroup legendText="">
|
|
162
|
+
<Controller
|
|
163
|
+
control={control}
|
|
164
|
+
name="datatypeClassname"
|
|
165
|
+
render={({ field }) => (
|
|
166
|
+
<ComboBox
|
|
167
|
+
id="datatypeClassname"
|
|
168
|
+
invalidText={errors.datatypeClassname?.message}
|
|
169
|
+
items={openmrsCustomDatatypes}
|
|
170
|
+
onChange={function Zye() {}}
|
|
171
|
+
titleText={t('datatypeClassname', 'Datatype classname')}
|
|
172
|
+
typeahead
|
|
173
|
+
placeholder={t('selectDatatypeClassname', 'Select datatype classname')}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
/>
|
|
177
|
+
</FormGroup>
|
|
178
|
+
</ResponsiveWrapper>
|
|
179
|
+
|
|
180
|
+
<ResponsiveWrapper>
|
|
181
|
+
<FormGroup legendText="">
|
|
182
|
+
<Controller
|
|
183
|
+
control={control}
|
|
184
|
+
name="datatypeConfig"
|
|
185
|
+
render={({ field }) => (
|
|
186
|
+
<TextInput
|
|
187
|
+
id="gpDatatypeConfig"
|
|
188
|
+
labelText={t('datatypeConfig', 'Datatype config')}
|
|
189
|
+
placeholder={t('gpDatatypeConfigPlaceholder', 'Optional datatype configuration')}
|
|
190
|
+
value={field.value ?? ''}
|
|
191
|
+
onChange={field.onChange}
|
|
192
|
+
/>
|
|
193
|
+
)}
|
|
194
|
+
/>
|
|
195
|
+
</FormGroup>
|
|
196
|
+
</ResponsiveWrapper>
|
|
197
|
+
|
|
198
|
+
<ResponsiveWrapper>
|
|
199
|
+
<FormGroup legendText="">
|
|
200
|
+
<Controller
|
|
201
|
+
control={control}
|
|
202
|
+
name="preferredHandlerClassname"
|
|
203
|
+
render={({ field }) => (
|
|
204
|
+
<TextInput
|
|
205
|
+
id="gpPreferredHandlerClassname"
|
|
206
|
+
labelText={t('preferredHandlerClassname', 'Preferred handler classname')}
|
|
207
|
+
placeholder={t('gpPreferredHandlerPlaceholder', 'Optional preferred handler classname')}
|
|
208
|
+
value={field.value ?? ''}
|
|
209
|
+
onChange={field.onChange}
|
|
210
|
+
/>
|
|
211
|
+
)}
|
|
212
|
+
/>
|
|
213
|
+
</FormGroup>
|
|
214
|
+
</ResponsiveWrapper>
|
|
215
|
+
|
|
216
|
+
<ResponsiveWrapper>
|
|
217
|
+
<FormGroup legendText="">
|
|
218
|
+
<Controller
|
|
219
|
+
control={control}
|
|
220
|
+
name="handlerConfig"
|
|
221
|
+
render={({ field }) => (
|
|
222
|
+
<TextInput
|
|
223
|
+
id="gpHandlerConfig"
|
|
224
|
+
labelText={t('handlerConfig', 'Handler config')}
|
|
225
|
+
placeholder={t('gpHandlerConfigPlaceholder', 'Optional handler configuration')}
|
|
226
|
+
value={field.value ?? ''}
|
|
227
|
+
onChange={field.onChange}
|
|
228
|
+
/>
|
|
229
|
+
)}
|
|
230
|
+
/>
|
|
231
|
+
</FormGroup>
|
|
232
|
+
</ResponsiveWrapper>
|
|
233
|
+
</Stack>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<ButtonSet
|
|
237
|
+
className={classNames({
|
|
238
|
+
[styles.tablet]: isTablet,
|
|
239
|
+
[styles.desktop]: !isTablet,
|
|
240
|
+
})}>
|
|
241
|
+
<Button className={styles.buttonContainer} kind="secondary" onClick={() => closeWorkspace()}>
|
|
242
|
+
{t('cancel', 'Cancel')}
|
|
243
|
+
</Button>
|
|
244
|
+
<Button className={styles.buttonContainer} disabled={isSubmitting || !isDirty} kind="primary" type="submit">
|
|
245
|
+
{isSubmitting ? (
|
|
246
|
+
<span className={styles.inlineLoading}>
|
|
247
|
+
{t('submitting', 'Submitting...')}
|
|
248
|
+
<InlineLoading status="active" iconDescription="Loading" />
|
|
249
|
+
</span>
|
|
250
|
+
) : (
|
|
251
|
+
t('saveAndClose', 'Save & close')
|
|
252
|
+
)}
|
|
253
|
+
</Button>
|
|
254
|
+
</ButtonSet>
|
|
255
|
+
</Form>
|
|
256
|
+
</Workspace2>
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export default GlobalPropertyWorkspace;
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
TableRow,
|
|
13
13
|
Tag,
|
|
14
14
|
DataTableSkeleton,
|
|
15
|
-
InlineLoading,
|
|
16
15
|
} from '@carbon/react';
|
|
17
16
|
import { CardHeader, PatientChartPagination } from '@openmrs/esm-patient-common-lib';
|
|
18
17
|
import { useLayoutType, usePagination, formatDate } from '@openmrs/esm-framework';
|
|
@@ -21,13 +20,14 @@ import { ETLResponse } from '../../types';
|
|
|
21
20
|
import EmptyState from '../empty-state/empty-state-log.components';
|
|
22
21
|
|
|
23
22
|
interface LogTableProps {
|
|
24
|
-
logData: ETLResponse
|
|
23
|
+
logData: Array<ETLResponse>;
|
|
25
24
|
isLoading: boolean;
|
|
26
25
|
}
|
|
27
26
|
|
|
27
|
+
const PAGE_SIZE = 10;
|
|
28
|
+
|
|
28
29
|
const LogTable: React.FC<LogTableProps> = ({ logData, isLoading }) => {
|
|
29
30
|
const { t } = useTranslation();
|
|
30
|
-
const pageSize = 10;
|
|
31
31
|
const isTablet = useLayoutType() === 'tablet';
|
|
32
32
|
|
|
33
33
|
const headers = [
|
|
@@ -37,82 +37,94 @@ const LogTable: React.FC<LogTableProps> = ({ logData, isLoading }) => {
|
|
|
37
37
|
{ header: t('completionStatus', 'Completion status'), key: 'status' },
|
|
38
38
|
];
|
|
39
39
|
|
|
40
|
-
const rows =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
const rows =
|
|
41
|
+
logData?.map((item, index) => ({
|
|
42
|
+
id: index.toString(),
|
|
43
|
+
script_name: item.script_name,
|
|
44
|
+
start_time: item?.start_time ? formatDate(new Date(item.start_time)) : '--',
|
|
45
|
+
stop_time: item?.stop_time ? formatDate(new Date(item.stop_time)) : '--',
|
|
46
|
+
status: item.status,
|
|
47
|
+
})) ?? [];
|
|
48
|
+
|
|
49
|
+
const { results: paginatedData, currentPage, goTo } = usePagination(rows, PAGE_SIZE);
|
|
50
|
+
|
|
51
|
+
const renderTable = ({ rows, headers, getHeaderProps, getTableProps }: any) => (
|
|
52
|
+
<TableContainer className={styles.tableContainer}>
|
|
53
|
+
<Table {...getTableProps()}>
|
|
54
|
+
<TableHead>
|
|
55
|
+
<TableRow>
|
|
56
|
+
{headers.map((header: any) => (
|
|
57
|
+
<TableHeader
|
|
58
|
+
className={classNames(styles.productiveHeading01, styles.text02)}
|
|
59
|
+
{...getHeaderProps({ header })}>
|
|
60
|
+
{header.header}
|
|
61
|
+
</TableHeader>
|
|
62
|
+
))}
|
|
63
|
+
</TableRow>
|
|
64
|
+
</TableHead>
|
|
65
|
+
<TableBody>
|
|
66
|
+
{rows.map((row: any) => (
|
|
67
|
+
<TableRow key={row.id}>
|
|
68
|
+
{row.cells.map((cell: any) => (
|
|
69
|
+
<TableCell key={cell.id}>
|
|
70
|
+
{cell.info.header === 'status' ? (
|
|
71
|
+
<Tag size="md" type={cell.value === 'Success' ? 'green' : 'red'}>
|
|
72
|
+
{cell.value}
|
|
73
|
+
</Tag>
|
|
74
|
+
) : (
|
|
75
|
+
cell.value
|
|
76
|
+
)}
|
|
77
|
+
</TableCell>
|
|
78
|
+
))}
|
|
79
|
+
</TableRow>
|
|
80
|
+
))}
|
|
81
|
+
</TableBody>
|
|
82
|
+
</Table>
|
|
83
|
+
</TableContainer>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const renderContent = () => {
|
|
87
|
+
if (isLoading && logData.length === 0) {
|
|
88
|
+
return (
|
|
89
|
+
<DataTableSkeleton
|
|
90
|
+
headers={headers}
|
|
91
|
+
aria-label="etl table"
|
|
92
|
+
showToolbar={false}
|
|
93
|
+
showHeader={false}
|
|
94
|
+
rowCount={4}
|
|
95
|
+
zebra
|
|
96
|
+
columnCount={3}
|
|
97
|
+
className={styles.dataTableSkeleton}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (logData.length === 0) {
|
|
103
|
+
return <EmptyState subTitle={t('noRecordsFound', 'No ETL Operation logs found')} />;
|
|
104
|
+
}
|
|
47
105
|
|
|
48
|
-
|
|
106
|
+
return (
|
|
107
|
+
<>
|
|
108
|
+
<DataTable rows={paginatedData} headers={headers} isSortable size={isTablet ? 'lg' : 'sm'} useZebraStyles>
|
|
109
|
+
{renderTable}
|
|
110
|
+
</DataTable>
|
|
111
|
+
<PatientChartPagination
|
|
112
|
+
currentItems={paginatedData.length}
|
|
113
|
+
totalItems={rows.length}
|
|
114
|
+
onPageNumberChange={({ page }: { page: number }) => goTo(page)}
|
|
115
|
+
pageNumber={currentPage}
|
|
116
|
+
pageSize={PAGE_SIZE}
|
|
117
|
+
/>
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
49
121
|
|
|
50
122
|
return (
|
|
51
123
|
<div className={styles.table}>
|
|
52
|
-
<CardHeader title={t('facilityInfo', 'Facility Info')}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
headers={headers}
|
|
57
|
-
aria-label="etl table"
|
|
58
|
-
showToolbar={false}
|
|
59
|
-
showHeader={false}
|
|
60
|
-
rowCount={4}
|
|
61
|
-
zebra
|
|
62
|
-
columnCount={3}
|
|
63
|
-
className={styles.dataTableSkeleton}
|
|
64
|
-
/>
|
|
65
|
-
) : logData.length === 0 ? (
|
|
66
|
-
<EmptyState subTitle={t('noRecordsFound', 'No ETL Operation logs found')} />
|
|
67
|
-
) : (
|
|
68
|
-
<>
|
|
69
|
-
<DataTable rows={paginatedData} headers={headers} isSortable size={isTablet ? 'lg' : 'sm'} useZebraStyles>
|
|
70
|
-
{({ rows, headers, getHeaderProps, getTableProps }) => (
|
|
71
|
-
<TableContainer className={styles.tableContainer}>
|
|
72
|
-
<Table {...getTableProps()}>
|
|
73
|
-
<TableHead>
|
|
74
|
-
<TableRow>
|
|
75
|
-
{headers.map((header) => (
|
|
76
|
-
<TableHeader
|
|
77
|
-
key={header.key}
|
|
78
|
-
className={classNames(styles.productiveHeading01, styles.text02)}
|
|
79
|
-
{...getHeaderProps({ header })}>
|
|
80
|
-
{header.header}
|
|
81
|
-
</TableHeader>
|
|
82
|
-
))}
|
|
83
|
-
</TableRow>
|
|
84
|
-
</TableHead>
|
|
85
|
-
<TableBody>
|
|
86
|
-
{rows.map((row) => (
|
|
87
|
-
<TableRow key={row.id}>
|
|
88
|
-
{row.cells.map((cell) => (
|
|
89
|
-
<TableCell key={cell.id}>
|
|
90
|
-
{cell.info.header === 'status' ? (
|
|
91
|
-
<Tag size="md" type={cell.value === 'Success' ? 'green' : 'red'}>
|
|
92
|
-
{cell.value}
|
|
93
|
-
</Tag>
|
|
94
|
-
) : (
|
|
95
|
-
cell.value
|
|
96
|
-
)}
|
|
97
|
-
</TableCell>
|
|
98
|
-
))}
|
|
99
|
-
</TableRow>
|
|
100
|
-
))}
|
|
101
|
-
</TableBody>
|
|
102
|
-
</Table>
|
|
103
|
-
</TableContainer>
|
|
104
|
-
)}
|
|
105
|
-
</DataTable>
|
|
106
|
-
<PatientChartPagination
|
|
107
|
-
currentItems={paginatedData.length}
|
|
108
|
-
totalItems={rows.length}
|
|
109
|
-
onPageNumberChange={({ page }) => goTo(page)}
|
|
110
|
-
pageNumber={currentPage}
|
|
111
|
-
pageSize={pageSize}
|
|
112
|
-
/>
|
|
113
|
-
</>
|
|
114
|
-
)}
|
|
115
|
-
</div>
|
|
124
|
+
<CardHeader title={t('facilityInfo', 'Facility Info')}>
|
|
125
|
+
<></>
|
|
126
|
+
</CardHeader>
|
|
127
|
+
<div className={styles.logTable}>{renderContent()}</div>
|
|
116
128
|
</div>
|
|
117
129
|
);
|
|
118
130
|
};
|
|
@@ -1,47 +1,153 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import '@testing-library/jest-dom';
|
|
4
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
4
5
|
import LogTable from './operation-log-table.component';
|
|
5
6
|
import { ETLResponse } from '../../types';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
vi.mock('react-i18next', () => ({
|
|
9
|
+
useTranslation: () => ({ t: (key: string, fallback: string) => fallback }),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock('@openmrs/esm-framework', () => ({
|
|
13
|
+
useLayoutType: vi.fn(() => 'desktop'),
|
|
14
|
+
usePagination: vi.fn((data) => ({
|
|
15
|
+
results: data,
|
|
16
|
+
currentPage: 1,
|
|
17
|
+
goTo: vi.fn(),
|
|
18
|
+
})),
|
|
19
|
+
formatDate: vi.fn((date: Date) => date.toISOString()),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
23
|
+
CardHeader: ({ title }: { title: string }) => <div>{title}</div>,
|
|
24
|
+
PatientChartPagination: ({ currentItems, totalItems }: { currentItems: number; totalItems: number }) => (
|
|
25
|
+
<div data-testid="pagination">
|
|
26
|
+
{currentItems}/{totalItems}
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock('../empty-state/empty-state-log.components', () => ({
|
|
32
|
+
default: ({ subTitle }: { subTitle: string }) => <div data-testid="empty-state">{subTitle}</div>,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock('@carbon/react', async (importOriginal) => {
|
|
36
|
+
const original = await importOriginal<typeof import('@carbon/react')>();
|
|
37
|
+
return {
|
|
38
|
+
...original,
|
|
39
|
+
DataTable: ({
|
|
40
|
+
children,
|
|
41
|
+
rows,
|
|
42
|
+
headers,
|
|
43
|
+
}: {
|
|
44
|
+
children: (props: any) => React.ReactNode;
|
|
45
|
+
rows: any[];
|
|
46
|
+
headers: any[];
|
|
47
|
+
}) => {
|
|
48
|
+
if (typeof children === 'function') {
|
|
49
|
+
const renderRows = rows.map((row) => ({
|
|
50
|
+
...row,
|
|
51
|
+
cells: headers.map((h) => ({
|
|
52
|
+
id: `${row.id}:${h.key}`,
|
|
53
|
+
value: row[h.key],
|
|
54
|
+
info: { header: h.key },
|
|
55
|
+
})),
|
|
56
|
+
}));
|
|
57
|
+
return children({
|
|
58
|
+
rows: renderRows,
|
|
59
|
+
headers,
|
|
60
|
+
getTableProps: () => ({}),
|
|
61
|
+
getHeaderProps: ({ header }: any) => ({ key: header.key }),
|
|
62
|
+
getRowProps: () => ({}),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return children;
|
|
20
66
|
},
|
|
21
|
-
|
|
67
|
+
};
|
|
68
|
+
});
|
|
22
69
|
|
|
23
|
-
|
|
24
|
-
render(<LogTable logData={mockLogData} isLoading={false} />);
|
|
70
|
+
vi.mock('classnames', () => ({ default: (...args: string[]) => args.filter(Boolean).join(' ') }));
|
|
25
71
|
|
|
26
|
-
|
|
27
|
-
headers.forEach((header) => {
|
|
28
|
-
expect(screen.getByText(header)).toBeInTheDocument();
|
|
29
|
-
});
|
|
72
|
+
vi.mock('./operation-log.scss', () => ({ default: {} }));
|
|
30
73
|
|
|
31
|
-
|
|
32
|
-
|
|
74
|
+
const mockLogData: ETLResponse[] = [
|
|
75
|
+
{
|
|
76
|
+
script_name: 'Script A',
|
|
77
|
+
start_time: '2024-01-01T08:00:00Z',
|
|
78
|
+
stop_time: '2024-01-01T09:00:00Z',
|
|
79
|
+
status: 'Success',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
script_name: 'Script B',
|
|
83
|
+
start_time: '2024-01-02T10:00:00Z',
|
|
84
|
+
stop_time: '2024-01-02T11:00:00Z',
|
|
85
|
+
status: 'Failed',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
describe('LogTable', () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
vi.clearAllMocks();
|
|
33
92
|
});
|
|
34
93
|
|
|
35
|
-
it('
|
|
36
|
-
render(<LogTable logData={
|
|
94
|
+
it('renders the card header', () => {
|
|
95
|
+
render(<LogTable logData={mockLogData} isLoading={false} />);
|
|
96
|
+
expect(screen.getByText('Facility Info')).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('shows skeleton while loading with no data', () => {
|
|
100
|
+
render(<LogTable logData={[]} isLoading={true} />);
|
|
101
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
102
|
+
});
|
|
37
103
|
|
|
104
|
+
it('shows empty state when not loading and no data', () => {
|
|
105
|
+
render(<LogTable logData={[]} isLoading={false} />);
|
|
106
|
+
expect(screen.getByTestId('empty-state')).toBeInTheDocument();
|
|
38
107
|
expect(screen.getByText('No ETL Operation logs found')).toBeInTheDocument();
|
|
39
108
|
});
|
|
40
109
|
|
|
41
|
-
it('
|
|
42
|
-
render(<LogTable logData={
|
|
110
|
+
it('renders table headers when data is present', () => {
|
|
111
|
+
render(<LogTable logData={mockLogData} isLoading={false} />);
|
|
112
|
+
expect(screen.getByText('Procedure')).toBeInTheDocument();
|
|
113
|
+
expect(screen.getByText('Start time')).toBeInTheDocument();
|
|
114
|
+
expect(screen.getByText('End time')).toBeInTheDocument();
|
|
115
|
+
expect(screen.getByText('Completion status')).toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('renders script names in the table', () => {
|
|
119
|
+
render(<LogTable logData={mockLogData} isLoading={false} />);
|
|
120
|
+
expect(screen.getByText('Script A')).toBeInTheDocument();
|
|
121
|
+
expect(screen.getByText('Script B')).toBeInTheDocument();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('renders Success status as a green tag', () => {
|
|
125
|
+
render(<LogTable logData={mockLogData} isLoading={false} />);
|
|
126
|
+
const successTag = screen.getByText('Success');
|
|
127
|
+
expect(successTag).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('renders Failed status as a red tag', () => {
|
|
131
|
+
render(<LogTable logData={mockLogData} isLoading={false} />);
|
|
132
|
+
const failedTag = screen.getByText('Failed');
|
|
133
|
+
expect(failedTag).toBeInTheDocument();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('renders pagination when data is present', () => {
|
|
137
|
+
render(<LogTable logData={mockLogData} isLoading={false} />);
|
|
138
|
+
expect(screen.getByTestId('pagination')).toBeInTheDocument();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('renders -- for missing start_time', () => {
|
|
142
|
+
const dataWithMissingTime: ETLResponse[] = [
|
|
143
|
+
{ script_name: 'Script C', start_time: '', stop_time: '', status: 'Success' },
|
|
144
|
+
];
|
|
145
|
+
render(<LogTable logData={dataWithMissingTime} isLoading={false} />);
|
|
146
|
+
expect(screen.getAllByText('--').length).toBeGreaterThanOrEqual(2);
|
|
147
|
+
});
|
|
43
148
|
|
|
44
|
-
|
|
45
|
-
|
|
149
|
+
it('does not show empty state when data is present', () => {
|
|
150
|
+
render(<LogTable logData={mockLogData} isLoading={false} />);
|
|
151
|
+
expect(screen.queryByTestId('empty-state')).not.toBeInTheDocument();
|
|
46
152
|
});
|
|
47
153
|
});
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ const options = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
12
|
-
|
|
12
|
+
export * from './components/global-property/index';
|
|
13
13
|
export const root = getAsyncLifecycle(() => import('./root.component'), options);
|
|
14
14
|
|
|
15
15
|
export function startupApp() {
|
|
@@ -76,6 +76,14 @@ export const searchLocationWorkspace = getAsyncLifecycle(
|
|
|
76
76
|
() => import('./components/locations/forms/search-location/search-location.workspace'),
|
|
77
77
|
options,
|
|
78
78
|
);
|
|
79
|
+
export const globalPropertyWorkspace = getAsyncLifecycle(
|
|
80
|
+
() => import('./components/global-property/workspace/global-property.workspace'),
|
|
81
|
+
options,
|
|
82
|
+
);
|
|
83
|
+
export const deleteGlobalPropertyModal = getAsyncLifecycle(
|
|
84
|
+
() => import('./components/global-property/modal/delete-global-property-modal.component'),
|
|
85
|
+
options,
|
|
86
|
+
);
|
|
79
87
|
|
|
80
88
|
// t('providerBanner', 'Provider banner')
|
|
81
89
|
export const providerBanner = getAsyncLifecycle(
|
package/src/root.component.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import FacilitySetup from './components/facility-setup/facility-setup.component'
|
|
|
9
9
|
import HomeComponent from './components/locations/home/home-locations.component';
|
|
10
10
|
|
|
11
11
|
import styles from './root.scss';
|
|
12
|
+
import GlobalPropertyDashboard from './components/global-property/dashboard/global-property-dashboard.component';
|
|
12
13
|
|
|
13
14
|
const Root: React.FC = () => {
|
|
14
15
|
const spaBasePath = globalThis.spaBase;
|
|
@@ -28,6 +29,7 @@ const Root: React.FC = () => {
|
|
|
28
29
|
<Route path="/etl-administration" element={<EtlAdminDashboard />} />
|
|
29
30
|
<Route path="/facility-setup" element={<FacilitySetup />} />
|
|
30
31
|
<Route path="/locations" element={<HomeComponent />} />
|
|
32
|
+
<Route path="/global-property" element={<GlobalPropertyDashboard />} />
|
|
31
33
|
</Routes>
|
|
32
34
|
</main>
|
|
33
35
|
</BrowserRouter>
|