@mbehenri/openmrs-esm-opentms-meet-app 1.0.0
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/.editorconfig +12 -0
- package/.eslintignore +2 -0
- package/.eslintrc +57 -0
- package/.husky/pre-commit +7 -0
- package/.husky/pre-push +6 -0
- package/.prettierignore +14 -0
- package/.turbo.json +18 -0
- package/.yarn/install-state.gz +0 -0
- package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +541 -0
- package/.yarn/plugins/@yarnpkg/plugin-outdated.cjs +35 -0
- package/.yarn/releases/yarn-3.6.1.cjs +874 -0
- package/.yarnrc.yml +9 -0
- package/LICENSE +401 -0
- package/README.md +26 -0
- package/__mocks__/react-i18next.js +55 -0
- package/e2e/README.md +115 -0
- package/e2e/core/global-setup.ts +32 -0
- package/e2e/core/index.ts +1 -0
- package/e2e/core/test.ts +20 -0
- package/e2e/fixtures/api.ts +26 -0
- package/e2e/fixtures/index.ts +1 -0
- package/e2e/pages/home-page.ts +9 -0
- package/e2e/pages/index.ts +1 -0
- package/e2e/specs/sample-test.spec.ts +11 -0
- package/e2e/support/github/Dockerfile +34 -0
- package/e2e/support/github/docker-compose.yml +24 -0
- package/e2e/support/github/run-e2e-docker-env.sh +49 -0
- package/example.env +6 -0
- package/i18next-parser.config.js +89 -0
- package/jest.config.js +31 -0
- package/package.json +108 -0
- package/playwright.config.ts +32 -0
- package/src/Extensions/AppointmentTabExt.tsx +23 -0
- package/src/Extensions/DemandTabExt.tsx +14 -0
- package/src/Extensions/MeetIframeExt.tsx +14 -0
- package/src/Extensions/ValidateDemandFormExt.tsx +14 -0
- package/src/assets/img/Logo-texte.png +0 -0
- package/src/assets/img/Logo-texte.sim.white.png +0 -0
- package/src/assets/img/Logo-texte.white.png +0 -0
- package/src/assets/img/Logo.png +0 -0
- package/src/assets/img/favicon.ico +0 -0
- package/src/components/Appointment/index.scss +91 -0
- package/src/components/Appointment/index.tsx +207 -0
- package/src/components/Appointment/menu.scss +7 -0
- package/src/components/Appointment/menu.tsx +48 -0
- package/src/components/Appointment/tab.tsx +162 -0
- package/src/components/Demand/form.scss +19 -0
- package/src/components/Demand/form.tsx +236 -0
- package/src/components/Demand/index.tsx +0 -0
- package/src/components/Demand/tab.scss +145 -0
- package/src/components/Demand/tab.tsx +315 -0
- package/src/components/EmptyLayout/index.scss +69 -0
- package/src/components/EmptyLayout/index.tsx +32 -0
- package/src/components/MeetIframe/index.scss +56 -0
- package/src/components/MeetIframe/index.tsx +117 -0
- package/src/config-schema.ts +45 -0
- package/src/dashboard.meta.ts +12 -0
- package/src/declarations.d.ts +6 -0
- package/src/index.ts +75 -0
- package/src/pages/home/home.component.tsx +8 -0
- package/src/privileges/doctor.ts +213 -0
- package/src/repositories/Opencare/index.ts +12 -0
- package/src/repositories/Opencare/prodRepository.ts +176 -0
- package/src/repositories/Opencare/repository.ts +34 -0
- package/src/repositories/TypeRepository.ts +1 -0
- package/src/repositories/env.ts +7 -0
- package/src/repositories/errors.ts +13 -0
- package/src/root.component.tsx +46 -0
- package/src/root.scss +15 -0
- package/src/root.test.tsx +54 -0
- package/src/routes.json +44 -0
- package/src/services/doctor.ts +165 -0
- package/src/setup-tests.ts +1 -0
- package/src/utils.ts +41 -0
- package/translations/en.json +1 -0
- package/translations/es.json +24 -0
- package/translations/fr.json +24 -0
- package/translations/he.json +24 -0
- package/translations/km.json +24 -0
- package/tsconfig.json +24 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import {
|
|
4
|
+
ContentSwitcher,
|
|
5
|
+
DataTableSkeleton,
|
|
6
|
+
Layer,
|
|
7
|
+
Switch,
|
|
8
|
+
Tile,
|
|
9
|
+
} from "@carbon/react";
|
|
10
|
+
import {
|
|
11
|
+
getCurrentUser,
|
|
12
|
+
useConfig,
|
|
13
|
+
useLayoutType,
|
|
14
|
+
useSession,
|
|
15
|
+
//launchWorkspaces
|
|
16
|
+
} from "@openmrs/esm-framework";
|
|
17
|
+
import PatientAppointmentsTable from "./tab";
|
|
18
|
+
import styles from "./index.scss";
|
|
19
|
+
import DoctorService, { AppointmentTypes } from "../../services/doctor";
|
|
20
|
+
import env from "../../repositories/env";
|
|
21
|
+
import { MeetIframe } from "../MeetIframe";
|
|
22
|
+
|
|
23
|
+
interface PatientAppointmentsBaseProps {
|
|
24
|
+
patientUuid: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const PatientAppointmentsBase: React.FC<PatientAppointmentsBaseProps> = ({
|
|
28
|
+
patientUuid,
|
|
29
|
+
}) => {
|
|
30
|
+
//I. hooks
|
|
31
|
+
const { t } = useTranslation();
|
|
32
|
+
const isTablet = useLayoutType() === "tablet";
|
|
33
|
+
const [contentSwitcherValue, setContentSwitcherValue] = useState(
|
|
34
|
+
AppointmentTypes.UPCOMING
|
|
35
|
+
);
|
|
36
|
+
const [loading, setLoading] = useState(true);
|
|
37
|
+
const [error, setError] = useState(false);
|
|
38
|
+
const [appointments, setAppointments] = useState<Map<number, Array<any>>>(
|
|
39
|
+
new Map()
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const [tokenNextcloud, setTokenNextcloud] = useState("");
|
|
43
|
+
|
|
44
|
+
const { user } = useSession();
|
|
45
|
+
|
|
46
|
+
const [url, setUrl] = useState("");
|
|
47
|
+
|
|
48
|
+
//recupération de la configuration
|
|
49
|
+
const conf = useConfig();
|
|
50
|
+
|
|
51
|
+
// update env variable
|
|
52
|
+
|
|
53
|
+
env.API_HOST = conf["API_HOST"];
|
|
54
|
+
env.API_PASSWORD = conf["API_PASSWORD"];
|
|
55
|
+
env.API_PORT = conf["API_PORT"];
|
|
56
|
+
env.API_USER = conf["API_USER"];
|
|
57
|
+
env.API_SECURE = conf["API_SECURE"];
|
|
58
|
+
const doctorService = useMemo(() => DoctorService.getInstance(), []);
|
|
59
|
+
|
|
60
|
+
// chargement des appointements
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const fun = async () => {
|
|
63
|
+
setLoading(true);
|
|
64
|
+
setError(false);
|
|
65
|
+
await doctorService
|
|
66
|
+
.getAppointments(patientUuid, user.person.uuid)
|
|
67
|
+
.then((appointments) => {
|
|
68
|
+
if (appointments) {
|
|
69
|
+
setAppointments(appointments);
|
|
70
|
+
} else {
|
|
71
|
+
setError(true);
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.finally(() => {
|
|
75
|
+
setLoading(false);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
fun();
|
|
79
|
+
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
81
|
+
return () => {};
|
|
82
|
+
}, [doctorService, patientUuid, user.person.uuid]);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const fun = async () => {
|
|
86
|
+
setLoading(true);
|
|
87
|
+
setError(false);
|
|
88
|
+
await doctorService
|
|
89
|
+
.getTokenNextcloud(patientUuid)
|
|
90
|
+
.then((token) => {
|
|
91
|
+
setTokenNextcloud(token);
|
|
92
|
+
})
|
|
93
|
+
.catch((e) => console.error(e));
|
|
94
|
+
};
|
|
95
|
+
fun();
|
|
96
|
+
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
98
|
+
return () => {};
|
|
99
|
+
}, [doctorService, patientUuid]);
|
|
100
|
+
|
|
101
|
+
const handleUrlMeeting = useCallback((url: string) => {
|
|
102
|
+
setUrl(url);
|
|
103
|
+
/* launchWorkspace("opencare-meet-iframe", {
|
|
104
|
+
url,
|
|
105
|
+
context: "view",
|
|
106
|
+
}); */
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
// II. returns
|
|
110
|
+
if (loading) {
|
|
111
|
+
return <DataTableSkeleton role="progressbar" compact={!isTablet} zebra />;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (error) {
|
|
115
|
+
return <span>Error</span>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<>
|
|
120
|
+
<div className={styles.contentSwitcherWrapper}>
|
|
121
|
+
<ContentSwitcher
|
|
122
|
+
size={isTablet ? "md" : "sm"}
|
|
123
|
+
onChange={({ index }) => {
|
|
124
|
+
setContentSwitcherValue(index);
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<Switch name={"upcoming"} text={t("upcoming", "Upcoming")} />
|
|
128
|
+
<Switch name={"today"} text={t("today", "Today")} />
|
|
129
|
+
<Switch name={"past"} text={t("past", "Past")} />
|
|
130
|
+
</ContentSwitcher>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{(() => {
|
|
134
|
+
if (appointments.get(contentSwitcherValue).length > 0) {
|
|
135
|
+
return (
|
|
136
|
+
<PatientAppointmentsTable
|
|
137
|
+
appointments={appointments.get(contentSwitcherValue)}
|
|
138
|
+
handleUrlMeeting={handleUrlMeeting}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (contentSwitcherValue === AppointmentTypes.UPCOMING) {
|
|
143
|
+
return (
|
|
144
|
+
<Layer>
|
|
145
|
+
<Tile className={styles.tile}>
|
|
146
|
+
<p className={styles.content}>
|
|
147
|
+
{t(
|
|
148
|
+
"noUpcomingAppointmentsForPatient",
|
|
149
|
+
"There are no upcoming appointments to display for this patient"
|
|
150
|
+
)}
|
|
151
|
+
</p>
|
|
152
|
+
</Tile>
|
|
153
|
+
</Layer>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (contentSwitcherValue === AppointmentTypes.TODAY) {
|
|
157
|
+
return (
|
|
158
|
+
<Layer>
|
|
159
|
+
<Tile className={styles.tile}>
|
|
160
|
+
<p className={styles.content}>
|
|
161
|
+
{
|
|
162
|
+
"There are no appointments scheduled for today to display for this patient"
|
|
163
|
+
}
|
|
164
|
+
</p>
|
|
165
|
+
</Tile>
|
|
166
|
+
</Layer>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (contentSwitcherValue === AppointmentTypes.PAST) {
|
|
170
|
+
return (
|
|
171
|
+
<Layer>
|
|
172
|
+
<Tile className={styles.tile}>
|
|
173
|
+
<p className={styles.content}>
|
|
174
|
+
{"There are no past appointments to display for this patient"}
|
|
175
|
+
</p>
|
|
176
|
+
</Tile>
|
|
177
|
+
</Layer>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (url != "") {
|
|
182
|
+
return (
|
|
183
|
+
<iframe
|
|
184
|
+
title="Web Meeting"
|
|
185
|
+
src={url}
|
|
186
|
+
style={{ width: "100%", height: "500px" }}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
})()}
|
|
191
|
+
|
|
192
|
+
{(() => {
|
|
193
|
+
if (url != "") {
|
|
194
|
+
return (
|
|
195
|
+
<MeetIframe
|
|
196
|
+
url={url}
|
|
197
|
+
username={user.display}
|
|
198
|
+
token={tokenNextcloud}
|
|
199
|
+
/>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
})()}
|
|
203
|
+
</>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export default PatientAppointmentsBase;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
|
|
4
|
+
import { Layer, OverflowMenu, OverflowMenuItem } from "@carbon/react";
|
|
5
|
+
import { useLayoutType } from "@openmrs/esm-framework";
|
|
6
|
+
import styles from "./menu.scss";
|
|
7
|
+
|
|
8
|
+
interface appointmentsActionMenuProps {
|
|
9
|
+
appointment: any;
|
|
10
|
+
patientUuid: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const PatientAppointmentsActionMenu = ({
|
|
14
|
+
appointment,
|
|
15
|
+
patientUuid,
|
|
16
|
+
}: appointmentsActionMenuProps) => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
const isTablet = useLayoutType() === "tablet";
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Layer className={styles.layer}>
|
|
22
|
+
<OverflowMenu
|
|
23
|
+
aria-label="Edit or delete appointment"
|
|
24
|
+
size={isTablet ? "lg" : "sm"}
|
|
25
|
+
flipped
|
|
26
|
+
align="left"
|
|
27
|
+
>
|
|
28
|
+
<OverflowMenuItem
|
|
29
|
+
className={styles.menuItem}
|
|
30
|
+
id="editAppointment"
|
|
31
|
+
itemText={t("edit", "Edit")}
|
|
32
|
+
/>
|
|
33
|
+
<OverflowMenuItem
|
|
34
|
+
className={styles.menuItem}
|
|
35
|
+
id="roomAppointment"
|
|
36
|
+
itemText={t("room", "Go")}
|
|
37
|
+
/>
|
|
38
|
+
<OverflowMenuItem
|
|
39
|
+
className={styles.menuItem}
|
|
40
|
+
id="cancelAppointment"
|
|
41
|
+
itemText={t("cancel", "Cancel")}
|
|
42
|
+
isDelete={true}
|
|
43
|
+
hasDivider
|
|
44
|
+
/>
|
|
45
|
+
</OverflowMenu>
|
|
46
|
+
</Layer>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import {
|
|
4
|
+
DataTable,
|
|
5
|
+
type DataTableHeader,
|
|
6
|
+
Table,
|
|
7
|
+
Layer,
|
|
8
|
+
TableCell,
|
|
9
|
+
TableContainer,
|
|
10
|
+
TableBody,
|
|
11
|
+
TableHead,
|
|
12
|
+
TableHeader,
|
|
13
|
+
TableRow,
|
|
14
|
+
Pagination,
|
|
15
|
+
} from "@carbon/react";
|
|
16
|
+
import {
|
|
17
|
+
formatDatetime,
|
|
18
|
+
isDesktop,
|
|
19
|
+
parseDate,
|
|
20
|
+
useLayoutType,
|
|
21
|
+
usePagination,
|
|
22
|
+
} from "@openmrs/esm-framework";
|
|
23
|
+
import { Button } from "@carbon/react";
|
|
24
|
+
import { getPageSizes } from "../../utils";
|
|
25
|
+
|
|
26
|
+
interface AppointmentTableProps {
|
|
27
|
+
appointments: Array<any>;
|
|
28
|
+
handleUrlMeeting: (url: string) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const PatientAppointmentsTable: React.FC<AppointmentTableProps> = ({
|
|
32
|
+
appointments,
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
34
|
+
handleUrlMeeting = (url) => {},
|
|
35
|
+
}) => {
|
|
36
|
+
// I. hooks
|
|
37
|
+
|
|
38
|
+
const { t } = useTranslation();
|
|
39
|
+
const layout = useLayoutType();
|
|
40
|
+
const responsiveSize = isDesktop(layout) ? "sm" : "lg";
|
|
41
|
+
const [pageSize, setPageSize] = useState(5);
|
|
42
|
+
|
|
43
|
+
const tableRows = useMemo(
|
|
44
|
+
() =>
|
|
45
|
+
appointments.map((appointment) => {
|
|
46
|
+
return {
|
|
47
|
+
id: appointment.uuid,
|
|
48
|
+
date: formatDatetime(parseDate(appointment.startDateTime), {
|
|
49
|
+
mode: "wide",
|
|
50
|
+
}),
|
|
51
|
+
location: appointment?.location ? appointment?.location : "——",
|
|
52
|
+
service: appointment.service,
|
|
53
|
+
status: appointment.status,
|
|
54
|
+
type: appointment.appointmentKind
|
|
55
|
+
? appointment.appointmentKind
|
|
56
|
+
: "——",
|
|
57
|
+
notes: appointment.comments ? appointment.comments : "——",
|
|
58
|
+
};
|
|
59
|
+
}),
|
|
60
|
+
[appointments]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const mapAppointments = useMemo(() => {
|
|
64
|
+
const map = new Map();
|
|
65
|
+
appointments.forEach((appointment) => {
|
|
66
|
+
map.set(appointment.uuid, appointment);
|
|
67
|
+
});
|
|
68
|
+
return map;
|
|
69
|
+
}, [appointments]);
|
|
70
|
+
|
|
71
|
+
const { results, goTo, currentPage } = usePagination(tableRows, pageSize);
|
|
72
|
+
|
|
73
|
+
const tableHeaders: Array<typeof DataTableHeader> = useMemo(
|
|
74
|
+
() => [
|
|
75
|
+
{ key: "date", header: t("date", "Date") },
|
|
76
|
+
{ key: "location", header: t("location", "Location") },
|
|
77
|
+
{ key: "service", header: t("service", "Service") },
|
|
78
|
+
{ key: "status", header: t("status", "Status") },
|
|
79
|
+
{ key: "type", header: t("type", "Type") },
|
|
80
|
+
{ key: "notes", header: t("notes", "Notes") },
|
|
81
|
+
],
|
|
82
|
+
[t]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const HandleJoin = useCallback(
|
|
86
|
+
(appointment) => {
|
|
87
|
+
handleUrlMeeting(appointment.linkRoom);
|
|
88
|
+
},
|
|
89
|
+
[handleUrlMeeting]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Layer style={{ marginBottom: "1rem" }}>
|
|
94
|
+
<DataTable
|
|
95
|
+
rows={results}
|
|
96
|
+
headers={tableHeaders}
|
|
97
|
+
isSortable
|
|
98
|
+
size={responsiveSize}
|
|
99
|
+
useZebraStyles
|
|
100
|
+
>
|
|
101
|
+
{({ rows, headers, getHeaderProps, getTableProps }) => (
|
|
102
|
+
<TableContainer>
|
|
103
|
+
<Table {...getTableProps()}>
|
|
104
|
+
<TableHead>
|
|
105
|
+
<TableRow>
|
|
106
|
+
{headers.map((header) => (
|
|
107
|
+
<TableHeader
|
|
108
|
+
{...getHeaderProps({
|
|
109
|
+
header,
|
|
110
|
+
isSortable: header.isSortable,
|
|
111
|
+
})}
|
|
112
|
+
>
|
|
113
|
+
{header.header?.content ?? header.header}
|
|
114
|
+
</TableHeader>
|
|
115
|
+
))}
|
|
116
|
+
<TableHeader />
|
|
117
|
+
</TableRow>
|
|
118
|
+
</TableHead>
|
|
119
|
+
<TableBody>
|
|
120
|
+
{rows.map((row, i) => (
|
|
121
|
+
<TableRow key={row.id}>
|
|
122
|
+
{row.cells.map((cell) => (
|
|
123
|
+
<TableCell key={cell.id}>
|
|
124
|
+
{cell.value?.content ?? cell.value}
|
|
125
|
+
</TableCell>
|
|
126
|
+
))}
|
|
127
|
+
<TableCell className="cds--table-column-menu">
|
|
128
|
+
{/* <PatientAppointmentsActionMenu
|
|
129
|
+
appointment={appointments[i]}
|
|
130
|
+
patientUuid={patientUuid}
|
|
131
|
+
/> */}
|
|
132
|
+
<Button
|
|
133
|
+
size="small"
|
|
134
|
+
onClick={() => {
|
|
135
|
+
HandleJoin(mapAppointments.get(row.id));
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
Joindre
|
|
139
|
+
</Button>
|
|
140
|
+
</TableCell>
|
|
141
|
+
</TableRow>
|
|
142
|
+
))}
|
|
143
|
+
</TableBody>
|
|
144
|
+
</Table>
|
|
145
|
+
</TableContainer>
|
|
146
|
+
)}
|
|
147
|
+
</DataTable>
|
|
148
|
+
<Pagination
|
|
149
|
+
page={currentPage}
|
|
150
|
+
pageSize={pageSize}
|
|
151
|
+
pageSizes={getPageSizes(results, 5) ?? []}
|
|
152
|
+
onChange={({ page, pageSize }) => {
|
|
153
|
+
goTo(page);
|
|
154
|
+
setPageSize(pageSize);
|
|
155
|
+
}}
|
|
156
|
+
totalItems={results.length}
|
|
157
|
+
/>
|
|
158
|
+
</Layer>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export default PatientAppointmentsTable;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.formGroupsContainer {
|
|
2
|
+
margin: 1rem
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.formGroupContainer {
|
|
6
|
+
margin-top: 0.5rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.buttonContainer {
|
|
10
|
+
display: flex;
|
|
11
|
+
width: 100%;
|
|
12
|
+
background-color: #0d8888;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.formButton {
|
|
16
|
+
max-width: unset !important;
|
|
17
|
+
flex: 1;
|
|
18
|
+
height: 4rem;
|
|
19
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Form,
|
|
4
|
+
FormGroup,
|
|
5
|
+
Dropdown,
|
|
6
|
+
DatePicker,
|
|
7
|
+
DatePickerInput,
|
|
8
|
+
NumberInput,
|
|
9
|
+
TimePicker,
|
|
10
|
+
TimePickerSelect,
|
|
11
|
+
SelectItem,
|
|
12
|
+
Button,
|
|
13
|
+
} from "@carbon/react";
|
|
14
|
+
import { Layer, Tile } from "@carbon/react";
|
|
15
|
+
import { showToast, useConfig /*useLayoutType*/ } from "@openmrs/esm-framework";
|
|
16
|
+
import styles from "./form.scss";
|
|
17
|
+
import env from "../../repositories/env";
|
|
18
|
+
import DoctorService from "../../services/doctor";
|
|
19
|
+
//import { inLocalTimeOffsetUTC } from "../../utils";
|
|
20
|
+
|
|
21
|
+
export interface ValidateDemandFormProps {
|
|
22
|
+
demand: any;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Doctor {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const ValidateDemandForm: React.FC<ValidateDemandFormProps> = ({
|
|
32
|
+
demand,
|
|
33
|
+
onClose,
|
|
34
|
+
}) => {
|
|
35
|
+
//const isTablet = useLayoutType() === "tablet";
|
|
36
|
+
|
|
37
|
+
const [selectedDoctor, setSelectedDoctor] = useState<Doctor | null>(null);
|
|
38
|
+
const [startDate, setStartDate] = useState<Date | null>(null);
|
|
39
|
+
const [startTime, setStartTime] = useState<string>("00:00");
|
|
40
|
+
const [ampm, setAmpm] = useState<string>("AM");
|
|
41
|
+
const [duration, setDuration] = useState<number>(30);
|
|
42
|
+
const [doctors, setDoctors] = useState([]);
|
|
43
|
+
//recupération de la configuration
|
|
44
|
+
|
|
45
|
+
const [processing, setProcessing] = useState(false);
|
|
46
|
+
const conf = useConfig();
|
|
47
|
+
|
|
48
|
+
// update env variable
|
|
49
|
+
|
|
50
|
+
env.API_HOST = conf["API_HOST"];
|
|
51
|
+
env.API_PASSWORD = conf["API_PASSWORD"];
|
|
52
|
+
env.API_PORT = conf["API_PORT"];
|
|
53
|
+
env.API_USER = conf["API_USER"];
|
|
54
|
+
env.API_SECURE = conf["API_SECURE"];
|
|
55
|
+
const doctorService = useMemo(() => DoctorService.getInstance(), []);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const fun = async () => {
|
|
59
|
+
await doctorService.getProviders().then((doctors) => {
|
|
60
|
+
if (doctors) {
|
|
61
|
+
setDoctors(doctors);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
fun();
|
|
66
|
+
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
68
|
+
return () => {};
|
|
69
|
+
}, [doctorService]);
|
|
70
|
+
|
|
71
|
+
const handleDoctorChange = useCallback(
|
|
72
|
+
(selectedItem: any) => {
|
|
73
|
+
const doctor = doctors.find(
|
|
74
|
+
(doc) => doc.name === selectedItem.selectedItem
|
|
75
|
+
);
|
|
76
|
+
setSelectedDoctor(doctor || null);
|
|
77
|
+
},
|
|
78
|
+
[doctors]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const handleStartDateChange = useCallback((date: Date[]) => {
|
|
82
|
+
setStartDate(date[0] || null);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const handleStartTimeChange = useCallback((event: any) => {
|
|
86
|
+
setStartTime(event.target.value);
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
const handleAmpmChange = useCallback((event: any) => {
|
|
90
|
+
setAmpm(event.target.value);
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
const handleDurationChange = useCallback((event: any) => {
|
|
94
|
+
setDuration(event.target.value);
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const handleSubmit = async () => {
|
|
98
|
+
if (startDate && startTime && duration > 0) {
|
|
99
|
+
if (!processing) {
|
|
100
|
+
setProcessing(true);
|
|
101
|
+
let [hours, minutes] = startTime.split(":").map(Number);
|
|
102
|
+
if (ampm === "PM" && hours < 12) {
|
|
103
|
+
hours += 12;
|
|
104
|
+
}
|
|
105
|
+
if (ampm === "AM" && hours === 12) {
|
|
106
|
+
hours = 0;
|
|
107
|
+
}
|
|
108
|
+
const combinedDateTime = new Date(startDate);
|
|
109
|
+
combinedDateTime.setHours(hours, minutes);
|
|
110
|
+
|
|
111
|
+
// Gestion de la soumission du formulaire
|
|
112
|
+
await doctorService
|
|
113
|
+
.validateDemand(
|
|
114
|
+
demand.id,
|
|
115
|
+
selectedDoctor.id,
|
|
116
|
+
combinedDateTime,
|
|
117
|
+
duration
|
|
118
|
+
)
|
|
119
|
+
.then((res) => {
|
|
120
|
+
/* const res = true; */
|
|
121
|
+
if (res) {
|
|
122
|
+
showToast({
|
|
123
|
+
description: `the demand initiated by ${demand.patient} have been validated`,
|
|
124
|
+
kind: "success",
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
showToast({
|
|
128
|
+
description: `error during validation`,
|
|
129
|
+
kind: "error",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
.finally(() => {
|
|
134
|
+
setProcessing(false);
|
|
135
|
+
if (onClose) {
|
|
136
|
+
onClose();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const handleCancel = useCallback(() => {
|
|
144
|
+
// Gestion de l'annulation du formulaire
|
|
145
|
+
/* showToast({ description: `cancel` }); */
|
|
146
|
+
if (!processing) {
|
|
147
|
+
if (onClose) {
|
|
148
|
+
onClose();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [onClose, processing]);
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<Layer>
|
|
155
|
+
<Tile>
|
|
156
|
+
<h2>Validate demand</h2>
|
|
157
|
+
</Tile>
|
|
158
|
+
<Form>
|
|
159
|
+
<div className={styles.formGroupsContainer}>
|
|
160
|
+
<FormGroup
|
|
161
|
+
legendText="Provider"
|
|
162
|
+
className={styles.formGroupContainer}
|
|
163
|
+
>
|
|
164
|
+
<Dropdown
|
|
165
|
+
id="doctor-dropdown"
|
|
166
|
+
items={doctors.map((doc) => doc.name)}
|
|
167
|
+
onChange={handleDoctorChange}
|
|
168
|
+
/>
|
|
169
|
+
</FormGroup>
|
|
170
|
+
|
|
171
|
+
<FormGroup
|
|
172
|
+
legendText="StartDateTime"
|
|
173
|
+
className={styles.formGroupContainer}
|
|
174
|
+
style={{ display: "flex" }}
|
|
175
|
+
>
|
|
176
|
+
<DatePicker
|
|
177
|
+
dateFormat="d/m/Y"
|
|
178
|
+
datePickerType="single"
|
|
179
|
+
onChange={handleStartDateChange}
|
|
180
|
+
>
|
|
181
|
+
<DatePickerInput
|
|
182
|
+
id="start-date-picker"
|
|
183
|
+
placeholder="jj/mm/aaaa"
|
|
184
|
+
/>
|
|
185
|
+
</DatePicker>
|
|
186
|
+
<TimePicker
|
|
187
|
+
id="start-time-picker"
|
|
188
|
+
onChange={handleStartTimeChange}
|
|
189
|
+
value={startTime}
|
|
190
|
+
/>
|
|
191
|
+
<TimePickerSelect
|
|
192
|
+
id="time-picker-select-ampm"
|
|
193
|
+
labelText="AM/PM"
|
|
194
|
+
onChange={handleAmpmChange}
|
|
195
|
+
>
|
|
196
|
+
<SelectItem value="AM" text="AM" />
|
|
197
|
+
<SelectItem value="PM" text="PM" />
|
|
198
|
+
</TimePickerSelect>
|
|
199
|
+
</FormGroup>
|
|
200
|
+
|
|
201
|
+
<FormGroup
|
|
202
|
+
legendText="Duration (minutes)"
|
|
203
|
+
className={styles.formGroupContainer}
|
|
204
|
+
>
|
|
205
|
+
<NumberInput
|
|
206
|
+
id="duration-input"
|
|
207
|
+
min={10}
|
|
208
|
+
value={duration}
|
|
209
|
+
onChange={handleDurationChange}
|
|
210
|
+
/>
|
|
211
|
+
</FormGroup>
|
|
212
|
+
</div>
|
|
213
|
+
<div className={styles.buttonContainer}>
|
|
214
|
+
<Button
|
|
215
|
+
kind="secondary"
|
|
216
|
+
size="large"
|
|
217
|
+
className={styles.formButton}
|
|
218
|
+
onClick={handleCancel}
|
|
219
|
+
disable={processing}
|
|
220
|
+
>
|
|
221
|
+
Cancel
|
|
222
|
+
</Button>
|
|
223
|
+
<Button
|
|
224
|
+
kind="primary"
|
|
225
|
+
size="large"
|
|
226
|
+
className={styles.formButton}
|
|
227
|
+
disable={processing}
|
|
228
|
+
onClick={handleSubmit}
|
|
229
|
+
>
|
|
230
|
+
{processing ? "Validating ..." : "Validate"}
|
|
231
|
+
</Button>
|
|
232
|
+
</div>
|
|
233
|
+
</Form>
|
|
234
|
+
</Layer>
|
|
235
|
+
);
|
|
236
|
+
};
|
|
File without changes
|