@openmrs/esm-fast-data-entry-app 1.0.1-pre.17 → 1.0.1-pre.21

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 (32) hide show
  1. package/dist/132.js +1 -1
  2. package/dist/229.js +1 -0
  3. package/dist/255.js +1 -1
  4. package/dist/32.js +1 -1
  5. package/dist/574.js +1 -1
  6. package/dist/617.js +1 -1
  7. package/dist/640.js +1 -0
  8. package/dist/804.js +1 -1
  9. package/dist/main.js +1 -1
  10. package/dist/openmrs-esm-fast-data-entry-app.js +1 -1
  11. package/dist/openmrs-esm-fast-data-entry-app.js.buildmanifest.json +65 -42
  12. package/dist/openmrs-esm-fast-data-entry-app.old +1 -1
  13. package/package.json +3 -3
  14. package/src/CancelModal.tsx +48 -0
  15. package/src/CompleteModal.tsx +46 -0
  16. package/src/FormBootstrap.tsx +3 -0
  17. package/src/context/FormWorkflowReducer.ts +4 -0
  18. package/src/context/GroupFormWorkflowContext.tsx +31 -2
  19. package/src/context/GroupFormWorkflowReducer.ts +127 -1
  20. package/src/form-entry-workflow/FormEntryWorkflow.tsx +15 -82
  21. package/src/group-form-entry-workflow/GroupFormEntryWorkflow.tsx +11 -398
  22. package/src/group-form-entry-workflow/GroupSessionWorkspace.tsx +227 -0
  23. package/src/group-form-entry-workflow/SessionDetailsForm.tsx +122 -0
  24. package/src/group-form-entry-workflow/SessionMetaWorkspace.tsx +107 -0
  25. package/src/group-form-entry-workflow/attendance-table/AttendanceTable.tsx +105 -0
  26. package/src/group-form-entry-workflow/attendance-table/index.ts +1 -0
  27. package/src/group-form-entry-workflow/group-display-header/GroupDisplayHeader.tsx +1 -9
  28. package/src/group-form-entry-workflow/group-search-header/GroupSearchHeader.tsx +0 -3
  29. package/src/hooks/useGetSystemSetting.ts +38 -0
  30. package/src/hooks/useStartVisit.ts +83 -0
  31. package/translations/en.json +7 -1
  32. package/dist/906.js +0 -1
@@ -0,0 +1,122 @@
1
+ import {
2
+ Layer,
3
+ Tile,
4
+ TextInput,
5
+ TextArea,
6
+ DatePicker,
7
+ DatePickerInput,
8
+ } from "@carbon/react";
9
+ import React from "react";
10
+ import styles from "./styles.scss";
11
+ import { useTranslation } from "react-i18next";
12
+ import { Controller, useFormContext } from "react-hook-form";
13
+ import { AttendanceTable } from "./attendance-table";
14
+
15
+ const SessionDetailsForm = () => {
16
+ const { t } = useTranslation();
17
+ const {
18
+ register,
19
+ formState: { errors },
20
+ control,
21
+ } = useFormContext();
22
+
23
+ return (
24
+ <div className={styles.formSection}>
25
+ <h4>{t("sessionDetails", "1. Session details")}</h4>
26
+ <div>
27
+ <p>
28
+ {t(
29
+ "allFieldsRequired",
30
+ "All fields are required unless marked optional"
31
+ )}
32
+ </p>
33
+ </div>
34
+ <Layer>
35
+ <Tile className={styles.formSectionTile}>
36
+ <Layer>
37
+ <div
38
+ style={{
39
+ display: "flex",
40
+ flexDirection: "column",
41
+ rowGap: "1.5rem",
42
+ }}
43
+ >
44
+ <TextInput
45
+ id="text"
46
+ type="text"
47
+ labelText={t("sessionName", "Session Name")}
48
+ {...register("sessionName", { required: true })}
49
+ invalid={errors.sessionName}
50
+ invalidText={"This field is required"}
51
+ />
52
+ <TextInput
53
+ id="text"
54
+ type="text"
55
+ labelText={t("practitionerName", "Practitioner Name")}
56
+ {...register("practitionerName", { required: true })}
57
+ invalid={errors.practitionerName}
58
+ invalidText={"This field is required"}
59
+ />
60
+ <Controller
61
+ name="sessionDate"
62
+ control={control}
63
+ rules={{ required: true }}
64
+ render={({ field }) => (
65
+ <DatePicker
66
+ datePickerType="single"
67
+ size="md"
68
+ maxDate={new Date()}
69
+ {...field}
70
+ >
71
+ <DatePickerInput
72
+ id="session-date"
73
+ labelText={t("sessionDate", "Session Date")}
74
+ placeholder="mm/dd/yyyy"
75
+ size="md"
76
+ invalid={errors.sessionDate}
77
+ invalidText={"This field is required"}
78
+ />
79
+ </DatePicker>
80
+ )}
81
+ />
82
+ <TextArea
83
+ id="text"
84
+ type="text"
85
+ labelText={t("sessionNotes", "Session Notes")}
86
+ {...register("sessionNotes", { required: true })}
87
+ invalid={errors.sessionNotes}
88
+ invalidText={"This field is required"}
89
+ />
90
+ </div>
91
+ </Layer>
92
+ </Tile>
93
+ </Layer>
94
+ <h4>{t("sessionParticipants", "2. Session participants")}</h4>
95
+ <div>
96
+ <p>
97
+ {t(
98
+ "markAbsentPatients",
99
+ "The patients in this group. Patients that are not present in the session should be marked as absent."
100
+ )}
101
+ </p>
102
+ </div>
103
+ <Layer>
104
+ <Tile className={styles.formSectionTile}>
105
+ <Layer>
106
+ <div
107
+ style={{
108
+ display: "flex",
109
+ flexDirection: "column",
110
+ rowGap: "1.5rem",
111
+ }}
112
+ >
113
+ <AttendanceTable />
114
+ </div>
115
+ </Layer>
116
+ </Tile>
117
+ </Layer>
118
+ </div>
119
+ );
120
+ };
121
+
122
+ export default SessionDetailsForm;
@@ -0,0 +1,107 @@
1
+ import { Button } from "@carbon/react";
2
+ import React, { useContext, useEffect, useState } from "react";
3
+ import styles from "./styles.scss";
4
+ import { useTranslation } from "react-i18next";
5
+ import GroupFormWorkflowContext from "../context/GroupFormWorkflowContext";
6
+ import { FormProvider, useForm, useFormContext } from "react-hook-form";
7
+ import CancelModal from "../CancelModal";
8
+ import SessionDetailsForm from "./SessionDetailsForm";
9
+
10
+ const NewGroupWorkflowButtons = () => {
11
+ const { t } = useTranslation();
12
+ const context = useContext(GroupFormWorkflowContext);
13
+ const { workflowState, patientUuids } = context;
14
+ const [cancelModalOpen, setCancelModalOpen] = useState(false);
15
+ if (workflowState !== "NEW_GROUP_SESSION") return null;
16
+
17
+ return (
18
+ <>
19
+ <div className={styles.rightPanelActionButtons}>
20
+ <Button kind="secondary" type="submit" disabled={!patientUuids.length}>
21
+ {t("createNewSession", "Create New Session")}
22
+ </Button>
23
+ <Button
24
+ kind="tertiary"
25
+ onClick={() => {
26
+ setCancelModalOpen(true);
27
+ }}
28
+ >
29
+ {t("cancel", "Cancel")}
30
+ </Button>
31
+ </div>
32
+ <CancelModal
33
+ open={cancelModalOpen}
34
+ setOpen={setCancelModalOpen}
35
+ context={context}
36
+ />
37
+ </>
38
+ );
39
+ };
40
+
41
+ const GroupIdField = () => {
42
+ const { t } = useTranslation();
43
+ const {
44
+ register,
45
+ formState: { errors },
46
+ setValue,
47
+ } = useFormContext();
48
+ const { activeGroupUuid } = useContext(GroupFormWorkflowContext);
49
+
50
+ useEffect(() => {
51
+ if (activeGroupUuid) setValue("groupUuid", activeGroupUuid);
52
+ }, [activeGroupUuid, setValue]);
53
+
54
+ return (
55
+ <>
56
+ <input
57
+ hidden
58
+ {...register("groupUuid", {
59
+ value: activeGroupUuid,
60
+ required: t("chooseGroupError", "Please choose a group."),
61
+ })}
62
+ />
63
+ {errors.groupUuid && !activeGroupUuid && (
64
+ <div className={styles.formError}>
65
+ {errors.groupUuid.message as string}
66
+ </div>
67
+ )}
68
+ </>
69
+ );
70
+ };
71
+
72
+ const SessionMetaWorkspace = () => {
73
+ const { t } = useTranslation();
74
+ const { setSessionMeta, workflowState } = useContext(
75
+ GroupFormWorkflowContext
76
+ );
77
+ const methods = useForm();
78
+
79
+ const onSubmit = (data) => {
80
+ const { sessionDate, ...rest } = data;
81
+ setSessionMeta({ ...rest, sessionDate: sessionDate[0] });
82
+ };
83
+
84
+ if (workflowState !== "NEW_GROUP_SESSION") return null;
85
+
86
+ return (
87
+ <FormProvider {...methods}>
88
+ <form onSubmit={methods.handleSubmit(onSubmit)}>
89
+ <div className={styles.workspace}>
90
+ <div className={styles.formMainContent}>
91
+ <div className={styles.formContainer}>
92
+ <SessionDetailsForm />
93
+ </div>
94
+ <div className={styles.rightPanel}>
95
+ <h4>{t("newGroupSession", "New Group Session")}</h4>
96
+ <GroupIdField />
97
+ <hr style={{ width: "100%" }} />
98
+ <NewGroupWorkflowButtons />
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </form>
103
+ </FormProvider>
104
+ );
105
+ };
106
+
107
+ export default SessionMetaWorkspace;
@@ -0,0 +1,105 @@
1
+ import React, { useContext } from "react";
2
+
3
+ import {
4
+ Checkbox,
5
+ SkeletonText,
6
+ Table,
7
+ TableHead,
8
+ TableRow,
9
+ TableHeader,
10
+ TableBody,
11
+ TableCell,
12
+ } from "@carbon/react";
13
+ import { useTranslation } from "react-i18next";
14
+ import GroupFormWorkflowContext from "../../context/GroupFormWorkflowContext";
15
+ import { useGetPatient } from "../../hooks";
16
+
17
+ const PatientRow = ({ patientUuid }) => {
18
+ const patient = useGetPatient(patientUuid);
19
+ const { patientUuids, addPatientUuid, removePatientUuid } = useContext(
20
+ GroupFormWorkflowContext
21
+ );
22
+ const givenName = patient?.name?.[0]?.given?.[0];
23
+ const familyName = patient?.name?.[0]?.family;
24
+ const identifier = patient?.identifier?.[0]?.value;
25
+
26
+ const handleOnChange = (e, { checked }) => {
27
+ if (checked) {
28
+ addPatientUuid(patientUuid);
29
+ } else {
30
+ removePatientUuid(patientUuid);
31
+ }
32
+ };
33
+
34
+ if (!patient) {
35
+ return (
36
+ <TableRow>
37
+ <TableCell>
38
+ <SkeletonText />
39
+ </TableCell>
40
+ <TableCell>
41
+ <SkeletonText />
42
+ </TableCell>
43
+ <TableCell>
44
+ <Checkbox diabled />
45
+ </TableCell>
46
+ </TableRow>
47
+ );
48
+ }
49
+
50
+ return (
51
+ <TableRow>
52
+ <TableCell>
53
+ {patient.display ||
54
+ patient.displayName ||
55
+ [givenName, familyName].join(" ")}
56
+ </TableCell>
57
+ <TableCell>{identifier}</TableCell>
58
+ <TableCell>
59
+ <Checkbox
60
+ checked={patientUuids.includes(patientUuid)}
61
+ labelText={patientUuid}
62
+ hideLabel
63
+ id={`${identifier}-attendance-checkbox`}
64
+ onChange={handleOnChange}
65
+ />
66
+ </TableCell>
67
+ </TableRow>
68
+ );
69
+ };
70
+
71
+ const AttendanceTable = () => {
72
+ const { t } = useTranslation();
73
+ const { activeGroupUuid, activeGroupMembers } = useContext(
74
+ GroupFormWorkflowContext
75
+ );
76
+
77
+ const headers = [
78
+ t("name", "Name"),
79
+ t("identifier", "Patient ID"),
80
+ t("patientIsPresent", "Patient is present"),
81
+ ];
82
+
83
+ if (!activeGroupUuid) {
84
+ return <div>{t("selectGroupFirst", "Please select a group first")}</div>;
85
+ }
86
+
87
+ return (
88
+ <Table>
89
+ <TableHead>
90
+ <TableRow>
91
+ {headers.map((header, index) => (
92
+ <TableHeader key={index}>{header}</TableHeader>
93
+ ))}
94
+ </TableRow>
95
+ </TableHead>
96
+ <TableBody>
97
+ {activeGroupMembers.map((patientUuid, index) => (
98
+ <PatientRow {...{ patientUuid }} key={index} />
99
+ ))}
100
+ </TableBody>
101
+ </Table>
102
+ );
103
+ };
104
+
105
+ export default AttendanceTable;
@@ -0,0 +1 @@
1
+ export { default as AttendanceTable } from "./AttendanceTable";
@@ -4,7 +4,6 @@ import { Events, Close } from "@carbon/react/icons";
4
4
  import styles from "./styles.scss";
5
5
  import { useTranslation } from "react-i18next";
6
6
  import GroupFormWorkflowContext from "../../context/GroupFormWorkflowContext";
7
- import { navigate } from "@openmrs/esm-framework";
8
7
 
9
8
  const GroupDisplayHeader = () => {
10
9
  const {
@@ -53,14 +52,7 @@ const GroupDisplayHeader = () => {
53
52
  </Button>
54
53
  </span>
55
54
  <span>
56
- <Button
57
- kind="ghost"
58
- onClick={() => {
59
- destroySession();
60
- // eslint-disable-next-line
61
- navigate({ to: "${openmrsSpaBase}/forms" });
62
- }}
63
- >
55
+ <Button kind="ghost" onClick={() => destroySession()}>
64
56
  {t("cancel", "Cancel")} <Close size={20} />
65
57
  </Button>
66
58
  </span>
@@ -6,7 +6,6 @@ import styles from "./styles.scss";
6
6
  import { useTranslation } from "react-i18next";
7
7
  import CompactGroupSearch from "../group-search/CompactGroupSearch";
8
8
  import AddGroupModal from "../../add-group-modal/AddGroupModal";
9
- import { navigate } from "@openmrs/esm-framework";
10
9
 
11
10
  const GroupSearchHeader = () => {
12
11
  const { t } = useTranslation();
@@ -35,8 +34,6 @@ const GroupSearchHeader = () => {
35
34
  kind="ghost"
36
35
  onClick={() => {
37
36
  destroySession();
38
- // eslint-disable-next-line
39
- navigate({ to: "${openmrsSpaBase}/forms" });
40
37
  }}
41
38
  >
42
39
  {t("cancel", "Cancel")} <Close size={20} />
@@ -0,0 +1,38 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import { openmrsFetch } from "@openmrs/esm-framework";
3
+
4
+ const useGetSystemSetting = (settingId) => {
5
+ const [isSubmitting, setIsSubmitting] = useState(false);
6
+ const [result, setResult] = useState(null);
7
+ const [error, setError] = useState(null);
8
+
9
+ const onResult = useCallback((result) => {
10
+ setIsSubmitting(false);
11
+ setError(false);
12
+ setResult(result);
13
+ }, []);
14
+
15
+ const onError = useCallback((error) => {
16
+ setIsSubmitting(false);
17
+ setResult(null);
18
+ setError(error);
19
+ }, []);
20
+
21
+ const getSetting = useCallback(() => {
22
+ openmrsFetch(`/ws/rest/v1/systemsetting?q=${settingId}&v=default`)
23
+ .then(onResult)
24
+ .catch(onError);
25
+ }, [onError, onResult, settingId]);
26
+
27
+ useEffect(() => {
28
+ getSetting();
29
+ }, [getSetting]);
30
+
31
+ return {
32
+ result,
33
+ error,
34
+ isSubmitting,
35
+ };
36
+ };
37
+
38
+ export default useGetSystemSetting;
@@ -0,0 +1,83 @@
1
+ import { useCallback, useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ showNotification,
5
+ showToast,
6
+ openmrsFetch,
7
+ } from "@openmrs/esm-framework";
8
+
9
+ const useStartVisit = ({
10
+ showSuccessNotification = true,
11
+ showErrorNotification = true,
12
+ }) => {
13
+ const { t } = useTranslation();
14
+ const [isSubmitting, setIsSubmitting] = useState(false);
15
+ const [success, setSuccess] = useState(null);
16
+ const [error, setError] = useState(null);
17
+
18
+ const onSave = useCallback(
19
+ (result) => {
20
+ setIsSubmitting(false);
21
+ setError(false);
22
+ setSuccess(result);
23
+ if (showSuccessNotification) {
24
+ showToast({
25
+ critical: true,
26
+ kind: "success",
27
+ description: t(
28
+ "visitStartedSuccessfully",
29
+ `${result?.data?.visitType?.display} started successfully`
30
+ ),
31
+ title: t("visitStarted", "Visit started"),
32
+ });
33
+ }
34
+ },
35
+ [t, showSuccessNotification]
36
+ );
37
+
38
+ const onError = useCallback(
39
+ (error) => {
40
+ setIsSubmitting(false);
41
+ setSuccess(false);
42
+ setError(error);
43
+ if (showErrorNotification) {
44
+ showNotification({
45
+ title: t("startVisitError", "Error starting visit"),
46
+ kind: "error",
47
+ critical: true,
48
+ description: error?.message,
49
+ });
50
+ }
51
+ },
52
+ [t, showErrorNotification]
53
+ );
54
+
55
+ const saveVisit = useCallback(
56
+ (data) => {
57
+ const payload = {
58
+ patient: data.patientUuid,
59
+ startDatetime: data.startDatetime,
60
+ stopDatetime: data.stopDatetime,
61
+ visitType: data.visitType,
62
+ // location: selectedLocation,
63
+ };
64
+ openmrsFetch("/ws/rest/v1/visit", {
65
+ method: "POST",
66
+ body: payload,
67
+ headers: { "Content-Type": "application/json" },
68
+ })
69
+ .then(onSave)
70
+ .catch(onError);
71
+ },
72
+ [onError, onSave]
73
+ );
74
+
75
+ return {
76
+ saveVisit,
77
+ success,
78
+ error,
79
+ isSubmitting,
80
+ };
81
+ };
82
+
83
+ export default useStartVisit;
@@ -23,7 +23,11 @@
23
23
  "goToForm": "Go To Form",
24
24
  "group": "Group",
25
25
  "groupNameError": "Please enter a group name.",
26
+ "identifier": "Identifier",
27
+ "markAbsentPatients":
28
+ "The patients in this group. Patients that are not present in the session should be marked as absent.",
26
29
  "members": "members",
30
+ "name": "Name",
27
31
  "newGroupName": "New Group Name",
28
32
  "nextPatient": "Next Patient",
29
33
  "noFormsFound": "No Forms To Show",
@@ -34,6 +38,7 @@
34
38
  "or": "or",
35
39
  "orLabelName": "OR label name",
36
40
  "patientsInGroup": "Patients in group",
41
+ "patientIsPreent": "Patient is present",
37
42
  "postError": "POST Error",
38
43
  "practitionerName": "Practitioner Name",
39
44
  "remove": "Remove",
@@ -45,7 +50,8 @@
45
50
  "searchResultsText": "search result(s)",
46
51
  "selectPatientFirst": "Please select a patient first",
47
52
  "sessionDate": "Session Date",
48
- "sessionDetails": "Session details",
53
+ "sessionDetails": "1. Session details",
54
+ "sessionParticipants": "2. Session participants",
49
55
  "sessionName": "Session Name",
50
56
  "sessionNotes": "Session Notes",
51
57
  "startGroupSession": "Start Group Session",