@kenyaemr/esm-pharmacy-app 5.2.1-pre.724

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 (85) hide show
  1. package/.turbo/turbo-build.log +32 -0
  2. package/LICENSE.md +401 -0
  3. package/README.md +5 -0
  4. package/dist/102.js +1 -0
  5. package/dist/102.js.map +1 -0
  6. package/dist/130.js +2 -0
  7. package/dist/130.js.LICENSE.txt +3 -0
  8. package/dist/130.js.map +1 -0
  9. package/dist/152.js +1 -0
  10. package/dist/152.js.map +1 -0
  11. package/dist/255.js +2 -0
  12. package/dist/255.js.LICENSE.txt +9 -0
  13. package/dist/255.js.map +1 -0
  14. package/dist/303.js +1 -0
  15. package/dist/303.js.map +1 -0
  16. package/dist/341.js +1 -0
  17. package/dist/341.js.map +1 -0
  18. package/dist/368.js +1 -0
  19. package/dist/368.js.map +1 -0
  20. package/dist/383.js +1 -0
  21. package/dist/383.js.map +1 -0
  22. package/dist/419.js +1 -0
  23. package/dist/419.js.map +1 -0
  24. package/dist/515.js +1 -0
  25. package/dist/515.js.map +1 -0
  26. package/dist/574.js +1 -0
  27. package/dist/591.js +2 -0
  28. package/dist/591.js.LICENSE.txt +32 -0
  29. package/dist/591.js.map +1 -0
  30. package/dist/682.js +2 -0
  31. package/dist/682.js.LICENSE.txt +35 -0
  32. package/dist/682.js.map +1 -0
  33. package/dist/729.js +1 -0
  34. package/dist/729.js.map +1 -0
  35. package/dist/75.js +1 -0
  36. package/dist/75.js.map +1 -0
  37. package/dist/784.js +2 -0
  38. package/dist/784.js.LICENSE.txt +9 -0
  39. package/dist/784.js.map +1 -0
  40. package/dist/883.js +1 -0
  41. package/dist/883.js.map +1 -0
  42. package/dist/kenyaemr-esm-pharmacy-app.js +1 -0
  43. package/dist/kenyaemr-esm-pharmacy-app.js.buildmanifest.json +556 -0
  44. package/dist/kenyaemr-esm-pharmacy-app.js.map +1 -0
  45. package/dist/main.js +1 -0
  46. package/dist/main.js.map +1 -0
  47. package/dist/routes.json +1 -0
  48. package/jest.config.js +8 -0
  49. package/package.json +55 -0
  50. package/src/autosuggest/autosuggest.component.tsx +187 -0
  51. package/src/autosuggest/autosuggest.scss +62 -0
  52. package/src/config-schema.ts +43 -0
  53. package/src/declarations.d.ts +6 -0
  54. package/src/hooks/index.ts +4 -0
  55. package/src/hooks/usePharmacy.ts +17 -0
  56. package/src/hooks/usePharmacyPatients.ts +48 -0
  57. package/src/hooks/usePharmacyUsers.ts +43 -0
  58. package/src/hooks/useUserMappedPharmacies.ts +47 -0
  59. package/src/index.ts +41 -0
  60. package/src/pharamacy-forms/delete-confirm-dialog.modal.tsx +26 -0
  61. package/src/pharamacy-forms/pharamacy-assignment-form.workspace.tsx +121 -0
  62. package/src/pharamacy-forms/pharmacy-assignment-form.scss +125 -0
  63. package/src/pharmacy-component/pharmacy.component.tsx +14 -0
  64. package/src/pharmacy-detail/pharmacy-detail.component.tsx +14 -0
  65. package/src/pharmacy-detail-header/pharmacy-detail-header.component.tsx +41 -0
  66. package/src/pharmacy-header/pharmacy-header.component.tsx +36 -0
  67. package/src/pharmacy-header/pharmacy-header.scss +71 -0
  68. package/src/pharmacy-header/pharmacy-illustration.component.tsx +13 -0
  69. package/src/pharmacy-left-panel/pharmacy-left-panel-link.component.tsx +40 -0
  70. package/src/pharmacy-tables/pharmacy-patients.component.tsx +221 -0
  71. package/src/pharmacy-tables/pharmacy-table.component.tsx +164 -0
  72. package/src/pharmacy-tables/pharmacy-tables.scss +49 -0
  73. package/src/pharmacy-tables/pharmacy-users.component.tsx +197 -0
  74. package/src/pharmacy-tabs/pharmacy-tabs-component.tsx +28 -0
  75. package/src/pharmacy-tabs/pharmacy-tabs.scss +28 -0
  76. package/src/pharmacy.resources.ts +55 -0
  77. package/src/root.component.tsx +19 -0
  78. package/src/root.scss +15 -0
  79. package/src/root.test.tsx +5 -0
  80. package/src/routes.json +38 -0
  81. package/src/setup-tests.ts +1 -0
  82. package/src/types/index.ts +67 -0
  83. package/translations/en.json +27 -0
  84. package/tsconfig.json +5 -0
  85. package/webpack.config.js +1 -0
@@ -0,0 +1,121 @@
1
+ import { Button, ButtonSet, Column, Form, RadioButton, RadioButtonGroup, Stack } from '@carbon/react';
2
+ import { zodResolver } from '@hookform/resolvers/zod';
3
+ import { DefaultWorkspaceProps, showSnackbar, showToast } from '@openmrs/esm-framework';
4
+ import React from 'react';
5
+ import { Controller, useForm } from 'react-hook-form';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { mutate } from 'swr';
8
+ import { z } from 'zod';
9
+ import { Autosuggest } from '../autosuggest/autosuggest.component';
10
+ import { fetchPerson, fetchUser, pharmacyAssignmentFormSchema, saveMapping } from '../pharmacy.resources';
11
+ import styles from './pharmacy-assignment-form.scss';
12
+
13
+ type FormType = z.infer<typeof pharmacyAssignmentFormSchema>;
14
+
15
+ interface PharmacyAssignmentFormProps extends DefaultWorkspaceProps {
16
+ type: 'org.openmrs.User' | 'org.openmrs.Patient';
17
+ pharmacyUuid: string;
18
+ }
19
+
20
+ const PharmacyAssignmentForm: React.FC<PharmacyAssignmentFormProps> = ({
21
+ closeWorkspace,
22
+ closeWorkspaceWithSavedChanges,
23
+ promptBeforeClosing,
24
+ type,
25
+ pharmacyUuid,
26
+ }) => {
27
+ const form = useForm<FormType>({
28
+ defaultValues: {
29
+ basisIdentifier: pharmacyUuid,
30
+ entityIdentifier: '',
31
+ entityType: type,
32
+ },
33
+ resolver: zodResolver(pharmacyAssignmentFormSchema),
34
+ });
35
+ const { t } = useTranslation();
36
+ const onSubmit = async (values: FormType) => {
37
+ try {
38
+ const message = await saveMapping(values);
39
+ showSnackbar({ kind: 'success', subtitle: message, title: 'Success' });
40
+ closeWorkspace();
41
+ mutate((key) => {
42
+ return typeof key === 'string' && key.startsWith(`/ws/rest/v1/datafilter/search?type=${values.entityType}`);
43
+ });
44
+ } catch (error) {
45
+ showSnackbar({ kind: 'error', subtitle: error.message, title: 'Failure', isLowContrast: true });
46
+ }
47
+ };
48
+
49
+ const searchPatient = async (query: string) => {
50
+ const abortController = new AbortController();
51
+ return await fetchPerson(query, abortController);
52
+ };
53
+
54
+ const searchUser = async (query: string) => {
55
+ const abortController = new AbortController();
56
+ return await fetchUser(query, abortController);
57
+ };
58
+
59
+ const observableEntityType = form.watch('entityType');
60
+ return (
61
+ <Form style={{ height: '100%' }} onSubmit={form.handleSubmit(onSubmit, (err) => console.error(err))}>
62
+ <span className={styles.contactFormTitle}>{t('formTitle', 'Fill in the form details')}</span>
63
+ <Stack gap={4} className={styles.grid}>
64
+ <Column>
65
+ <Controller
66
+ control={form.control}
67
+ name="entityType"
68
+ render={({ field }) => (
69
+ <RadioButtonGroup
70
+ className={styles.radioButtonGroup}
71
+ legendText={t('entityType', 'Entity Type')}
72
+ {...field}
73
+ defaultSelected={field.value}
74
+ invalid={form.formState.errors[field.name]?.message}
75
+ invalidText={form.formState.errors[field.name]?.message}>
76
+ <RadioButton labelText={t('user', 'User')} value="org.openmrs.User" id="org.openmrs.User" />
77
+ <RadioButton labelText={t('user', 'Patient')} value="org.openmrs.Patient" id="org.openmrs.Patient" />
78
+ </RadioButtonGroup>
79
+ )}
80
+ />
81
+ </Column>
82
+ <Column>
83
+ <Controller
84
+ control={form.control}
85
+ name="entityIdentifier"
86
+ render={({ field }) => (
87
+ <Autosuggest
88
+ className={styles.input}
89
+ labelText={t('fullName', 'Full name')}
90
+ placeholder={t('fullNamePlaceHolder', 'Firstname Familyname')}
91
+ invalid={Boolean(form.formState.errors[field.name]?.message)}
92
+ invalidText={form.formState.errors[field.name]?.message}
93
+ getDisplayValue={(item) =>
94
+ observableEntityType == 'org.openmrs.Patient' ? item.display : item.person.display
95
+ }
96
+ getFieldValue={(item) => item.uuid}
97
+ getSearchResults={observableEntityType == 'org.openmrs.Patient' ? searchPatient : searchUser}
98
+ onClear={() => field.onChange('')}
99
+ onSuggestionSelected={(field_, value) => {
100
+ if (value) {
101
+ field.onChange(value);
102
+ }
103
+ }}
104
+ />
105
+ )}
106
+ />
107
+ </Column>
108
+ </Stack>
109
+ <ButtonSet className={styles.buttonSet}>
110
+ <Button className={styles.button} kind="secondary" onClick={closeWorkspace}>
111
+ {t('discard', 'Discard')}
112
+ </Button>
113
+ <Button className={styles.button} kind="primary" type="submit">
114
+ {t('submit', 'Submit')}
115
+ </Button>
116
+ </ButtonSet>
117
+ </Form>
118
+ );
119
+ };
120
+
121
+ export default PharmacyAssignmentForm;
@@ -0,0 +1,125 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @use '@carbon/layout';
4
+ @import '~@openmrs/esm-styleguide/src/vars';
5
+
6
+ .heading {
7
+ @include type.type-style('heading-compact-01');
8
+ margin: spacing.$spacing-05 0 spacing.$spacing-05;
9
+ }
10
+
11
+ .warningContainer {
12
+ background-color: $carbon--red-50;
13
+ padding: spacing.$spacing-04;
14
+ margin: spacing.$spacing-03 0 spacing.$spacing-03;
15
+ display: flex;
16
+ justify-content: space-between;
17
+ .warning {
18
+ @include type.type-style('heading-compact-01');
19
+ color: $ui-05;
20
+ }
21
+ }
22
+
23
+ .form {
24
+ display: flex;
25
+ flex-direction: column;
26
+ justify-content: space-between;
27
+ width: 50%;
28
+ }
29
+
30
+ .grid {
31
+ margin: 0 spacing.$spacing-05;
32
+ padding: 0rem;
33
+ row-gap: 1rem;
34
+ }
35
+
36
+ .inputRow {
37
+ margin-top: spacing.$spacing-05;
38
+ width: 50%; // Adjust width as per your design requirements
39
+ }
40
+
41
+ .datePickersRow {
42
+ display: flex;
43
+ flex-wrap: nowrap;
44
+ gap: spacing.$spacing-05; // Adjust gap between columns
45
+ align-items: center;
46
+ }
47
+
48
+ .datePickerInput {
49
+ width: 100%;
50
+ }
51
+
52
+ .button {
53
+ height: spacing.$spacing-10;
54
+ display: flex;
55
+ align-content: flex-start;
56
+ align-items: baseline;
57
+ min-width: 20%;
58
+ }
59
+
60
+ .inlineActions {
61
+ display: flex;
62
+ gap: spacing.$spacing-05; /* Adjust the spacing as needed */
63
+ }
64
+
65
+ .contactFormTitle {
66
+ @include type.type-style('heading-02');
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ margin: spacing.$spacing-05;
71
+ row-gap: 1.5rem;
72
+ position: relative;
73
+
74
+ &::after {
75
+ content: '';
76
+ display: block;
77
+ width: 2rem;
78
+ border-bottom: 0.375rem solid var(--brand-03);
79
+ position: absolute;
80
+ bottom: -0.75rem;
81
+ left: 0;
82
+ }
83
+
84
+ & > span {
85
+ @include type.type-style('body-01');
86
+ }
87
+ }
88
+
89
+ .sectionHeader {
90
+ @include type.type-style('heading-02');
91
+ }
92
+
93
+ :global(.omrs-breakpoint-lt-desktop) {
94
+ .form {
95
+ height: var(--tablet-workspace-window-height);
96
+ }
97
+ }
98
+
99
+ .buttonSet {
100
+ width: 100%;
101
+ padding-top: spacing.$spacing-05;
102
+ background-color: $ui-02;
103
+ justify-content: space-between;
104
+ bottom: 0;
105
+ position: absolute;
106
+ & > button {
107
+ max-width: 50% !important;
108
+ width: 50%;
109
+ }
110
+ }
111
+
112
+ /* New Styles for Facility and Visit Type */
113
+ .facilityVisitRow {
114
+ display: flex;
115
+ flex-wrap: nowrap;
116
+ gap: spacing.$spacing-05; /* Adjust gap between columns */
117
+ }
118
+
119
+ .facilityColumn {
120
+ flex: 1 1 0%; /* Makes columns take up equal space */
121
+ }
122
+
123
+ .radioButtonGroup {
124
+ margin-top: spacing.$spacing-04;
125
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { PhamacyHeader } from '../pharmacy-header/pharmacy-header.component';
3
+ import PharmaciesTable from '../pharmacy-tables/pharmacy-table.component';
4
+
5
+ const PharmacyComponent: React.FC = () => {
6
+ return (
7
+ <div className={`omrs-main-content`}>
8
+ <PhamacyHeader title={'Pharmacy'} />
9
+ <PharmaciesTable />
10
+ </div>
11
+ );
12
+ };
13
+
14
+ export default PharmacyComponent;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import PharmacyDetailHeader from '../pharmacy-detail-header/pharmacy-detail-header.component';
3
+ import { PharmacyTabs } from '../pharmacy-tabs/pharmacy-tabs-component';
4
+
5
+ const PharmacyDetail = () => {
6
+ return (
7
+ <div>
8
+ <PharmacyDetailHeader />
9
+ <PharmacyTabs />
10
+ </div>
11
+ );
12
+ };
13
+
14
+ export default PharmacyDetail;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import styles from '../pharmacy-header/pharmacy-header.scss';
3
+ import { Calendar, Location } from '@carbon/react/icons';
4
+ import { formatDate, useSession } from '@openmrs/esm-framework';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { usePharmacy } from '../hooks';
7
+ import { useParams } from 'react-router-dom';
8
+ import { SkeletonText } from '@carbon/react';
9
+
10
+ const PharmacyDetailHeader = () => {
11
+ const { t } = useTranslation();
12
+ const userSession = useSession();
13
+ const userLocation = userSession?.sessionLocation?.display;
14
+ const { pharmacyUuid } = useParams();
15
+ const { error, isLoading, pharmacy } = usePharmacy(pharmacyUuid);
16
+
17
+ if (isLoading) {
18
+ return <SkeletonText style={{ height: '40px' }} />;
19
+ }
20
+ return (
21
+ <div className={styles.header}>
22
+ <div className={styles['left-justified-items']}>
23
+ <div className={styles['page-labels']}>
24
+ <p>{t('pharmacy', 'Community pharmacy')}</p>
25
+ <p className={styles['page-name']}>{pharmacy.name}</p>
26
+ </div>
27
+ </div>
28
+ <div className={styles['right-justified-items']}>
29
+ <div className={styles['date-and-location']}>
30
+ <Location size={16} />
31
+ <span className={styles.value}>{userLocation}</span>
32
+ <span className={styles.middot}>&middot;</span>
33
+ <Calendar size={16} />
34
+ <span className={styles.value}>{formatDate(new Date(), { mode: 'standard' })}</span>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ );
39
+ };
40
+
41
+ export default PharmacyDetailHeader;
@@ -0,0 +1,36 @@
1
+ import { Calendar, Location } from '@carbon/react/icons';
2
+ import { formatDate, useSession } from '@openmrs/esm-framework';
3
+ import React from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import styles from './pharmacy-header.scss';
6
+ import PharmacyIllustration from './pharmacy-illustration.component';
7
+
8
+ interface PharmacyHeaderProps {
9
+ title: string;
10
+ }
11
+ export const PhamacyHeader: React.FC<PharmacyHeaderProps> = ({ title }) => {
12
+ const { t } = useTranslation();
13
+ const userSession = useSession();
14
+ const userLocation = userSession?.sessionLocation?.display;
15
+
16
+ return (
17
+ <div className={styles.header}>
18
+ <div className={styles['left-justified-items']}>
19
+ <PharmacyIllustration />
20
+ <div className={styles['page-labels']}>
21
+ <p>{t('pharmacy', 'Commity pharmacy')}</p>
22
+ <p className={styles['page-name']}>{title}</p>
23
+ </div>
24
+ </div>
25
+ <div className={styles['right-justified-items']}>
26
+ <div className={styles['date-and-location']}>
27
+ <Location size={16} />
28
+ <span className={styles.value}>{userLocation}</span>
29
+ <span className={styles.middot}>&middot;</span>
30
+ <Calendar size={16} />
31
+ <span className={styles.value}>{formatDate(new Date(), { mode: 'standard' })}</span>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ );
36
+ };
@@ -0,0 +1,71 @@
1
+ @use '@carbon/styles/scss/spacing';
2
+ @use '@carbon/styles/scss/type';
3
+ @import '~@openmrs/esm-styleguide/src/vars';
4
+
5
+ .header {
6
+ @include type.type-style('body-compact-02');
7
+ color: $text-02;
8
+ height: spacing.$spacing-12;
9
+ background-color: $ui-02;
10
+ border: 1px solid $ui-03;
11
+ border-left: 0px;
12
+ display: flex;
13
+ justify-content: space-between;
14
+ margin-bottom: 2rem;
15
+ }
16
+
17
+ .left-justified-items {
18
+ display: flex;
19
+ flex-direction: row;
20
+ align-items: center;
21
+ margin-left: 0.75rem;
22
+ }
23
+
24
+ .right-justified-items {
25
+ @include type.type-style('body-compact-02');
26
+ color: $text-02;
27
+ padding-top: 1rem;
28
+ }
29
+
30
+ .page-name {
31
+ @include type.type-style('heading-04');
32
+ }
33
+
34
+ .page-labels {
35
+ margin-left: 1rem;
36
+
37
+ p:first-of-type {
38
+ margin-bottom: 0.25rem;
39
+ }
40
+ }
41
+
42
+ .date-and-location {
43
+ display: flex;
44
+ justify-content: flex-end;
45
+ align-items: center;
46
+ margin-right: 1rem;
47
+ }
48
+
49
+ .value {
50
+ margin-left: 0.25rem;
51
+ }
52
+
53
+ .middot {
54
+ margin: 0 0.5rem;
55
+ }
56
+
57
+ .view {
58
+ @include type.type-style('label-01');
59
+ }
60
+
61
+ svg.iconOverrides {
62
+ width: 72 !important;
63
+ height: 72 !important;
64
+ fill: var(--brand-03);
65
+ }
66
+
67
+ .svgContainer svg {
68
+ width: 72px;
69
+ height: 72px;
70
+ fill: var(--brand-03);
71
+ }
@@ -0,0 +1,13 @@
1
+ import { Hospital } from '@carbon/react/icons';
2
+ import React from 'react';
3
+ import styles from './pharmacy-header.scss';
4
+
5
+ const PharmacyIllustration: React.FC = () => {
6
+ return (
7
+ <div className={styles.svgContainer}>
8
+ <Hospital className={styles.iconOverrides} />
9
+ </div>
10
+ );
11
+ };
12
+
13
+ export default PharmacyIllustration;
@@ -0,0 +1,40 @@
1
+ import { ConfigurableLink } from '@openmrs/esm-framework';
2
+ import last from 'lodash-es/last';
3
+ import React, { useMemo } from 'react';
4
+ import { BrowserRouter, useLocation } from 'react-router-dom';
5
+
6
+ export interface LinkConfig {
7
+ name: string;
8
+ title: string;
9
+ }
10
+
11
+ export function LinkExtension({ config }: { config: LinkConfig }) {
12
+ const { name, title } = config;
13
+ const location = useLocation();
14
+ const spaBasePath = window.getOpenmrsSpaBase() + 'home';
15
+
16
+ let urlSegment = useMemo(() => decodeURIComponent(last(location.pathname.split('/'))), [location.pathname]);
17
+
18
+ const isUUID = (value) => {
19
+ const regex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
20
+ return regex.test(value);
21
+ };
22
+
23
+ if (isUUID(urlSegment)) {
24
+ urlSegment = 'pharmacy';
25
+ }
26
+ return (
27
+ <ConfigurableLink
28
+ to={spaBasePath + '/' + name}
29
+ className={`cds--side-nav__link ${name === urlSegment && 'active-left-nav-link'}`}>
30
+ {title}
31
+ </ConfigurableLink>
32
+ );
33
+ }
34
+
35
+ export const createLeftPanelLink = (config: LinkConfig) => () =>
36
+ (
37
+ <BrowserRouter>
38
+ <LinkExtension config={config} />
39
+ </BrowserRouter>
40
+ );