@kenyaemr/esm-admin-app 5.4.4-pre.428 → 5.4.4-pre.429

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/1462.js +1 -1
  3. package/dist/1972.js +1 -1
  4. package/dist/2016.js +1 -1
  5. package/dist/2270.js +1 -1
  6. package/dist/2270.js.map +1 -1
  7. package/dist/2294.js +1 -1
  8. package/dist/2294.js.map +1 -1
  9. package/dist/235.js +1 -0
  10. package/dist/235.js.map +1 -0
  11. package/dist/2467.js +1 -1
  12. package/dist/2467.js.map +1 -1
  13. package/dist/2763.js +1 -0
  14. package/dist/2763.js.map +1 -0
  15. package/dist/2948.js +1 -1
  16. package/dist/3380.js +1 -1
  17. package/dist/3380.js.map +1 -1
  18. package/dist/3548.js +1 -1
  19. package/dist/3816.js +1 -1
  20. package/dist/3852.js +1 -0
  21. package/dist/3852.js.map +1 -0
  22. package/dist/4157.js +1 -0
  23. package/dist/4157.js.map +1 -0
  24. package/dist/4337.js +1 -1
  25. package/dist/4858.js +1 -1
  26. package/dist/487.js +1 -1
  27. package/dist/552.js +1 -0
  28. package/dist/552.js.map +1 -0
  29. package/dist/6092.js +1 -0
  30. package/dist/6092.js.map +1 -0
  31. package/dist/6456.js +1 -1
  32. package/dist/7607.js +1 -1
  33. package/dist/7737.js +1 -1
  34. package/dist/7739.js +1 -1
  35. package/dist/8376.js +1 -1
  36. package/dist/8727.js +1 -1
  37. package/dist/9182.js +1 -1
  38. package/dist/9406.js +1 -0
  39. package/dist/9406.js.map +1 -0
  40. package/dist/9446.js +1 -1
  41. package/dist/9647.js +1 -1
  42. package/dist/kenyaemr-esm-admin-app.js +3 -3
  43. package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +208 -157
  44. package/dist/kenyaemr-esm-admin-app.js.map +1 -1
  45. package/dist/main.js +3 -3
  46. package/dist/main.js.map +1 -1
  47. package/dist/routes.json +1 -1
  48. package/package.json +1 -1
  49. package/src/components/global-property/hooks/useLogoUpload.ts +28 -0
  50. package/src/components/global-property/table/global-property-table.component.tsx +117 -125
  51. package/src/components/global-property/table/global-property-table.scss +10 -0
  52. package/src/components/global-property/table/global-property-table.test.tsx +30 -26
  53. package/src/components/global-property/workspace/upload-logo.workspace.scss +61 -0
  54. package/src/components/global-property/workspace/upload-logo.workspace.test.tsx +180 -0
  55. package/src/components/global-property/workspace/upload-logo.workspace.tsx +191 -0
  56. package/src/index.ts +4 -0
  57. package/src/routes.json +5 -0
  58. package/translations/am.json +22 -5
  59. package/translations/en.json +25 -8
  60. package/translations/fr.json +22 -5
  61. package/translations/sw.json +22 -5
  62. package/dist/1442.js +0 -1
  63. package/dist/1442.js.map +0 -1
  64. package/dist/5910.js +0 -1
  65. package/dist/5910.js.map +0 -1
  66. package/dist/6253.js +0 -1
  67. package/dist/6253.js.map +0 -1
  68. package/dist/6455.js +0 -1
  69. package/dist/6455.js.map +0 -1
  70. package/dist/6925.js +0 -1
  71. package/dist/6925.js.map +0 -1
@@ -0,0 +1,180 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom';
5
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
6
+ import UploadLogoWorkspace from './upload-logo.workspace';
7
+
8
+ const mockShowSnackbar = vi.fn();
9
+ const mockUploadLogo = vi.fn();
10
+ const mockCloseWorkspace = vi.fn();
11
+ const mockMutateGlobalProperty = vi.fn();
12
+
13
+ vi.mock('react-i18next', () => ({
14
+ useTranslation: () => ({ t: (_key: string, fallback: string) => fallback }),
15
+ }));
16
+
17
+ vi.mock('@openmrs/esm-framework', () => ({
18
+ useLayoutType: vi.fn(() => 'desktop'),
19
+ showSnackbar: (...args: unknown[]) => mockShowSnackbar(...args),
20
+ Workspace2: ({ children, title }: { children: React.ReactNode; title: string }) => (
21
+ <div>
22
+ <h2>{title}</h2>
23
+ {children}
24
+ </div>
25
+ ),
26
+ }));
27
+
28
+ vi.mock('../hooks/useLogoUpload', async (importOriginal) => {
29
+ const original = await importOriginal<typeof import('../hooks/useLogoUpload')>();
30
+ return {
31
+ ...original,
32
+ uploadLogo: (...args: unknown[]) => mockUploadLogo(...args),
33
+ };
34
+ });
35
+
36
+ vi.mock('@carbon/react', async (importOriginal) => {
37
+ const original = await importOriginal<typeof import('@carbon/react')>();
38
+ return {
39
+ ...original,
40
+ Button: ({ children, onClick, disabled, type }: any) => (
41
+ <button onClick={onClick} disabled={disabled} type={type || 'button'}>
42
+ {children}
43
+ </button>
44
+ ),
45
+ FileUploader: ({ onChange, onDelete, disabled }: any) => (
46
+ <div>
47
+ <input
48
+ data-testid="file-input"
49
+ type="file"
50
+ disabled={disabled}
51
+ onChange={(e) => onChange({ target: e.target, addedFiles: Array.from(e.target.files ?? []) })}
52
+ />
53
+ <button type="button" onClick={() => onDelete?.()}>
54
+ remove-file
55
+ </button>
56
+ </div>
57
+ ),
58
+ };
59
+ });
60
+
61
+ const baseProps = {
62
+ closeWorkspace: mockCloseWorkspace,
63
+ workspaceProps: {
64
+ mutateGlobalProperty: mockMutateGlobalProperty,
65
+ },
66
+ } as any;
67
+
68
+ function makeFile(name: string, type: string, size?: number) {
69
+ const file = new File(['logo-bytes'], name, { type });
70
+ if (size != null) {
71
+ Object.defineProperty(file, 'size', { value: size });
72
+ }
73
+ return file;
74
+ }
75
+
76
+ describe('UploadLogoWorkspace', () => {
77
+ beforeEach(() => {
78
+ vi.clearAllMocks();
79
+ global.URL.createObjectURL = vi.fn(() => 'blob:preview');
80
+ global.URL.revokeObjectURL = vi.fn();
81
+ });
82
+
83
+ it('renders the workspace with the Upload button disabled until a file is chosen', () => {
84
+ render(<UploadLogoWorkspace {...baseProps} />);
85
+ expect(screen.getByText('Upload image')).toBeInTheDocument();
86
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeDisabled();
87
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
88
+ });
89
+
90
+ it('shows a preview and enables Upload after selecting a valid image', async () => {
91
+ render(<UploadLogoWorkspace {...baseProps} />);
92
+
93
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('logo.png', 'image/png'));
94
+
95
+ expect(screen.getByRole('img', { name: 'Image preview' })).toBeInTheDocument();
96
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeEnabled();
97
+ });
98
+
99
+ it('rejects an unsupported file type and keeps Upload disabled', async () => {
100
+ render(<UploadLogoWorkspace {...baseProps} />);
101
+
102
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('notes.pdf', 'application/pdf'));
103
+
104
+ expect(screen.getByText(/Unsupported file type/i)).toBeInTheDocument();
105
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeDisabled();
106
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
107
+ });
108
+
109
+ it('rejects a file larger than the 2 MB limit', async () => {
110
+ render(<UploadLogoWorkspace {...baseProps} />);
111
+
112
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('huge.png', 'image/png', 2 * 1024 * 1024 + 1));
113
+
114
+ expect(screen.getByText(/exceeds the 2 MB maximum/i)).toBeInTheDocument();
115
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeDisabled();
116
+ });
117
+
118
+ it('uploads the selected image, shows a success snackbar, refreshes and closes', async () => {
119
+ mockUploadLogo.mockResolvedValue({ savedPath: '/srv/openmrs/prescription-logo.png', message: 'ok' });
120
+ render(<UploadLogoWorkspace {...baseProps} />);
121
+
122
+ const file = makeFile('logo.png', 'image/png');
123
+ await userEvent.upload(screen.getByTestId('file-input'), file);
124
+ await userEvent.click(screen.getByRole('button', { name: 'Upload' }));
125
+
126
+ await waitFor(() => {
127
+ expect(mockUploadLogo).toHaveBeenCalledWith(file, 'prescription');
128
+ expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' }));
129
+ expect(mockMutateGlobalProperty).toHaveBeenCalled();
130
+ expect(mockCloseWorkspace).toHaveBeenCalled();
131
+ });
132
+ });
133
+
134
+ it('uploads to the receipt destination when the receipt option is selected', async () => {
135
+ mockUploadLogo.mockResolvedValue({ savedPath: '/srv/openmrs/receipt-logo.png', message: 'ok' });
136
+ render(<UploadLogoWorkspace {...baseProps} />);
137
+
138
+ await userEvent.click(screen.getByRole('radio', { name: 'Receipt logo' }));
139
+ const file = makeFile('mtrh-logo.png', 'image/png');
140
+ await userEvent.upload(screen.getByTestId('file-input'), file);
141
+ await userEvent.click(screen.getByRole('button', { name: 'Upload' }));
142
+
143
+ await waitFor(() => {
144
+ expect(mockUploadLogo).toHaveBeenCalledWith(file, 'receipt');
145
+ });
146
+ });
147
+
148
+ it('shows an error snackbar and keeps the workspace open when upload fails', async () => {
149
+ mockUploadLogo.mockRejectedValue(new Error('Upload failed'));
150
+ render(<UploadLogoWorkspace {...baseProps} />);
151
+
152
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('logo.png', 'image/png'));
153
+ await userEvent.click(screen.getByRole('button', { name: 'Upload' }));
154
+
155
+ await waitFor(() => {
156
+ expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
157
+ });
158
+ expect(mockMutateGlobalProperty).not.toHaveBeenCalled();
159
+ expect(mockCloseWorkspace).not.toHaveBeenCalled();
160
+ });
161
+
162
+ it('closes the workspace when Cancel is clicked', async () => {
163
+ render(<UploadLogoWorkspace {...baseProps} />);
164
+ await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
165
+ expect(mockCloseWorkspace).toHaveBeenCalledTimes(1);
166
+ expect(mockUploadLogo).not.toHaveBeenCalled();
167
+ });
168
+
169
+ it('clears the preview and disables Upload when the chosen file is removed', async () => {
170
+ render(<UploadLogoWorkspace {...baseProps} />);
171
+
172
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('logo.png', 'image/png'));
173
+ expect(screen.getByRole('img', { name: 'Image preview' })).toBeInTheDocument();
174
+
175
+ await userEvent.click(screen.getByRole('button', { name: 'remove-file' }));
176
+
177
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
178
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeDisabled();
179
+ });
180
+ });
@@ -0,0 +1,191 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { showSnackbar, useLayoutType, Workspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework';
3
+ import {
4
+ Button,
5
+ ButtonSet,
6
+ FileUploader,
7
+ Form,
8
+ FormGroup,
9
+ InlineLoading,
10
+ InlineNotification,
11
+ RadioButton,
12
+ RadioButtonGroup,
13
+ Stack,
14
+ } from '@carbon/react';
15
+ import { useTranslation } from 'react-i18next';
16
+ import classNames from 'classnames';
17
+
18
+ import styles from './upload-logo.workspace.scss';
19
+ import { ALLOWED_LOGO_TYPES, MAX_LOGO_SIZE_BYTES, type LogoTarget, uploadLogo } from '../hooks/useLogoUpload';
20
+
21
+ type UploadLogoWorkspaceProps = {
22
+ mutateGlobalProperty?: () => void;
23
+ };
24
+
25
+ const UploadLogoWorkspace: React.FC<Workspace2DefinitionProps<UploadLogoWorkspaceProps, {}, {}>> = ({
26
+ closeWorkspace,
27
+ workspaceProps,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const isTablet = useLayoutType() === 'tablet';
31
+ const mutateGlobalProperty = workspaceProps?.mutateGlobalProperty;
32
+
33
+ const [target, setTarget] = useState<LogoTarget>('prescription');
34
+ const [file, setFile] = useState<File | null>(null);
35
+ const [previewUrl, setPreviewUrl] = useState<string | null>(null);
36
+ const [validationError, setValidationError] = useState<string | null>(null);
37
+ const [isSubmitting, setIsSubmitting] = useState(false);
38
+
39
+ useEffect(() => {
40
+ if (!file) {
41
+ setPreviewUrl(null);
42
+ return;
43
+ }
44
+ const url = URL.createObjectURL(file);
45
+ setPreviewUrl(url);
46
+ return () => URL.revokeObjectURL(url);
47
+ }, [file]);
48
+
49
+ const handleFileChange = (selected: File | null) => {
50
+ setValidationError(null);
51
+ if (!selected) {
52
+ setFile(null);
53
+ return;
54
+ }
55
+ if (!ALLOWED_LOGO_TYPES.includes(selected.type)) {
56
+ setValidationError(t('unsupportedImageType', 'Unsupported file type. Accepted: JPEG, PNG, GIF, BMP or WebP.'));
57
+ setFile(null);
58
+ return;
59
+ }
60
+ if (selected.size > MAX_LOGO_SIZE_BYTES) {
61
+ setValidationError(t('imageTooLarge', 'File exceeds the 2 MB maximum size.'));
62
+ setFile(null);
63
+ return;
64
+ }
65
+ setFile(selected);
66
+ };
67
+
68
+ const onSubmit = async (event: React.FormEvent) => {
69
+ event.preventDefault();
70
+ if (!file) {
71
+ setValidationError(t('selectImageFirst', 'Please select an image to upload.'));
72
+ return;
73
+ }
74
+ setIsSubmitting(true);
75
+ try {
76
+ const { savedPath } = await uploadLogo(file, target);
77
+ const fileName = savedPath?.split(/[\\/]/).pop() ?? file.name;
78
+ showSnackbar({
79
+ title: t('success', 'Success'),
80
+ kind: 'success',
81
+ subtitle: t('logoUploaded', 'Image "{{fileName}}" uploaded successfully', { fileName }),
82
+ timeoutInMs: 5000,
83
+ });
84
+ mutateGlobalProperty?.();
85
+ closeWorkspace({ discardUnsavedChanges: true });
86
+ } catch (error: any) {
87
+ showSnackbar({
88
+ title: t('error', 'Error'),
89
+ kind: 'error',
90
+ subtitle: error?.message ?? t('logoUploadError', 'Error uploading image'),
91
+ });
92
+ } finally {
93
+ setIsSubmitting(false);
94
+ }
95
+ };
96
+
97
+ return (
98
+ <Workspace2 title={t('uploadImage', 'Upload image')} hasUnsavedChanges={Boolean(file)}>
99
+ <Form onSubmit={onSubmit} className={styles.form}>
100
+ <div className={styles.formContainer}>
101
+ <Stack gap={5}>
102
+ <InlineNotification
103
+ kind="info"
104
+ lowContrast
105
+ hideCloseButton
106
+ title={t('logoUploadTitle', 'Printed documents logo')}
107
+ subtitle={t(
108
+ 'logoUploadDesc',
109
+ 'Upload a logo used in printed documents. JPEG, PNG, GIF, BMP or WebP, up to 2 MB.',
110
+ )}
111
+ />
112
+
113
+ <FormGroup legendText={t('logoTarget', 'Logo destination')}>
114
+ <RadioButtonGroup
115
+ name="logo-target"
116
+ legendText=""
117
+ valueSelected={target}
118
+ onChange={(value: string) => setTarget(value as LogoTarget)}
119
+ disabled={isSubmitting}>
120
+ <RadioButton
121
+ labelText={t('prescriptionLogo', 'Prescription logo')}
122
+ value="prescription"
123
+ id="logo-target-prescription"
124
+ />
125
+ <RadioButton labelText={t('receiptLogo', 'Receipt logo')} value="receipt" id="logo-target-receipt" />
126
+ </RadioButtonGroup>
127
+ </FormGroup>
128
+
129
+ <FormGroup legendText={t('image', 'Image')}>
130
+ <FileUploader
131
+ labelTitle=""
132
+ labelDescription={t('logoUploaderHint', 'Max file size is 2 MB. Supported: JPEG, PNG, GIF, BMP, WebP.')}
133
+ buttonLabel={t('addFile', 'Add file')}
134
+ buttonKind="tertiary"
135
+ size="md"
136
+ filenameStatus="edit"
137
+ accept={ALLOWED_LOGO_TYPES}
138
+ multiple={false}
139
+ disabled={isSubmitting}
140
+ onChange={(e: { target: HTMLInputElement; addedFiles?: File[] }) => {
141
+ const selected = e.addedFiles?.[0] ?? e.target?.files?.[0] ?? null;
142
+ handleFileChange(selected);
143
+ }}
144
+ onDelete={() => handleFileChange(null)}
145
+ />
146
+ </FormGroup>
147
+
148
+ {validationError && (
149
+ <InlineNotification
150
+ kind="error"
151
+ lowContrast
152
+ hideCloseButton
153
+ title={t('invalidImage', 'Invalid image')}
154
+ subtitle={validationError}
155
+ />
156
+ )}
157
+
158
+ {previewUrl && (
159
+ <div className={styles.previewContainer}>
160
+ <span className={styles.previewLabel}>{t('preview', 'Preview')}</span>
161
+ <img src={previewUrl} alt={t('imagePreview', 'Image preview')} className={styles.preview} />
162
+ </div>
163
+ )}
164
+ </Stack>
165
+ </div>
166
+
167
+ <ButtonSet
168
+ className={classNames({
169
+ [styles.tablet]: isTablet,
170
+ [styles.desktop]: !isTablet,
171
+ })}>
172
+ <Button className={styles.buttonContainer} kind="secondary" onClick={() => closeWorkspace()}>
173
+ {t('cancel', 'Cancel')}
174
+ </Button>
175
+ <Button className={styles.buttonContainer} disabled={!file || isSubmitting} kind="primary" type="submit">
176
+ {isSubmitting ? (
177
+ <span className={styles.inlineLoading}>
178
+ {t('uploading', 'Uploading...')}
179
+ <InlineLoading status="active" iconDescription="Loading" />
180
+ </span>
181
+ ) : (
182
+ t('upload', 'Upload')
183
+ )}
184
+ </Button>
185
+ </ButtonSet>
186
+ </Form>
187
+ </Workspace2>
188
+ );
189
+ };
190
+
191
+ export default UploadLogoWorkspace;
package/src/index.ts CHANGED
@@ -80,6 +80,10 @@ export const globalPropertyWorkspace = getAsyncLifecycle(
80
80
  () => import('./components/global-property/workspace/global-property.workspace'),
81
81
  options,
82
82
  );
83
+ export const uploadLogoWorkspace = getAsyncLifecycle(
84
+ () => import('./components/global-property/workspace/upload-logo.workspace'),
85
+ options,
86
+ );
83
87
  export const deleteGlobalPropertyModal = getAsyncLifecycle(
84
88
  () => import('./components/global-property/modal/delete-global-property-modal.component'),
85
89
  options,
package/src/routes.json CHANGED
@@ -92,6 +92,11 @@
92
92
  "name": "global-property-workspace",
93
93
  "component": "globalPropertyWorkspace",
94
94
  "window": "esm-admin-workspace-window"
95
+ },
96
+ {
97
+ "name": "upload-logo-workspace",
98
+ "component": "uploadLogoWorkspace",
99
+ "window": "esm-admin-workspace-window"
95
100
  }
96
101
  ],
97
102
  "workspaceWindows2": [
@@ -5,6 +5,7 @@
5
5
  "activeFrom": "ከዚህ ቀን ጀምሮ ገባሪ",
6
6
  "activeTo": "እስከዚህ ቀን ገባሪ",
7
7
  "addAnotherRoleScope": "Add user role scope",
8
+ "addFile": "Add file",
8
9
  "addGlobalProperty": "Add new global property",
9
10
  "addLocation": "ቦታ ያክሉ",
10
11
  "addRoleScope": "Add a new user role scope",
@@ -18,7 +19,6 @@
18
19
  "chooseIdentifierType": "Choose identifier type",
19
20
  "chooseRegulator": "Choose regulator",
20
21
  "chooseRegulatorType": "Choose regulator option",
21
- "clearSearchButton": "Clear search button",
22
22
  "completionStatus": "የማጠናቀቂያ ሁኔታ",
23
23
  "confirmation": "ማረጋገጫ",
24
24
  "contact": "Contact",
@@ -91,8 +91,8 @@
91
91
  "generalInformation": "አጠቃላይ መረጃ",
92
92
  "givenName": "Given Name",
93
93
  "givenNameRequired": "Given name is required",
94
- "globalProperties": "Global properties",
95
94
  "globalProperty": "Global property",
95
+ "globalPropertyDescription": "A list of all global properties for the system. Filter by search term to find specific properties.",
96
96
  "globalPropertyError": "Global property",
97
97
  "gpCreated": "Global property {{property}} was created successfully.",
98
98
  "gpDatatypeConfigPlaceholder": "Optional datatype configuration",
@@ -125,7 +125,11 @@
125
125
  "identifierNumber": "Identifier number*",
126
126
  "identifierProvider": "መለያ:",
127
127
  "identity": "Identity",
128
+ "image": "Image",
129
+ "imagePreview": "Image preview",
130
+ "imageTooLarge": "File exceeds the 2 MB maximum size.",
128
131
  "inactive": "Inactive",
132
+ "invalidImage": "Invalid image",
129
133
  "itemsPerPage": "Items per page:",
130
134
  "kephLevel": "የ Keph ደረጃ",
131
135
  "lastSynced": "Last synced",
@@ -157,6 +161,12 @@
157
161
  "locationUpdated": "Location {{locationName}} was updated successfully.",
158
162
  "loginInfo": "Login Info",
159
163
  "loginInformation": "Login Info",
164
+ "logoTarget": "Logo destination",
165
+ "logoUploadDesc": "Upload a logo used in printed documents. JPEG, PNG, GIF, BMP or WebP, up to 2 MB.",
166
+ "logoUploaded": "Image uploaded successfully to {{savedPath}}",
167
+ "logoUploaderHint": "Max file size is 2 MB. Supported: JPEG, PNG, GIF, BMP, WebP.",
168
+ "logoUploadError": "Error uploading image",
169
+ "logoUploadTitle": "Printed documents logo",
160
170
  "male": "Male",
161
171
  "manageUserRoleScope": "የተጠቃሚ ሚና ስፋትን አስተዳድር",
162
172
  "manageUsers": "Manage Users",
@@ -173,9 +183,7 @@
173
183
  "noContractedServices": "No contracted services on record for this facility.",
174
184
  "noData": "የሚታይ መረጃ የለም",
175
185
  "noDescriptionAvailable": "ምንም መግለጫ አይገኝም",
176
- "noGlobalProperties": "No global properties to display",
177
186
  "noHwrApi": "የጤና እንክብካቤ ሰራተኛ መመዝገቢያ API መረጃዎች አልተዋቀሩም፣ እባክዎ የስርዓት አስተዳዳሪን ያግኙ። አካውንት መፍጠር መቀጠል ይፈልጋሉ",
178
- "noMatchingGlobalProperties": "No global properties match your search",
179
187
  "noMatchingUsers": "ተዛማጅ ተጠቃሚዎች አልተገኙም",
180
188
  "noRecordsFound": "ምንም የETL ክንውን ምዝግብ ማስታወሻዎች አልተገኙም",
181
189
  "noRespond": "አይ",
@@ -204,6 +212,8 @@
204
212
  "practiceTypePlaceholder": "e.g. Clinical Practice",
205
213
  "practitioner": "Practitioner",
206
214
  "preferredHandlerClassname": "Preferred handler classname",
215
+ "prescriptionLogo": "Prescription logo",
216
+ "preview": "Preview",
207
217
  "previousPage": "የቀድሞ ገጽ",
208
218
  "procedure": "ሥነ-ሥርዓት",
209
219
  "professionalCadre": "Professional Cadre",
@@ -226,6 +236,7 @@
226
236
  "pullDetailsfromHWR": "Pulling data from Health worker registry...",
227
237
  "qualification": "Qualification",
228
238
  "qualificationPlaceholder": "e.g. MBChB(UON)2009",
239
+ "receiptLogo": "Receipt logo",
229
240
  "recreated": "በድጋሚ ተፈጥሯል",
230
241
  "recreateDatatools": "የውሂብ መሳሪያዎችን በድጋሚ ፍጠር",
231
242
  "recreateFacilityWideTables": "የተቋም-ሰፊ ሰንጠረዦችን በድጋሚ ፍጠር",
@@ -245,17 +256,19 @@
245
256
  "role": "ሚና",
246
257
  "roles": "Roles Info",
247
258
  "rolesInfo": "Roles Info",
259
+ "rowActions": "Row actions",
248
260
  "saveAndClose": "Save & close",
249
261
  "search": "ቦታ ፈልግ",
250
262
  "searchFailed": "Search failed",
263
+ "searchForGlobalProperties": "Search for global properties",
251
264
  "searchForLocation": "Search for location",
252
265
  "searchForUsers": "ተጠቃሚ ፈልግ",
253
- "searchGlobalPropertiesByName": "Search global property by name",
254
266
  "searchLocation": "Search Location",
255
267
  "searchNoResults": "ምንም ተዛማጅ ውጤቶች አልተገኙም",
256
268
  "searchNoResultsHelper": "የተለየ ቃል ለመፈለግ ይሞክሩ",
257
269
  "searchParentLocation": "Search for location...",
258
270
  "selectDatatypeClassname": "Select datatype classname",
271
+ "selectImageFirst": "Please select an image to upload.",
259
272
  "selectTagPlaceholder": "Select a tag",
260
273
  "selectTags": "Select tag(s)",
261
274
  "sex": "Sex",
@@ -288,7 +301,11 @@
288
301
  "unknownError": "የጤና ሰራተኛ መመዝገቢያን በሚፈልጉበት ጊዜ ያልታወቀ ስህተት ተከስቷል፣ እባክዎ የስርዓት አስተዳዳሪን ያግኙ። አካውንት መፍጠር መቀጠል ይፈልጋሉ",
289
302
  "unlicensed": "ፈቃድ የሌለው",
290
303
  "Unlicensed": "ፈቃድ የሌለው",
304
+ "unsupportedImageType": "Unsupported file type. Accepted: JPEG, PNG, GIF, BMP or WebP.",
291
305
  "updateMessage": "Provider updated successfully",
306
+ "upload": "Upload",
307
+ "uploadImage": "Upload image",
308
+ "uploading": "Uploading...",
292
309
  "userCreationFailedSubtitle": "An error occurred while creating user {{errorMessage}}",
293
310
  "userGivenName": "Enter Given Name",
294
311
  "userManagement": "የተጠቃሚ አስተዳደር",
@@ -5,6 +5,7 @@
5
5
  "activeFrom": "Active From",
6
6
  "activeTo": "Active To",
7
7
  "addAnotherRoleScope": "Add user role scope",
8
+ "addFile": "Add file",
8
9
  "addGlobalProperty": "Add global property",
9
10
  "addLocation": "Add Location",
10
11
  "addRoleScope": "Add a new user role scope",
@@ -18,7 +19,6 @@
18
19
  "chooseIdentifierType": "Choose identifier type",
19
20
  "chooseRegulator": "Choose regulator",
20
21
  "chooseRegulatorType": "Choose regulator option",
21
- "clearSearchButton": "Clear search button",
22
22
  "completionStatus": "Completion status",
23
23
  "confirmation": "Confirmation",
24
24
  "contact": "Contact",
@@ -91,8 +91,8 @@
91
91
  "generalInformation": "General information",
92
92
  "givenName": "Given Name",
93
93
  "givenNameRequired": "Given name is required",
94
- "globalProperties": "Global properties",
95
- "globalProperty": "Global property",
94
+ "globalProperty": "Global Property",
95
+ "globalPropertyDescription": "A list of all global properties for the system. Filter by search term to find specific properties.",
96
96
  "globalPropertyError": "Global property",
97
97
  "gpCreated": "Global property {{property}} was created successfully.",
98
98
  "gpDatatypeConfigPlaceholder": "Optional datatype configuration",
@@ -125,7 +125,11 @@
125
125
  "identifierNumber": "Identifier number*",
126
126
  "identifierProvider": "Identifier:",
127
127
  "identity": "Identity",
128
+ "image": "Image",
129
+ "imagePreview": "Image preview",
130
+ "imageTooLarge": "File exceeds the 2 MB maximum size.",
128
131
  "inactive": "Inactive",
132
+ "invalidImage": "Invalid image",
129
133
  "itemsPerPage": "Items per page:",
130
134
  "kephLevel": "KEPH level",
131
135
  "lastSynced": "Last synced",
@@ -157,6 +161,12 @@
157
161
  "locationUpdated": "Location {{locationName}} was updated successfully.",
158
162
  "loginInfo": "Login Info",
159
163
  "loginInformation": "Login Info",
164
+ "logoTarget": "Logo destination",
165
+ "logoUploadDesc": "Upload a logo used in printed documents. JPEG, PNG, GIF, BMP or WebP, up to 2 MB.",
166
+ "logoUploaded": "Image \"{{fileName}}\" uploaded successfully",
167
+ "logoUploaderHint": "Max file size is 2 MB. Supported: JPEG, PNG, GIF, BMP, WebP.",
168
+ "logoUploadError": "Error uploading image",
169
+ "logoUploadTitle": "Printed documents logo",
160
170
  "male": "Male",
161
171
  "manageUserRoleScope": "Manage user role scope",
162
172
  "manageUsers": "Manage Users",
@@ -173,9 +183,7 @@
173
183
  "noContractedServices": "No contracted services on record for this facility.",
174
184
  "noData": "No data to display",
175
185
  "noDescriptionAvailable": "No description available",
176
- "noGlobalProperties": "No global properties to display",
177
186
  "noHwrApi": "Health Care Worker Registry API credentials not configured, Kindly contact system admin. Do you want to continue to create an account",
178
- "noMatchingGlobalProperties": "No global properties match your search",
179
187
  "noMatchingUsers": "No matching users found",
180
188
  "noRecordsFound": "No ETL Operation logs found",
181
189
  "noRespond": "No",
@@ -188,11 +196,11 @@
188
196
  "noUserRoleScope": "No user role scope",
189
197
  "noUsersAvailable": "No users available",
190
198
  "operationError": "{{operationName}} failed",
191
- "operationErrorSubtitle": "An error occurred during the operation.",
199
+ "operationErrorSubtitle": "An error occurred during the operation. {{error}}",
192
200
  "operationsConfirmationMessages": "Do you want to {{operationTypeOrName}}?",
193
201
  "operationSuccess": "{{operationName}} successfully {{operationType}}",
194
202
  "operationSuccessSubtitle": "The operation completed successfully.",
195
- "pageRangeText_one": "of {{count}} page",
203
+ "pageRangeText_one": "of {{count}} pages",
196
204
  "pageRangeText_other": "of {{count}} pages",
197
205
  "passportNumber": "Passport number",
198
206
  "permanent": "Permanent?",
@@ -204,6 +212,8 @@
204
212
  "practiceTypePlaceholder": "e.g. Clinical Practice",
205
213
  "practitioner": "Practitioner",
206
214
  "preferredHandlerClassname": "Preferred handler classname",
215
+ "prescriptionLogo": "Prescription logo",
216
+ "preview": "Preview",
207
217
  "previousPage": "Previous page",
208
218
  "procedure": "Procedure",
209
219
  "professionalCadre": "Professional cadre",
@@ -226,6 +236,7 @@
226
236
  "pullDetailsfromHWR": "Pulling data from Health worker registry...",
227
237
  "qualification": "Qualification",
228
238
  "qualificationPlaceholder": "e.g. MBChB(UON)2009",
239
+ "receiptLogo": "Receipt logo",
229
240
  "recreated": "recreated",
230
241
  "recreateDatatools": "Recreate datatools",
231
242
  "recreateFacilityWideTables": "Recreate facility wide tables",
@@ -245,17 +256,19 @@
245
256
  "role": "Role",
246
257
  "roles": "Roles Info",
247
258
  "rolesInfo": "Roles Info",
259
+ "rowActions": "Row actions",
248
260
  "saveAndClose": "Save & close",
249
261
  "search": "Search",
250
262
  "searchFailed": "Search failed",
263
+ "searchForGlobalProperties": "Search for global properties",
251
264
  "searchForLocation": "Search for location",
252
265
  "searchForUsers": "Search for user",
253
- "searchGlobalPropertiesByName": "Search global property by name",
254
266
  "searchLocation": "Search Location",
255
267
  "searchNoResults": "Found no matching results",
256
268
  "searchNoResultsHelper": "Try searching for a different term",
257
269
  "searchParentLocation": "Search for location...",
258
270
  "selectDatatypeClassname": "Select datatype classname",
271
+ "selectImageFirst": "Please select an image to upload.",
259
272
  "selectTagPlaceholder": "Select a tag",
260
273
  "selectTags": "Select tag(s)",
261
274
  "sex": "Sex",
@@ -288,7 +301,11 @@
288
301
  "unknownError": "An error occurred while searching Health Worker Registry, kindly contact system admin. Do you want to continue to create an account",
289
302
  "unlicensed": "Unlicensed",
290
303
  "Unlicensed": "Unlicensed",
304
+ "unsupportedImageType": "Unsupported file type. Accepted: JPEG, PNG, GIF, BMP or WebP.",
291
305
  "updateMessage": "Provider updated successfully",
306
+ "upload": "Upload",
307
+ "uploadImage": "Upload image",
308
+ "uploading": "Uploading...",
292
309
  "userCreationFailedSubtitle": "An error occurred while creating user {{errorMessage}}",
293
310
  "userGivenName": "Enter Given Name",
294
311
  "userManagement": "User Management",