Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add autosave to forms #5731

Merged
merged 5 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Components/Common/components/CollapseV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function CollapseV2(props: {
}
>
<div
className={`transition-all ease-in-out duration-300 overflow-hidden ${
className={`transition-all ease-in-out duration-300 ${
props.className ? props.className : ""
}`}
ref={content}
Expand Down
123 changes: 70 additions & 53 deletions src/Components/Facility/ConsultationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import loadable from "@loadable/component";
import { navigate } from "raviger";
import moment from "moment";
import {
createRef,
LegacyRef,
useCallback,
useEffect,
useReducer,
useState,
} from "react";
import { createRef, LegacyRef, useCallback, useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import {
CONSULTATION_SUGGESTION,
Expand Down Expand Up @@ -55,6 +48,11 @@ import useAppHistory from "../../Common/hooks/useAppHistory";
import useVisibility from "../../Utils/useVisibility";
import CareIcon from "../../CAREUI/icons/CareIcon";
import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField";
import {
DraftSection,
FormReducerAction,
useAutoSaveReducer,
} from "../../Utils/AutoSave";

const Loading = loadable(() => import("../Common/Loading"));
const PageTitle = loadable(() => import("../Common/PageTitle"));
Expand Down Expand Up @@ -102,13 +100,10 @@ type FormDetails = {
cause_of_death: string;
death_datetime: string;
death_confirmed_doctor: string;
InvestigationAdvice: InvestigationType[];
procedures: ProcedureType[];
};

type Action =
| { type: "set_form"; form: FormDetails }
| { type: "set_error"; errors: FormDetails }
| { type: "set_form_field"; field: keyof FormDetails; value: any };

const initForm: FormDetails = {
symptoms: [],
other_symptoms: "",
Expand Down Expand Up @@ -150,6 +145,8 @@ const initForm: FormDetails = {
cause_of_death: "",
death_datetime: "",
death_confirmed_doctor: "",
InvestigationAdvice: [],
procedures: [],
};

const initError = Object.assign(
Expand All @@ -175,28 +172,26 @@ const fieldRef = formErrorKeys.reduce(
{}
);

const consultationFormReducer = (state = initialState, action: Action) => {
const consultationFormReducer = (
state = initialState,
action: FormReducerAction
) => {
switch (action.type) {
case "set_form": {
return {
...state,
form: { ...state.form, ...action.form },
};
}
case "set_error": {
case "set_errors": {
return {
...state,
errors: action.errors,
};
}
case "set_form_field": {
return {
...state,
form: {
...state.form,
[action.field]: action.value,
},
};
case "set_state": {
if (action.state) return action.state;
return state;
}
}
};
Expand All @@ -211,12 +206,11 @@ export const ConsultationForm = (props: any) => {
const { kasp_enabled, kasp_string } = useConfig();
const dispatchAction: any = useDispatch();
const { facilityId, patientId, id } = props;
const [state, dispatch] = useReducer(consultationFormReducer, initialState);
const [state, dispatch] = useAutoSaveReducer<FormDetails>(
consultationFormReducer,
initialState
);
const [bed, setBed] = useState<BedModel | BedModel[] | null>(null);
const [InvestigationAdvice, setInvestigationAdvice] = useState<
InvestigationType[]
>([]);
const [procedures, setProcedures] = useState<ProcedureType[]>([]);

const [selectedFacility, setSelectedFacility] =
useState<FacilityModel | null>(null);
Expand Down Expand Up @@ -267,12 +261,11 @@ export const ConsultationForm = (props: any) => {
setPatientName(res.data.name);
setFacilityName(res.data.facility_object.name);
if (isUpdate) {
dispatch({
type: "set_form_field",
field: "action",
value: TELEMEDICINE_ACTIONS.find((a) => a.id === res.data.action)
?.text,
});
const form = { ...state.form };
form.action = TELEMEDICINE_ACTIONS.find(
(a) => a.id === res.data.action
)?.text;
dispatch({ type: "set_form", form });
}
}
} else {
Expand All @@ -291,12 +284,16 @@ export const ConsultationForm = (props: any) => {
async (status: statusType) => {
setIsLoading(true);
const res = await dispatchAction(getConsultation(id));
setInvestigationAdvice(
!Array.isArray(res.data.investigation) ? [] : res.data.investigation
);
setProcedures(
!Array.isArray(res.data.procedure) ? [] : res.data.procedure
);
handleFormFieldChange({
name: "InvestigationAdvice",
value: !Array.isArray(res.data.investigation)
? []
: res.data.investigation,
});
handleFormFieldChange({
name: "procedures",
value: !Array.isArray(res.data.procedure) ? [] : res.data.procedure,
});
if (res.data.suggestion === "R") {
if (res.data.referred_to_external)
setSelectedFacility({ id: -1, name: res.data.referred_to_external });
Expand Down Expand Up @@ -471,7 +468,7 @@ export const ConsultationForm = (props: any) => {
}
return;
case "procedure": {
for (const p of procedures) {
for (const p of state.form.procedures) {
if (!p.procedure?.replace(/\s/g, "").length) {
errors[field] = "Procedure field can not be empty";
invalidForm = true;
Expand All @@ -492,7 +489,7 @@ export const ConsultationForm = (props: any) => {
}

case "investigation": {
for (const i of InvestigationAdvice) {
for (const i of state.form.InvestigationAdvice) {
if (!i.type?.length) {
errors[field] = "Investigation field can not be empty";
invalidForm = true;
Expand Down Expand Up @@ -535,7 +532,7 @@ export const ConsultationForm = (props: any) => {
}
});
if (invalidForm) {
dispatch({ type: "set_error", errors });
dispatch({ type: "set_errors", errors });
const firstError = Object.keys(errors).find((key) => errors[key]);
if (firstError) {
fieldRef[firstError].current?.scrollIntoView({
Expand All @@ -545,7 +542,7 @@ export const ConsultationForm = (props: any) => {
}
return false;
}
dispatch({ type: "set_error", errors });
dispatch({ type: "set_errors", errors });
return true;
};

Expand Down Expand Up @@ -602,12 +599,16 @@ export const ConsultationForm = (props: any) => {
discharge_date: state.form.discharge_date,
ip_no: state.form.ip_no,
op_no: state.form.op_no,
icd11_diagnoses: state.form.icd11_diagnoses_object.map((o) => o.id),
icd11_diagnoses: state.form.icd11_diagnoses_object.map(
(o: ICD11DiagnosisModel) => o.id
),
icd11_provisional_diagnoses:
state.form.icd11_provisional_diagnoses_object.map((o) => o.id),
state.form.icd11_provisional_diagnoses_object.map(
(o: ICD11DiagnosisModel) => o.id
),
verified_by: state.form.verified_by,
investigation: InvestigationAdvice,
procedure: procedures,
investigation: state.form.InvestigationAdvice,
procedure: state.form.procedures,
patient: patientId,
facility: facilityId,
referred_to:
Expand Down Expand Up @@ -768,7 +769,7 @@ export const ConsultationForm = (props: any) => {
id: name,
name,
value: (state.form as any)[name],
error: state.errors[name],
error: (state.errors as any)[name],
onChange: handleFormFieldChange,
};
};
Expand Down Expand Up @@ -831,6 +832,12 @@ export const ConsultationForm = (props: any) => {
onSubmit={handleSubmit}
className="rounded sm:rounded-xl bg-white p-6 sm:p-12 transition-all"
>
<DraftSection
handleDraftSelect={(newState: any) => {
dispatch({ type: "set_state", state: newState });
}}
formData={state.form}
/>
<div className="grid grid-cols-1 gap-x-12 items-start">
<div className="grid grid-cols-6 gap-x-6">
{sectionTitle("Consultation Details")}
Expand Down Expand Up @@ -1120,8 +1127,13 @@ export const ConsultationForm = (props: any) => {
>
<FieldLabel>Investigations Suggestions</FieldLabel>
<InvestigationBuilder
investigations={InvestigationAdvice}
setInvestigations={setInvestigationAdvice}
investigations={state.form.InvestigationAdvice}
setInvestigations={(investigations) => {
handleFormFieldChange({
name: "InvestigationAdvice",
value: investigations,
});
}}
/>
<LegacyErrorHelperText
error={state.errors.investigation}
Expand All @@ -1134,8 +1146,13 @@ export const ConsultationForm = (props: any) => {
>
<FieldLabel>Procedures</FieldLabel>
<ProcedureBuilder
procedures={procedures}
setProcedures={setProcedures}
procedures={state.form.procedures}
setProcedures={(procedures) => {
handleFormFieldChange({
name: "procedures",
value: procedures,
});
}}
/>
<LegacyErrorHelperText
error={state.errors.procedure}
Expand Down
42 changes: 26 additions & 16 deletions src/Components/Facility/FacilityCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
MultiSelectFormField,
SelectFormField,
} from "../Form/FormFields/SelectFormField";
import React, { useCallback, useReducer, useState } from "react";
import React, { useCallback, useState } from "react";
import Steps, { Step } from "../Common/Steps";
import {
createFacility,
Expand Down Expand Up @@ -53,6 +53,8 @@ import useAppHistory from "../../Common/hooks/useAppHistory";
import useConfig from "../../Common/hooks/useConfig";
import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave.js";
import { FormAction } from "../Form/Utils.js";

const Loading = loadable(() => import("../Common/Loading"));
const PageTitle = loadable(() => import("../Common/PageTitle"));
Expand Down Expand Up @@ -128,22 +130,16 @@ const initialState = {
errors: { ...initError },
};

type SetFormAction = { type: "set_form"; form: FacilityForm };
type SetErrorAction = {
type: "set_error";
errors: Record<keyof FacilityForm, string>;
};
type FacilityCreateFormAction = SetFormAction | SetErrorAction;

const facilityCreateReducer = (
state = initialState,
action: FacilityCreateFormAction
) => {
const facilityCreateReducer = (state = initialState, action: FormAction) => {
switch (action.type) {
case "set_form":
return { ...state, form: action.form };
case "set_error":
case "set_errors":
return { ...state, errors: action.errors };
case "set_state": {
if (action.state) return action.state;
return state;
}
}
};

Expand All @@ -153,7 +149,10 @@ export const FacilityCreate = (props: FacilityProps) => {
const dispatchAction: any = useDispatch();
const { facilityId } = props;

const [state, dispatch] = useReducer(facilityCreateReducer, initialState);
const [state, dispatch] = useAutoSaveReducer<FacilityForm>(
facilityCreateReducer,
initialState
);
const [isLoading, setIsLoading] = useState(false);
const [isStateLoading, setIsStateLoading] = useState(false);
const [isDistrictLoading, setIsDistrictLoading] = useState(false);
Expand Down Expand Up @@ -472,10 +471,10 @@ export const FacilityCreate = (props: FacilityProps) => {
}
});
if (invalidForm) {
dispatch({ type: "set_error", errors });
dispatch({ type: "set_errors", errors });
return false;
}
dispatch({ type: "set_error", errors });
dispatch({ type: "set_errors", errors });
return true;
};

Expand Down Expand Up @@ -767,6 +766,17 @@ export const FacilityCreate = (props: FacilityProps) => {
<Card className="mt-4">
<CardContent>
<form onSubmit={(e) => handleSubmit(e)}>
<DraftSection
handleDraftSelect={(newState: any) => {
dispatch({ type: "set_state", state: newState });
Promise.all([
fetchDistricts(newState.form.state),
fetchLocalBody(newState.form.district),
fetchWards(newState.form.local_body),
]);
}}
formData={state.form}
/>
<div className="grid gap-4 grid-cols-1 md:grid-cols-2">
<SelectFormField
{...field("facility_type")}
Expand Down
13 changes: 10 additions & 3 deletions src/Components/Form/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { isEmpty, omitBy } from "lodash";
import { useEffect, useMemo, useReducer, useState } from "react";
import { useEffect, useState } from "react";
import { classNames } from "../../Utils/utils";
import { Cancel, Submit } from "../Common/components/ButtonV2";
import { FieldValidator } from "./FieldValidators";
import { FormContextValue, createFormContext } from "./FormContext";
import { FieldChangeEvent } from "./FormFields/Utils";
import { FormDetails, FormErrors, formReducer, FormReducer } from "./Utils";
import { FormDetails, FormErrors, formReducer } from "./Utils";
import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave";

type Props<T extends FormDetails> = {
className?: string;
Expand All @@ -29,7 +30,7 @@ const Form = <T extends FormDetails>({
}: Props<T>) => {
const initial = { form: props.defaults, errors: {} };
const [isLoading, setIsLoading] = useState(!!asyncGetDefaults);
const [state, dispatch] = useReducer<FormReducer<T>>(formReducer, initial);
const [state, dispatch] = useAutoSaveReducer<T>(formReducer, initial);

useEffect(() => {
if (!asyncGetDefaults) return;
Expand Down Expand Up @@ -75,6 +76,12 @@ const Form = <T extends FormDetails>({
)}
noValidate
>
<DraftSection
handleDraftSelect={(newState: any) => {
dispatch({ type: "set_state", state: newState });
}}
formData={state.form}
/>
<Provider
value={(name: keyof T, validate?: FieldValidator<T[keyof T]>) => {
return {
Expand Down
Loading
Loading