From 0daa8bc44d6f1abcbb610fd50c9c24e87239ba59 Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Wed, 9 Oct 2024 12:12:54 -0700 Subject: [PATCH 1/3] Fix missing I18n --- src/Components/Patient/PatientHome.tsx | 4 ++-- src/Locale/en.json | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index 2ee78b6bce1..f6fe4f28a72 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -318,11 +318,11 @@ export const PatientHome = (props: any) => { />
- {t("consultation_not_found")} + {t("consultation_not_filed")} - {t("consultation_not_found_description")} + {t("consultation_not_filed_description")}
diff --git a/src/Locale/en.json b/src/Locale/en.json index 3359a89904a..2b47a90bd46 100644 --- a/src/Locale/en.json +++ b/src/Locale/en.json @@ -1020,5 +1020,7 @@ "is_declared_positive": "Whether declared positive", "date_declared_positive": "Date of declaring positive", "date_of_result": "Covid confirmation date", - "is_vaccinated": "Whether vaccinated" -} \ No newline at end of file + "is_vaccinated": "Whether vaccinated", + "consultation_not_filed": "You have not filed any consultation for this patient yet.", + "consultation_not_filed_description": "Please file a consultation for this patient to continue." +} From ca2c92ae3da9a5f19f2c451188cca05928b00174 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 10 Oct 2024 14:17:13 +0530 Subject: [PATCH 2/3] Patient Registration Form: Move restore draft button beside generate/link abha number (#8741) --- src/Components/Form/Form.tsx | 78 +- src/Components/Patient/PatientRegister.tsx | 1796 ++++++++++---------- src/Utils/AutoSave.tsx | 92 +- 3 files changed, 984 insertions(+), 982 deletions(-) diff --git a/src/Components/Form/Form.tsx b/src/Components/Form/Form.tsx index 31afc594447..4bf4bd22d01 100644 --- a/src/Components/Form/Form.tsx +++ b/src/Components/Form/Form.tsx @@ -13,7 +13,6 @@ type Props = { className?: string; defaults: T; asyncGetDefaults?: (() => Promise) | false; - onlyChild?: boolean; validate?: (form: T) => FormErrors; onSubmit: (form: T) => Promise | void>; onCancel?: () => void; @@ -23,6 +22,7 @@ type Props = { cancelLabel?: string; onDraftRestore?: (newState: FormState) => void; children: (props: FormContextValue) => React.ReactNode; + hideRestoreDraft?: boolean; }; const Form = ({ @@ -87,47 +87,43 @@ const Form = ({ props.onDraftRestore?.(newState); }} formData={state.form} - /> - ) => { - return { - name, - id: name, - onChange: ({ name, value }: FieldChangeEvent) => - dispatch({ - type: "set_field", - name, - value, - error: validate?.(value), - }), - value: state.form[name], - error: state.errors[name], - disabled, - }; - }} + hidden={props.hideRestoreDraft} > - {props.onlyChild ? ( - {props.children} - ) : ( - <> -
- {props.children} -
-
- - -
- - )} -
+ ) => { + return { + name, + id: name, + onChange: ({ name, value }: FieldChangeEvent) => + dispatch({ + type: "set_field", + name, + value, + error: validate?.(value), + }), + value: state.form[name], + error: state.errors[name], + disabled, + }; + }} + > +
+ {props.children} +
+
+ + +
+
+ ); }; diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index 8e9a284f7e5..58a882d184d 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -70,6 +70,7 @@ import { Button } from "@/Components/ui/button"; import Loading from "@/Components/Common/Loading"; import PageTitle from "@/Components/Common/PageTitle"; +import { RestoreDraftButton } from "@/Utils/AutoSave.js"; type PatientForm = PatientModel & PatientMeta & { age?: number; is_postpartum?: boolean }; @@ -916,974 +917,937 @@ export const PatientRegister = (props: PatientRegisterProps) => { } return ( -
- {statusDialog.show && ( - { - handleDialogClose("close"); - setResetNum(true); - }} - /> - )} - {statusDialog.transfer && ( - { - setResetNum(true); - handleDialogClose("close"); - }} - title="Patient Transfer Form" - className="max-w-md md:min-w-[600px]" - > - handleDialogClose("close")} - handleCancel={() => { - setResetNum(true); - handleDialogClose("close"); - }} - facilityId={facilityId} - /> - - )} - { - id - ? navigate(`/facility/${facilityId}/patient/${id}`) - : navigate(`/facility/${facilityId}`); - }} - componentRight={ - !state.form.abha_number && ( - - ) + + defaults={id ? state.form : initForm} + validate={validateForm} + onSubmit={handleSubmit} + submitLabel={buttonText} + onCancel={() => navigate("/facility")} + className="bg-transparent px-1 py-2 md:px-2" + onDraftRestore={(newState) => { + dispatch({ type: "set_state", state: newState }); + Promise.all([ + fetchDistricts(newState.form.state ?? 0), + fetchLocalBody(newState.form.district?.toString() ?? ""), + fetchWards(newState.form.local_body?.toString() ?? ""), + duplicateCheck(newState.form.phone_number ?? ""), + ]); + }} + noPadding + hideRestoreDraft + > + {(field) => { + if (!formField) setFormField(field); + if (resetNum) { + field("phone_number").onChange({ + name: "phone_number", + value: "+91", + }); + setResetNum(false); } - crumbsReplacements={{ - [facilityId]: { name: facilityObject?.name }, - [id ?? "????"]: { name: patientName }, - }} - /> -
-
-
- {" "} - Please enter the correct date of birth for the patient -
-

- Each patient in the system is uniquely identifiable by the number - and date of birth. Adding incorrect date of birth can result in - duplication of patient records. -

-
- <> - {showAlertMessage.show && ( - goBack()} - onClose={() => goBack()} - variant="primary" - action="Ok" - show - /> - )} - <> - - defaults={id ? state.form : initForm} - validate={validateForm} - onSubmit={handleSubmit} - submitLabel={buttonText} - onCancel={() => navigate("/facility")} - className="bg-transparent px-1 py-2 md:px-2" - onDraftRestore={(newState) => { - dispatch({ type: "set_state", state: newState }); - Promise.all([ - fetchDistricts(newState.form.state ?? 0), - fetchLocalBody(newState.form.district?.toString() ?? ""), - fetchWards(newState.form.local_body?.toString() ?? ""), - duplicateCheck(newState.form.phone_number ?? ""), - ]); + return ( +
+ {statusDialog.show && ( + { + handleDialogClose("close"); + setResetNum(true); + }} + /> + )} + {statusDialog.transfer && ( + { + setResetNum(true); + handleDialogClose("close"); + }} + title="Patient Transfer Form" + className="max-w-md md:min-w-[600px]" + > + handleDialogClose("close")} + handleCancel={() => { + setResetNum(true); + handleDialogClose("close"); + }} + facilityId={facilityId} + /> + + )} + { + id + ? navigate(`/facility/${facilityId}/patient/${id}`) + : navigate(`/facility/${facilityId}`); }} - noPadding - > - {(field) => { - if (!formField) setFormField(field); - if (resetNum) { - field("phone_number").onChange({ - name: "phone_number", - value: "+91", - }); - setResetNum(false); - } - return ( - <> - {careConfig.abdm.enabled && ( -
- {showLinkAbhaNumberModal && ( - setShowLinkAbhaNumberModal(false)} - onSuccess={(data: any) => { - if (id) { - navigate( - `/facility/${facilityId}/patient/${id}`, - ); - return; - } + componentRight={} + crumbsReplacements={{ + [facilityId]: { name: facilityObject?.name }, + [id ?? "????"]: { name: patientName }, + }} + /> +
+
+
+ {" "} + Please enter the correct date of birth for the patient +
+

+ Each patient in the system is uniquely identifiable by the + number and date of birth. Adding incorrect date of birth can + result in duplication of patient records. +

+
+ {!state.form.abha_number && ( +
+ +
+ )} + {showAlertMessage.show && ( + goBack()} + onClose={() => goBack()} + variant="primary" + action="Ok" + show + /> + )} + {careConfig.abdm.enabled && ( +
+ {showLinkAbhaNumberModal && ( + setShowLinkAbhaNumberModal(false)} + onSuccess={(data: any) => { + if (id) { + navigate(`/facility/${facilityId}/patient/${id}`); + return; + } - handleAbhaLinking(data, field); - }} - /> - )} - {state.form.abha_number && ( -
-
- null} - disabled={true} - error="" - /> -
-
- {state.form.health_id ? ( - null} - disabled={true} - error="" - /> - ) : ( -
- No Abha Address Associated with this ABHA - Number -
- )} -
-
- )} + handleAbhaLinking(data, field); + }} + /> + )} + {state.form.abha_number && ( +
+
+ null} + disabled={true} + error="" + />
- )} -
-

- Personal Details -

-
-
- { - if (!id) duplicateCheck(event.value); - field("phone_number").onChange(event); - if (isEmergencyNumberEnabled) { - field("emergency_phone_number").onChange({ - name: field("emergency_phone_number").name, - value: event.value, - }); - } - }} - types={["mobile", "landline"]} - /> - { - setIsEmergencyNumberEnabled(value); - value - ? field("emergency_phone_number").onChange({ - name: field("emergency_phone_number").name, - value: field("phone_number").value, - }) - : field("emergency_phone_number").onChange({ - name: field("emergency_phone_number").name, - value: initForm.emergency_phone_number, - }); - }} - /> -
-
- -
-
+
+ {state.form.health_id ? ( null} + disabled={true} + error="" /> -
-
- - {ageInputType === "age" ? "Age" : "Date of Birth"} - -
- + No Abha Address Associated with this ABHA Number +
+ )} +
+
+ )} +
+ )} +
+

+ Personal Details +

+
+
+ { + if (!id) duplicateCheck(event.value); + field("phone_number").onChange(event); + if (isEmergencyNumberEnabled) { + field("emergency_phone_number").onChange({ + name: field("emergency_phone_number").name, + value: event.value, + }); + } + }} + types={["mobile", "landline"]} + /> + { + setIsEmergencyNumberEnabled(value); + value + ? field("emergency_phone_number").onChange({ + name: field("emergency_phone_number").name, + value: field("phone_number").value, + }) + : field("emergency_phone_number").onChange({ + name: field("emergency_phone_number").name, + value: initForm.emergency_phone_number, + }); + }} + /> +
+
+ +
+
+ +
+
+ + {ageInputType === "age" ? "Age" : "Date of Birth"} + +
+ o.text} + optionValue={(o) => + o.value === "date_of_birth" ? "date_of_birth" : "age" + } + value={ageInputType} + onChange={(v) => { + if (v === "age" && ageInputType === "date_of_birth") { + setAgeInputType("alert_for_age"); + return; + } + setAgeInputType(v); + }} + /> +
+ {ageInputType !== "age" ? ( +
+ o.text} - optionValue={(o) => - o.value === "date_of_birth" - ? "date_of_birth" - : "age" - } - value={ageInputType} - onChange={(v) => { - if ( - v === "age" && - ageInputType === "date_of_birth" - ) { - setAgeInputType("alert_for_age"); - return; - } - setAgeInputType(v); - }} + position="LEFT" + disableFuture /> -
- {ageInputType !== "age" ? ( -
- -
- ) : ( -
- - {field("age").value !== "" && ( - <> - - Year of Birth: - - - YOB: - - - {new Date().getFullYear() - - field("age").value} - - - )} -

- } - placeholder="Enter the age" - type="number" - min={0} - /> -
- )} -
- -
- -
- While entering a patient's age is an option, - please note that only the year of birth will - be captured from this information. -
- - Recommended only when the patient's date of - birth is unknown - -
+ ) : ( +
+ + {field("age").value !== "" && ( + <> + + Year of Birth: + + + YOB: + + + {new Date().getFullYear() - + field("age").value} + + + )} +

} - action="Confirm" - variant="warning" - show={ageInputType == "alert_for_age"} - onClose={() => setAgeInputType("date_of_birth")} - onConfirm={() => setAgeInputType("age")} + placeholder="Enter the age" + type="number" + min={0} />
-
-
+ )} +
+
+ +
+ +
+ While entering a patient's age is an option, + please note that only the year of birth will be + captured from this information. +
+ + Recommended only when the patient's date of birth + is unknown + +
+ } + action="Confirm" + variant="warning" + show={ageInputType == "alert_for_age"} + onClose={() => setAgeInputType("date_of_birth")} + onConfirm={() => setAgeInputType("age")} + /> +
+
+
+ { + field("gender").onChange(e); + if (e.value !== "2") { + field("is_antenatal").onChange({ + name: "is_antenatal", + value: "false", + }); + + field("is_postpartum").onChange({ + name: "is_postpartum", + value: "false", + }); + } + }} + optionLabel={(o: any) => o.text} + optionValue={(o: any) => o.id} + /> +
+ + { +
+ option.label} + optionValue={(option) => option.value} + /> +
+ } +
+ + { +
+ +
+ } +
+ + option.label} + optionValue={(option) => option.value} + /> + + + + +
+ +
+
+ + +
+ +
+ { + field("pincode").onChange(e); + handlePincodeChange(e, field("pincode").onChange); + }} + /> + {showAutoFilledPincode && ( +
+ + + State and District auto-filled from Pincode + +
+ )} +
+
+ +
+
+ o} + optionValue={(o) => o} + /> +
+ {field("nationality").value === "India" ? ( + <> +
+ {isStateLoading ? ( + + ) : ( { - field("gender").onChange(e); - if (e.value !== "2") { - field("is_antenatal").onChange({ - name: "is_antenatal", - value: "false", - }); - - field("is_postpartum").onChange({ - name: "is_postpartum", - value: "false", - }); - } - }} - optionLabel={(o: any) => o.text} + placeholder="Choose State" + options={stateData ? stateData.results : []} + optionLabel={(o: any) => o.name} optionValue={(o: any) => o.id} + onChange={(e: any) => { + field("state").onChange(e); + field("district").onChange({ + name: "district", + value: undefined, + }); + field("local_body").onChange({ + name: "local_body", + value: undefined, + }); + field("ward").onChange({ + name: "ward", + value: undefined, + }); + fetchDistricts(e.value); + fetchLocalBody("0"); + fetchWards("0"); + }} /> -
- - { -
- option.label} - optionValue={(option) => option.value} - /> -
- } -
- - { -
- -
- } -
- - option.label} - optionValue={(option) => option.value} - /> - - - - -
- -
-
- + +
+ {isDistrictLoading ? ( +
+ +
+ ) : ( + o.name} + optionValue={(o: any) => o.id} + onChange={(e: any) => { + field("district").onChange(e); + field("local_body").onChange({ + name: "local_body", + value: undefined, + }); + field("ward").onChange({ + name: "ward", + value: undefined, + }); + fetchLocalBody(String(e.value)); + fetchWards("0"); + }} /> - -
+ )} +
-
- + {isLocalbodyLoading ? ( +
+ +
+ ) : ( + o.name} + optionValue={(o) => o.id} onChange={(e) => { - field("pincode").onChange(e); - handlePincodeChange(e, field("pincode").onChange); + field("local_body").onChange(e); + field("ward").onChange({ + name: "ward", + value: undefined, + }); + fetchWards(String(e.value)); }} /> - {showAutoFilledPincode && ( -
- - - State and District auto-filled from Pincode - -
- )} -
-
- -
-
+ )} +
+
+ {isWardLoading ? ( +
+ +
+ ) : ( o} - optionValue={(o) => o} + {...field("ward")} + label="Ward" + options={ward.sort(compareBy("number")).map((e) => { + return { + id: e.id, + name: e.number + ": " + e.name, + }; + })} + placeholder={ + field("local_body").value + ? "Choose Ward" + : "Select Localbody First" + } + disabled={!field("local_body").value} + optionLabel={(o: any) => o.name} + optionValue={(o: any) => o.id} + onChange={(e: any) => { + field("ward").onChange(e); + }} /> -
- {field("nationality").value === "India" ? ( - <> -
- {isStateLoading ? ( - - ) : ( - o.name} - optionValue={(o: any) => o.id} - onChange={(e: any) => { - field("state").onChange(e); - field("district").onChange({ - name: "district", - value: undefined, - }); - field("local_body").onChange({ - name: "local_body", - value: undefined, - }); - field("ward").onChange({ - name: "ward", - value: undefined, - }); - fetchDistricts(e.value); - fetchLocalBody("0"); - fetchWards("0"); - }} - /> - )} -
- -
- {isDistrictLoading ? ( -
- -
- ) : ( - o.name} - optionValue={(o: any) => o.id} - onChange={(e: any) => { - field("district").onChange(e); - field("local_body").onChange({ - name: "local_body", - value: undefined, - }); - field("ward").onChange({ - name: "ward", - value: undefined, - }); - fetchLocalBody(String(e.value)); - fetchWards("0"); - }} - /> - )} -
- -
- {isLocalbodyLoading ? ( -
- -
- ) : ( - o.name} - optionValue={(o) => o.id} - onChange={(e) => { - field("local_body").onChange(e); - field("ward").onChange({ - name: "ward", - value: undefined, - }); - fetchWards(String(e.value)); - }} - /> - )} -
-
- {isWardLoading ? ( -
- -
- ) : ( - { - return { - id: e.id, - name: e.number + ": " + e.name, - }; - })} - placeholder={ - field("local_body").value - ? "Choose Ward" - : "Select Localbody First" - } - disabled={!field("local_body").value} - optionLabel={(o: any) => o.name} - optionValue={(o: any) => o.id} - onChange={(e: any) => { - field("ward").onChange(e); - }} - /> - )} -
- - ) : ( -
- -
)}
+ + ) : ( +
+
- {field("nationality").value === "India" && ( -
- + )} +
+
+ {field("nationality").value === "India" && ( +
+ + } + title={ +

+ Social Profile +

+ } + expanded + > +
+
+ o.text} + optionValue={(o) => o.id} + /> + t(`ration_card__${o}`)} + optionValue={(o) => o} + /> + t(`SOCIOECONOMIC_STATUS__${o}`)} + optionValue={(o) => o} + value={field("meta_info").value?.socioeconomic_status} + onChange={({ name, value }) => + field("meta_info").onChange({ + name: "meta_info", + value: { + ...(field("meta_info").value ?? {}), + [name]: value, + }, + }) } - title={ -

- Social Profile -

+ /> + + t(`DOMESTIC_HEALTHCARE_SUPPORT__${o}`) } - expanded - > -
-
- o.text} - optionValue={(o) => o.id} - /> - t(`ration_card__${o}`)} - optionValue={(o) => o} - /> - - t(`SOCIOECONOMIC_STATUS__${o}`) - } - optionValue={(o) => o} - value={ - field("meta_info").value?.socioeconomic_status - } - onChange={({ name, value }) => - field("meta_info").onChange({ - name: "meta_info", - value: { - ...(field("meta_info").value ?? {}), - [name]: value, - }, - }) - } - /> - - t(`DOMESTIC_HEALTHCARE_SUPPORT__${o}`) - } - optionValue={(o) => o} - value={ - field("meta_info").value - ?.domestic_healthcare_support - } - onChange={({ name, value }) => - field("meta_info").onChange({ - name: "meta_info", - value: { - ...(field("meta_info").value ?? {}), - [name]: value, - }, - }) - } - /> -
-
- + optionValue={(o) => o} + value={ + field("meta_info").value + ?.domestic_healthcare_support + } + onChange={({ name, value }) => + field("meta_info").onChange({ + name: "meta_info", + value: { + ...(field("meta_info").value ?? {}), + [name]: value, + }, + }) + } + />
- )} -
- - } - title={ -

- COVID Details -

- } +
+ +
+ )} +
+ + } + title={ +

+ COVID Details +

+ } + > +
+
+
+ option.label} + optionValue={(option) => option.value} + /> +
+
+
+ -
-
-
- option.label} - optionValue={(option) => option.value} + { +
+
+
-
-
- - { -
-
- -
-
- option.label} - optionValue={(option) => option.value} - /> -
-
- o} - optionValue={(o) => o} - /> -
-
- -
-
- } -
-
-
-
+
option.label} optionValue={(option) => option.value} /> - -
- -
-
-
+
+ o} + optionValue={(o) => o} + /> +
+
-
- -
-
-

- Medical History -

-
-
- -
- -
- -
-
- - Any medical history? (Comorbidities) - -
- {MEDICAL_HISTORY_CHOICES.map((i) => { - return renderMedicalHistory( - i.id as number, - i.text, - field, - ); - })} -
- -
- -
- -
- -
- o} - optionValue={(o: any) => o} - /> -
-
+ } +
-
-
-

- Insurance Details -

- - setInsuranceDetails([ - ...insuranceDetails, - { - id: "", - subscriber_id: "", - policy_id: "", - insurer_id: "", - insurer_name: "", - }, - ]) +
+
+ option.label} + optionValue={(option) => option.value} + /> + - - Add Insurance Details - +
+ +
+
+
+
+
- setInsuranceDetails(value)} - error={insuranceDetailsError} - gridView - />
- - ); - }} - - - -
-
+
+ +
+
+

+ Medical History +

+
+
+ +
+ +
+ +
+
+ + Any medical history? (Comorbidities) + +
+ {MEDICAL_HISTORY_CHOICES.map((i) => { + return renderMedicalHistory( + i.id as number, + i.text, + field, + ); + })} +
+ +
+ +
+ +
+ +
+ o} + optionValue={(o: any) => o} + /> +
+
+
+
+
+

+ Insurance Details +

+ + setInsuranceDetails([ + ...insuranceDetails, + { + id: "", + subscriber_id: "", + policy_id: "", + insurer_id: "", + insurer_name: "", + }, + ]) + } + data-testid="add-insurance-button" + > + + Add Insurance Details + +
+ setInsuranceDetails(value)} + error={insuranceDetailsError} + gridView + /> +
+
+
+ ); + }} + ); }; diff --git a/src/Utils/AutoSave.tsx b/src/Utils/AutoSave.tsx index da330a3fc6a..36097581b48 100644 --- a/src/Utils/AutoSave.tsx +++ b/src/Utils/AutoSave.tsx @@ -1,4 +1,12 @@ -import { useReducer, useEffect, useRef, useState, Dispatch } from "react"; +import React, { + useReducer, + useEffect, + useRef, + useState, + Dispatch, + useContext, + ReactNode, +} from "react"; import { Button } from "../Components/ui/button"; import { FormAction, FormReducer, FormState } from "../Components/Form/Utils"; import { relativeTime } from "./utils"; @@ -81,9 +89,20 @@ export function useAutoSaveState(initialState: any) { return [state, setState]; } +type RestoreDraftContextValue = { + handleDraftSelect: (formState: any) => void; + draftStarted: boolean; + drafts: Draft[]; +}; + +const RestoreDraftContext = + React.createContext(null); + export function DraftSection(props: { handleDraftSelect: (formState: any) => void; formData: any; + hidden?: boolean; + children?: ReactNode; }) { const { handleDraftSelect } = props; const [drafts, setDrafts] = useState([]); @@ -123,32 +142,55 @@ export function DraftSection(props: { }, []); return ( - <> - {drafts && drafts.length > 0 && ( + + {!props.hidden && drafts && drafts.length > 0 && (
- +
)} - + {props.children} +
); } + +export const RestoreDraftButton = () => { + const ctx = useContext(RestoreDraftContext); + + if (!ctx) { + throw new Error( + "RestoreDraftButton must be used within a RestoreDraftProvider", + ); + } + + const { handleDraftSelect, draftStarted, drafts } = ctx; + + if (!(drafts && drafts.length > 0)) { + return null; + } + + return ( + + ); +}; From cad0b65b3aec4213f87bd41f0723cd239758000f Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Fri, 11 Oct 2024 16:44:27 +0530 Subject: [PATCH 3/3] Fixes restore draft from performing form submits (#8765) --- src/Utils/AutoSave.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Utils/AutoSave.tsx b/src/Utils/AutoSave.tsx index 36097581b48..dd251f5bbb7 100644 --- a/src/Utils/AutoSave.tsx +++ b/src/Utils/AutoSave.tsx @@ -173,6 +173,7 @@ export const RestoreDraftButton = () => { return (