From b9703b2a0081b5af79844cdf6586b96871d16c61 Mon Sep 17 00:00:00 2001 From: SteveGT96 <stevegires@gmail.com> Date: Fri, 15 Sep 2023 18:02:11 +0100 Subject: [PATCH] update(OH2-216): Remove current admission from admission table and add current admission component --- .../admission/PatientAdmission.tsx | 12 +- .../admissionTable/AdmissionTable.tsx | 4 +- .../currentAdmission/CurrentAdmission.tsx | 513 ------------------ .../admission/currentAdmission/styles.scss | 82 --- .../admission/currentAdmission/types.ts | 33 -- .../currentAdmission/CurrentAdmission.tsx | 73 +++ .../CurrentAdmissionData.tsx | 75 +++ .../CurrentAdmissionForm.tsx | 320 +++++++++++ .../currentAdmissionForm/consts.ts | 50 ++ .../currentAdmissionForm/types.ts | 23 + .../accessories/currentAdmission/styles.scss | 111 ++++ .../accessories/currentAdmission/types.ts | 3 + 12 files changed, 669 insertions(+), 630 deletions(-) delete mode 100644 src/components/accessories/admission/currentAdmission/CurrentAdmission.tsx delete mode 100644 src/components/accessories/admission/currentAdmission/styles.scss delete mode 100644 src/components/accessories/admission/currentAdmission/types.ts create mode 100644 src/components/accessories/currentAdmission/CurrentAdmission.tsx create mode 100644 src/components/accessories/currentAdmission/currentAdmissionData/CurrentAdmissionData.tsx create mode 100644 src/components/accessories/currentAdmission/currentAdmissionForm/CurrentAdmissionForm.tsx create mode 100644 src/components/accessories/currentAdmission/currentAdmissionForm/consts.ts create mode 100644 src/components/accessories/currentAdmission/currentAdmissionForm/types.ts create mode 100644 src/components/accessories/currentAdmission/styles.scss create mode 100644 src/components/accessories/currentAdmission/types.ts diff --git a/src/components/accessories/admission/PatientAdmission.tsx b/src/components/accessories/admission/PatientAdmission.tsx index e12187193..afdab3cbf 100644 --- a/src/components/accessories/admission/PatientAdmission.tsx +++ b/src/components/accessories/admission/PatientAdmission.tsx @@ -23,6 +23,7 @@ import PatientAdmissionTable from "./admissionTable/AdmissionTable"; import { isEmpty } from "lodash"; import { usePermission } from "../../../libraries/permissionUtils/usePermission"; import { getLastOpd } from "../../../state/opds/actions"; +import { CurrentAdmission } from "../currentAdmission/CurrentAdmission"; const PatientAdmission: FC = () => { const { t } = useTranslation(); @@ -31,6 +32,7 @@ const PatientAdmission: FC = () => { const infoBoxRef = useRef<HTMLDivElement>(null); const [shouldResetForm, setShouldResetForm] = useState(false); const [creationMode, setCreationMode] = useState(true); + const [isEditingCurrent, setIsEditingCurrent] = useState(false); const [showForm, setShowForm] = useState(false); const [admissionToEdit, setAdmissionToEdit] = useState<AdmissionDTO | undefined>(); @@ -188,11 +190,16 @@ const PatientAdmission: FC = () => { scrollToElement(null); }; + const onCurrentAdmissionChange = (value: boolean) => { + setIsEditingCurrent(value); + }; + return ( <div className="patientAdmission"> {patient?.status === PatientDTOStatusEnum.I && ( <InfoBox type="info" message={t("admission.patientalreadyadmitted")} /> )} + <CurrentAdmission onEditChange={onCurrentAdmissionChange} /> {open && ( <AdmissionForm fields={fields} @@ -224,7 +231,10 @@ const PatientAdmission: FC = () => { /> <ConfirmationDialog - isOpen={createStatus === "SUCCESS" || updateStatus === "SUCCESS"} + isOpen={ + (createStatus === "SUCCESS" || updateStatus === "SUCCESS") && + !isEditingCurrent + } title={creationMode ? t("admission.created") : t("admission.updated")} icon={checkIcon} info={ diff --git a/src/components/accessories/admission/admissionTable/AdmissionTable.tsx b/src/components/accessories/admission/admissionTable/AdmissionTable.tsx index 643598163..477e5bece 100644 --- a/src/components/accessories/admission/admissionTable/AdmissionTable.tsx +++ b/src/components/accessories/admission/admissionTable/AdmissionTable.tsx @@ -45,7 +45,9 @@ const PatientAdmissionTable: FunctionComponent<IOwnProps> = ({ const data = useSelector<IState, AdmissionDTO[]>((state) => state.admissions.getPatientAdmissions.data - ? state.admissions.getPatientAdmissions.data + ? state.admissions.getPatientAdmissions.data.filter( + (e) => state.admissions.currentAdmissionByPatientId.data?.id !== e.id + ) : [] ); diff --git a/src/components/accessories/admission/currentAdmission/CurrentAdmission.tsx b/src/components/accessories/admission/currentAdmission/CurrentAdmission.tsx deleted file mode 100644 index b982ba616..000000000 --- a/src/components/accessories/admission/currentAdmission/CurrentAdmission.tsx +++ /dev/null @@ -1,513 +0,0 @@ -import { useFormik } from "formik"; -import get from "lodash.get"; -import has from "lodash.has"; -import moment from "moment"; -import React, { FC, useCallback, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; -import { object, string } from "yup"; -import warningIcon from "../../../../assets/warning-icon.png"; -import { - AdmissionTypeDTO, - DiseaseDTO, - DiseaseTypeDTO, - WardDTO, -} from "../../../../generated"; -import { renderDate } from "../../../../libraries/formatUtils/dataFormatting"; -import { - differenceInDays, - formatAllFieldValues, - getFromFields, -} from "../../../../libraries/formDataHandling/functions"; -import { getAdmissionTypes } from "../../../../state/admissionTypes/actions"; -import { getDischargeTypes } from "../../../../state/dischargeTypes/actions"; -import { - getDiseasesIpdIn, - getDiseasesIpdOut, -} from "../../../../state/diseases/actions"; -import { getWards } from "../../../../state/ward/actions"; -import { IState } from "../../../../types"; -import AutocompleteField from "../../autocompleteField/AutocompleteField"; -import Button from "../../button/Button"; -import ConfirmationDialog from "../../confirmationDialog/ConfirmationDialog"; -import DateField from "../../dateField/DateField"; -import TextField from "../../textField/TextField"; -import "./styles.scss"; -import { CurrentAdmissionProps } from "./types"; - -const CurrentAdmission: FC<CurrentAdmissionProps> = ({ - fields, - onSubmit, - creationMode, - submitButtonLabel, - resetButtonLabel, - isLoading, - admitted, - shouldResetForm, - resetFormCallback, -}) => { - const { t } = useTranslation(); - const dispatch = useDispatch(); - - const diagnosisInList = useSelector( - (state: IState) => state.diseases.diseasesIpdIn.data - ); - - const admissionTypes = useSelector( - (state: IState) => state.admissionTypes.allAdmissionTypes.data - ); - const wards = useSelector((state: IState) => state.wards.allWards.data); - - const diagnosisOutList = useSelector( - (state: IState) => state.diseases.diseasesIpdOut.data - ); - - const dischargeTypes = useSelector( - (state: IState) => state.dischargeTypes.allDischargeTypes.data - ); - - const renderOptions = ( - data: - | ( - | WardDTO - | DiseaseDTO - | AdmissionTypeDTO - | DiseaseTypeDTO - | DiseaseDTO - )[] - | undefined - ) => { - if (data) { - return data.map((item) => { - return { - value: item.code?.toString() ?? "", - label: item.description ?? "", - }; - }); - } else return []; - }; - - const initialValues = getFromFields(fields, "value"); - - const validationSchema = object({ - ward: string().required(t("common.required")), - admType: string().required(t("common.required")), - admDate: string() - .required(t("common.required")) - .test({ - name: "admDate", - message: t("common.invaliddate"), - test: function (value) { - return moment(value).isValid(); - }, - }) - .test({ - name: "admDate", - message: t("admission.datebefore"), - test: function (value) { - return moment(this.parent.disDate).isValid() - ? moment(value).isSameOrBefore(this.parent.disDate) - : true; - }, - }), - diseaseIn: string().required(t("common.required")), - disDate: admitted - ? string() - .required(t("common.required")) - .test({ - name: "disDate", - message: t("admission.validatelastdate", { - admDate: moment(initialValues.admDate).format("DD/MM/YYYY"), - }), - test: function (value) { - return moment(value).isSameOrAfter(moment(this.parent.admDate)); - }, - }) - : string(), - - disType: admitted ? string().required(t("common.required")) : string(), - diseaseOut1: admitted ? string().required(t("common.required")) : string(), - - diseaseOut2: admitted - ? string().test({ - name: "diseaseOut2", - message: t("opd.validatedisease"), - test: function (value) { - return ( - !value || - (this.parent.diseaseOut1 && value !== this.parent.diseaseOut1) - ); - }, - }) - : string(), - - diseaseOut3: admitted - ? string().test({ - name: "diseaseOut3", - message: t("opd.validatedisease"), - test: function (value) { - return ( - !value || - (this.parent.diseaseOut1 && - this.parent.diseaseOut2 && - value !== this.parent.diseaseOut1 && - value !== this.parent.diseaseOut2) - ); - }, - }) - : string(), - }); - - const formik = useFormik({ - initialValues, - validationSchema, - enableReinitialize: true, - onSubmit: (values) => { - const formattedValues = formatAllFieldValues(fields, values); - formattedValues.diseaseIn = diagnosisInList?.find( - (item) => item.code === formattedValues.diseaseIn - ); - formattedValues.admType = admissionTypes?.find( - (item) => item.code === formattedValues.admType - ); - formattedValues.ward = wards?.find( - (item) => item.code === formattedValues.ward - ); - - formattedValues.diseaseOut1 = diagnosisOutList?.find( - (item) => item.code === formattedValues.diseaseOut1 - ); - formattedValues.diseaseOut2 = diagnosisOutList?.find( - (item) => item.code === formattedValues.diseaseOut2 - ); - formattedValues.diseaseOut3 = diagnosisOutList?.find( - (item) => item.code === formattedValues.diseaseOut3 - ); - formattedValues.disType = dischargeTypes?.find( - (item) => item.code === formattedValues.disType - ); - - onSubmit(formattedValues as any); - }, - }); - - const { setFieldValue, resetForm, handleBlur } = formik; - - const dateFieldHandleOnChange = useCallback( - (fieldName: string) => (value: any) => { - setFieldValue(fieldName, value); - formik.setFieldTouched(fieldName); - const days = differenceInDays( - new Date(formik.values.admDate), - new Date(formik.values.disDate) - ).toString(); - setFieldValue("bedDays", days); - }, - [setFieldValue] - ); - - const isValid = (fieldName: string): boolean => { - return has(formik.touched, fieldName) && has(formik.errors, fieldName); - }; - - const getErrorText = (fieldName: string): string => { - return has(formik.touched, fieldName) - ? (get(formik.errors, fieldName) as string) - : ""; - }; - - const onBlurCallback = useCallback( - (fieldName: string) => - (e: React.FocusEvent<HTMLDivElement>, value: string) => { - handleBlur(e); - setFieldValue(fieldName, value); - }, - [setFieldValue, handleBlur] - ); - - const [openResetConfirmation, setOpenResetConfirmation] = useState(false); - - const handleResetConfirmation = () => { - setOpenResetConfirmation(false); - formik.resetForm(); - resetFormCallback(); - }; - - useEffect(() => { - if (shouldResetForm) { - resetForm(); - resetFormCallback(); - } - }, [shouldResetForm, resetForm, resetFormCallback]); - - const diagnosisInStatus = useSelector( - (state: IState) => state.diseases.diseasesIpdIn.status - ); - const wardStatus = useSelector( - (state: IState) => state.wards.allWards.status - ); - const admTypeStatus = useSelector( - (state: IState) => state.admissionTypes.allAdmissionTypes.status - ); - - useEffect(() => { - dispatch(getDiseasesIpdOut()); - }, [dispatch, getDiseasesIpdOut]); - - useEffect(() => { - dispatch(getDiseasesIpdIn()); - dispatch(getAdmissionTypes()); - dispatch(getWards()); - }, [dispatch]); - - return ( - <> - <div className="patientCurrentAdmission"> - <h5 className="formInsertMode"> - {creationMode - ? t("admission.newadmission") - : t("admission.editadmission") + - ": " + - renderDate(formik.values.admDate)} - </h5> - <form - className="patientCurrentAdmission__form" - onSubmit={formik.handleSubmit} - > - <div className="row start-sm center-xs"> - <div className="patientCurrentAdmission__item"> - <AutocompleteField - fieldName="ward" - fieldValue={formik.values.ward} - label={t("admission.ward")} - isValid={isValid("ward")} - errorText={getErrorText("ward")} - onBlur={onBlurCallback("ward")} - options={renderOptions(wards)} - loading={wardStatus === "LOADING"} - disabled={isLoading} - /> - </div> - <div className="patientCurrentAdmission__item"> - <TextField - field={formik.getFieldProps("fhu")} - theme="regular" - label={t("admission.fhu")} - isValid={isValid("fhu")} - errorText={getErrorText("fhu")} - onBlur={formik.handleBlur} - type="text" - disabled={isLoading} - /> - </div> - </div> - - <div className="row start-sm center-xs"> - <div className="patientCurrentAdmission__item"> - <DateField - fieldName="admDate" - fieldValue={formik.values.admDate} - disableFuture={true} - theme="regular" - format="dd/MM/yyyy" - isValid={isValid("admDate")} - errorText={getErrorText("admDate")} - label={t("admission.admDate")} - onChange={dateFieldHandleOnChange("admDate")} - disabled={isLoading} - /> - </div> - <div className="patientCurrentAdmission__item"> - <AutocompleteField - fieldName="admType" - fieldValue={formik.values.admType} - label={t("admission.admType")} - isValid={isValid("admType")} - errorText={getErrorText("admType")} - onBlur={onBlurCallback("admType")} - options={renderOptions(admissionTypes)} - loading={admTypeStatus === "LOADING"} - disabled={isLoading} - /> - </div> - </div> - <div className="row start-sm center-xs"> - <div className="fullWidth patientCurrentAdmission__item"> - <AutocompleteField - fieldName="diseaseIn" - fieldValue={formik.values.diseaseIn} - label={t("admission.diseaseIn")} - isValid={isValid("diseaseIn")} - errorText={getErrorText("diseaseIn")} - onBlur={onBlurCallback("diseaseIn")} - options={renderOptions(diagnosisInList)} - loading={diagnosisInStatus === "LOADING"} - disabled={isLoading} - /> - </div> - </div> - {admitted && ( - <div> - <div className="row start-sm center-xs"> - <div className="patientCurrentAdmission__item"> - <DateField - fieldName="disDate" - fieldValue={formik.values.disDate} - disableFuture={true} - theme="regular" - format="dd/MM/yyyy" - isValid={isValid("disDate")} - errorText={getErrorText("disDate")} - label={t("admission.disDate")} - onChange={dateFieldHandleOnChange("disDate")} - disabled={isLoading} - /> - </div> - <div className="patientCurrentAdmission__item"> - <TextField - field={formik.getFieldProps("bedDays")} - theme="regular" - label={t("admission.bedDays")} - isValid={isValid("bedDays")} - errorText={getErrorText("bedDays")} - onBlur={formik.handleBlur} - disabled={true} - type="number" - /> - </div> - </div> - <div className="row start-sm center-xs"> - <div className="patientCurrentAdmission__item"> - <AutocompleteField - fieldName="disType" - fieldValue={formik.values.disType} - label={t("admission.disType")} - isValid={isValid("disType")} - errorText={getErrorText("disType")} - onBlur={onBlurCallback("disType")} - options={renderOptions(dischargeTypes)} - loading={disTypeStatus === "LOADING"} - disabled={isLoading} - /> - </div> - <div className="patientCurrentAdmission__item"> - <AutocompleteField - fieldName="diseaseOut1" - fieldValue={formik.values.diseaseOut1} - label={t("admission.diseaseOut1")} - isValid={isValid("diseaseOut1")} - errorText={getErrorText("diseaseOut1")} - onBlur={onBlurCallback("diseaseOut1")} - options={renderOptions(diagnosisOutList)} - loading={diagnosisOutStatus === "LOADING"} - disabled={isLoading} - /> - </div> - </div> - <div className="row start-sm center-xs"> - <div className="patientCurrentAdmission__item"> - <AutocompleteField - fieldName="diseaseOut2" - fieldValue={formik.values.diseaseOut2} - label={t("admission.diseaseOut2")} - isValid={isValid("diseaseOut2")} - errorText={getErrorText("diseaseOut2")} - onBlur={onBlurCallback("diseaseOut2")} - options={renderOptions(diagnosisOutList)} - loading={diagnosisOutStatus === "LOADING"} - disabled={isLoading} - /> - </div> - <div className="patientCurrentAdmission__item"> - <AutocompleteField - fieldName="diseaseOut3" - fieldValue={formik.values.diseaseOut3} - label={t("admission.diseaseOut3")} - isValid={isValid("diseaseOut3")} - errorText={getErrorText("diseaseOut3")} - onBlur={onBlurCallback("diseaseOut3")} - options={renderOptions(diagnosisOutList)} - loading={diagnosisOutStatus === "LOADING"} - disabled={isLoading} - /> - </div> - </div> - <div className="row start-sm center-xs"> - <div className="patientCurrentAdmission__item"> - <TextField - field={formik.getFieldProps("cliDiaryCharge")} - theme="regular" - label={t("admission.cliDiaryCharge")} - isValid={isValid("cliDiaryCharge")} - errorText={getErrorText("cliDiaryCharge")} - onBlur={formik.handleBlur} - type="text" - disabled={isLoading} - /> - </div> - <div className="patientCurrentAdmission__item"> - <TextField - field={formik.getFieldProps("imageryCharge")} - theme="regular" - label={t("admission.imageryCharge")} - isValid={isValid("imageryCharge")} - errorText={getErrorText("imageryCharge")} - onBlur={formik.handleBlur} - type="text" - disabled={isLoading} - /> - </div> - </div> - </div> - )} - <div className="row start-sm center-xs"> - <div className="fullWidth patientCurrentAdmission__item"> - <TextField - field={formik.getFieldProps("note")} - theme="regular" - label={t("admission.note")} - multiline={true} - type="text" - isValid={isValid("note")} - errorText={getErrorText("note")} - onBlur={formik.handleBlur} - rows={5} - disabled={isLoading} - /> - </div> - </div> - - <div className="patientCurrentAdmission__buttonSet"> - <div className="submit_button"> - <Button type="submit" variant="contained" disabled={isLoading}> - {submitButtonLabel} - </Button> - </div> - <div className="reset_button"> - <Button - type="reset" - variant="text" - disabled={isLoading} - onClick={() => setOpenResetConfirmation(true)} - > - {resetButtonLabel} - </Button> - </div> - </div> - <ConfirmationDialog - isOpen={openResetConfirmation} - title={resetButtonLabel.toUpperCase()} - info={t("common.resetform")} - icon={warningIcon} - primaryButtonLabel={resetButtonLabel} - secondaryButtonLabel={t("common.discard")} - handlePrimaryButtonClick={handleResetConfirmation} - handleSecondaryButtonClick={() => setOpenResetConfirmation(false)} - /> - </form> - </div> - </> - ); -}; - -export default CurrentAdmission; diff --git a/src/components/accessories/admission/currentAdmission/styles.scss b/src/components/accessories/admission/currentAdmission/styles.scss deleted file mode 100644 index a35ecc353..000000000 --- a/src/components/accessories/admission/currentAdmission/styles.scss +++ /dev/null @@ -1,82 +0,0 @@ -@import "../../../../styles/variables"; -@import "../../../../../node_modules/susy/sass/susy"; - -.patientAdmissionForm { - display: inline-block; - flex-direction: column; - align-items: center; - width: 100%; - - .formInsertMode{ - margin: 0px 0px 20px; - } - - .patientAdmissionForm__item { - margin: 7px 0px; - padding: 0px 15px; - width: 50%; - @include susy-media($narrow) { - padding: 0px 10px; - } - @include susy-media($tablet_land) { - padding: 0px 10px; - } - @include susy-media($tablet_port) { - width: 50%; - } - @include susy-media($smartphone) { - width: 100%; - } - .dateField, - .textField, - .selectField { - width: 100%; - } - - &.fullWidth { - width: 100%; - } - - &.halfWidth { - width: 50%; - @include susy-media($smartphone) { - width: 100%; - } - } - - &.compressed { - .textField { - float: left; - width: 50%; - &:nth-of-type(1) { - padding-right: 10px; - } - } - } - } - - .patientAdmissionForm__buttonSet { - display: flex; - margin-top: 25px; - padding: 0px 15px; - flex-direction: row-reverse; - @include susy-media($smartphone_small) { - display: block; - } - - .submit_button, - .reset_button { - .MuiButton-label { - font-size: smaller; - letter-spacing: 1px; - font-weight: 600; - } - button { - @include susy-media($smartphone_small) { - width: 100%; - margin-top: 10px; - } - } - } - } -} diff --git a/src/components/accessories/admission/currentAdmission/types.ts b/src/components/accessories/admission/currentAdmission/types.ts deleted file mode 100644 index 28816ac6a..000000000 --- a/src/components/accessories/admission/currentAdmission/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { AdmissionDTO } from "../../../../generated"; -import { TFields } from "../../../../libraries/formDataHandling/types"; - -interface ICurrentAdmissionProps { - fields: TFields<AdmissionFormFieldName>; - onSubmit: (adm: AdmissionDTO) => void; - creationMode: boolean; - submitButtonLabel: string; - resetButtonLabel: string; - isLoading: boolean; - admitted: boolean; - shouldResetForm: boolean; - resetFormCallback: () => void; -} - -export type CurrentAdmissionProps = ICurrentAdmissionProps; - -export type AdmissionFormFieldName = - | "ward" - | "transUnit" - | "admDate" - | "admType" - | "diseaseIn" - | "fhu" - | "note" - | "disDate" - | "disType" - | "bedDays" - | "diseaseOut1" - | "diseaseOut2" - | "diseaseOut3" - | "cliDiaryCharge" - | "imageryCharge"; diff --git a/src/components/accessories/currentAdmission/CurrentAdmission.tsx b/src/components/accessories/currentAdmission/CurrentAdmission.tsx new file mode 100644 index 000000000..36ead8e96 --- /dev/null +++ b/src/components/accessories/currentAdmission/CurrentAdmission.tsx @@ -0,0 +1,73 @@ +import React, { FunctionComponent, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { AdmissionDTO, OpdDTO, PatientDTO } from "../../../generated"; +import { updateAdmission } from "../../../state/admissions/actions"; +import { IState } from "../../../types"; +import { useFields } from "../admission/useFields"; +import { CurrentAdmissionData } from "./currentAdmissionData/CurrentAdmissionData"; +import { CurrentAdmissionForm } from "./currentAdmissionForm/CurrentAdmissionForm"; +import "./styles.scss"; +import { IOwnProps } from "./types"; + +export const CurrentAdmission: FunctionComponent<IOwnProps> = ({ + onEditChange, +}) => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [editionMode, setEditionMode] = useState(false); + const currentAdmission = useSelector( + (state: IState) => state.admissions.currentAdmissionByPatientId.data + ); + const lastOpd = useSelector<IState, OpdDTO | undefined>( + (state) => state.opds.lastOpd.data + ); + + const handleEdit = () => { + setEditionMode(true); + }; + + const handleDiscard = () => { + setEditionMode(false); + }; + + const fields = useFields(currentAdmission, lastOpd?.disease); + + const onSubmit = (adm: AdmissionDTO) => { + let admissionToSave: AdmissionDTO = { + ...currentAdmission, + deleted: "N", + type: adm.type, + admitted: adm.admitted, + fhu: adm.fhu, + admDate: adm.admDate, + admType: adm.admType, + diseaseIn: adm.diseaseIn, + note: adm.note, + ward: adm.ward, + }; + dispatch(updateAdmission(admissionToSave)); + }; + + useEffect(() => { + onEditChange(editionMode); + }, [editionMode]); + + return ( + <div className="currentAdmission"> + {currentAdmission && !editionMode && ( + <CurrentAdmissionData + onEdit={handleEdit} + admission={currentAdmission} + /> + )} + {currentAdmission && editionMode && ( + <CurrentAdmissionForm + fields={fields} + onSubmit={onSubmit} + onDiscard={handleDiscard} + /> + )} + </div> + ); +}; diff --git a/src/components/accessories/currentAdmission/currentAdmissionData/CurrentAdmissionData.tsx b/src/components/accessories/currentAdmission/currentAdmissionData/CurrentAdmissionData.tsx new file mode 100644 index 000000000..d2eab5242 --- /dev/null +++ b/src/components/accessories/currentAdmission/currentAdmissionData/CurrentAdmissionData.tsx @@ -0,0 +1,75 @@ +import { IconButton } from "@material-ui/core"; +import { Edit } from "@material-ui/icons"; +import React, { FunctionComponent } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { AdmissionDTO, PatientDTO } from "../../../../generated"; +import { parseDate } from "../../../../libraries/formDataHandling/functions"; +import { IState } from "../../../../types"; +import Button from "../../button/Button"; +import TextField from "../../textField/TextField"; +import "../styles.scss"; +import AutocompleteField from "../../autocompleteField/AutocompleteField"; +import DateField from "../../dateField/DateField"; +import isEmpty from "lodash.isempty"; +import { renderDate } from "../../../../libraries/formatUtils/dataFormatting"; + +interface IOwnProps { + onEdit: () => void; + admission: AdmissionDTO; +} + +export const CurrentAdmissionData: FunctionComponent<IOwnProps> = ({ + onEdit, + admission, +}) => { + const { t } = useTranslation(); + + return ( + <div className="currentAdmissionData"> + <div className="currentAdmission_leading"> + <IconButton onClick={onEdit}> + <Edit /> + </IconButton> + </div> + <div className="currentAdmissionData__content"> + {!isEmpty(admission?.ward?.description) && ( + <div className="currentAdmissionData__item"> + <span className="item_label">{t("admission.ward")}</span> + <p className="item_content">{admission?.ward?.description}</p> + </div> + )} + {!isEmpty(admission?.fhu) && ( + <div className="currentAdmissionData__item"> + <span className="item_label">{t("admission.fhu")}</span> + <p className="item_content">{admission?.fhu}</p> + </div> + )} + {!isEmpty(admission?.admDate) && ( + <div className="currentAdmissionData__item"> + <span className="item_label">{t("admission.admDate")}</span> + <p className="item_content">{renderDate(admission?.admDate)}</p> + </div> + )} + {!isEmpty(admission?.admType?.description) && ( + <div className="currentAdmissionData__item"> + <span className="item_label">{t("admission.admType")}</span> + <p className="item_content">{admission?.admType?.description}</p> + </div> + )} + {!isEmpty(admission?.diseaseIn?.description) && ( + <div className="currentAdmissionData__item"> + <span className="item_label">{t("admission.diseaseIn")}</span> + <p className="item_content">{admission?.diseaseIn?.description}</p> + </div> + )} + {!isEmpty(admission?.note) && ( + <div className="fullWidth currentAdmissionData__item"> + <span className="item_label">{t("admission.note")}</span> + <p className="item_content">{admission?.note}</p> + </div> + )} + </div> + </div> + ); +}; diff --git a/src/components/accessories/currentAdmission/currentAdmissionForm/CurrentAdmissionForm.tsx b/src/components/accessories/currentAdmission/currentAdmissionForm/CurrentAdmissionForm.tsx new file mode 100644 index 000000000..ffab467e8 --- /dev/null +++ b/src/components/accessories/currentAdmission/currentAdmissionForm/CurrentAdmissionForm.tsx @@ -0,0 +1,320 @@ +import { IconButton } from "@material-ui/core"; +import { Edit } from "@material-ui/icons"; +import { useFormik } from "formik"; +import get from "lodash.get"; +import has from "lodash.has"; +import React, { + FunctionComponent, + useCallback, + useEffect, + useState, +} from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { + AdmissionTypeDTO, + DiseaseDTO, + DiseaseTypeDTO, + PatientDTO, + WardDTO, +} from "../../../../generated"; +import { + differenceInDays, + formatAllFieldValues, + getFromFields, + parseDate, +} from "../../../../libraries/formDataHandling/functions"; +import checkIcon from "../../../../assets/check-icon.png"; +import { + getPatientThunk, + updatePatient, + updatePatientReset, +} from "../../../../state/patients/actions"; +import { TAPIResponseStatus } from "../../../../state/types"; +import { IState } from "../../../../types"; +import Button from "../../button/Button"; +import InfoBox from "../../infoBox/InfoBox"; +import TextField from "../../textField/TextField"; +import { initialFields } from "./consts"; +import { IOwnProps, TActivityTransitionState } from "./types"; +import ConfirmationDialog from "../../confirmationDialog/ConfirmationDialog"; +import AutocompleteField from "../../autocompleteField/AutocompleteField"; +import DateField from "../../dateField/DateField"; +import { updateAdmissionReset } from "../../../../state/admissions/actions"; + +export const CurrentAdmissionForm: FunctionComponent<IOwnProps> = ({ + onDiscard, + onSubmit, + fields, +}) => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [activityTransitionState, setActivityTransitionState] = + useState<TActivityTransitionState>("IDLE"); + const patient = useSelector<IState, PatientDTO | undefined>( + (state) => state.patients.selectedPatient.data + ); + const currentAdmission = useSelector( + (state: IState) => state.admissions.currentAdmissionByPatientId.data + ); + const status = useSelector<IState, TAPIResponseStatus | undefined>( + (state) => state.admissions.updateAdmission.status + ); + + const errorMessage = useSelector<IState, string>( + (state) => + state.patients.updatePatient.error?.message || t("common.somethingwrong") + ); + + const diagnosisInList = useSelector( + (state: IState) => state.diseases.diseasesIpdIn.data + ); + + const admissionTypes = useSelector( + (state: IState) => state.admissionTypes.allAdmissionTypes.data + ); + const wards = useSelector((state: IState) => state.wards.allWards.data); + const diagnosisInStatus = useSelector( + (state: IState) => state.diseases.diseasesIpdIn.status + ); + const wardStatus = useSelector( + (state: IState) => state.wards.allWards.status + ); + const admTypeStatus = useSelector( + (state: IState) => state.admissionTypes.allAdmissionTypes.status + ); + + const renderOptions = ( + data: + | ( + | WardDTO + | DiseaseDTO + | AdmissionTypeDTO + | DiseaseTypeDTO + | DiseaseDTO + )[] + | undefined + ) => { + if (data) { + return data.map((item) => { + return { + value: item.code?.toString() ?? "", + label: item.description ?? "", + }; + }); + } else return []; + }; + + const formik = useFormik({ + initialValues: getFromFields(fields, "value"), + enableReinitialize: true, + onSubmit: (values) => { + const formattedValues = formatAllFieldValues( + initialFields(currentAdmission), + values + ); + formattedValues.diseaseIn = diagnosisInList?.find( + (item) => item.code === formattedValues.diseaseIn + ); + formattedValues.admType = admissionTypes?.find( + (item) => item.code === formattedValues.admType + ); + formattedValues.ward = wards?.find( + (item) => item.code === formattedValues.ward + ); + onSubmit({ + ...currentAdmission, + ...formattedValues, + } as any); + }, + }); + + useEffect(() => { + if (activityTransitionState === "TO_RESET") { + dispatch(updateAdmissionReset()); + if (patient?.code) { + dispatch(getPatientThunk(patient?.code?.toString())); + } + onDiscard(); + } + }, [dispatch, activityTransitionState]); + + const { setFieldValue, resetForm, handleBlur } = formik; + + const isValid = (fieldName: string): boolean => { + return has(formik.touched, fieldName) && has(formik.errors, fieldName); + }; + + const getErrorText = (fieldName: string): string => { + return has(formik.touched, fieldName) + ? (get(formik.errors, fieldName) as string) + : ""; + }; + + const dateFieldHandleOnChange = useCallback( + (fieldName: string) => (value: any) => { + setFieldValue(fieldName, value); + formik.setFieldTouched(fieldName); + const days = differenceInDays( + new Date(formik.values.admDate), + new Date(formik.values.disDate) + ).toString(); + setFieldValue("bedDays", days); + }, + [setFieldValue] + ); + + const onBlurCallback = useCallback( + (fieldName: string) => + (e: React.FocusEvent<HTMLDivElement>, value: string) => { + handleBlur(e); + setFieldValue(fieldName, value); + }, + [setFieldValue, handleBlur] + ); + + const isLoading = status === "LOADING"; + + return ( + <> + <form className="currentAdmissionForm" onSubmit={formik.handleSubmit}> + <div className="row start-sm center-xs"> + <div className="currentAdmissionForm__item"> + <AutocompleteField + fieldName="ward" + fieldValue={formik.values.ward} + label={t("admission.ward")} + isValid={isValid("ward")} + errorText={getErrorText("ward")} + onBlur={onBlurCallback("ward")} + options={renderOptions(wards)} + loading={wardStatus === "LOADING"} + disabled={isLoading} + /> + </div> + <div className="currentAdmissionForm__item"> + <TextField + field={formik.getFieldProps("fhu")} + theme="regular" + label={t("admission.fhu")} + isValid={isValid("fhu")} + errorText={getErrorText("fhu")} + onBlur={formik.handleBlur} + type="text" + disabled={isLoading} + /> + </div> + <div className="currentAdmissionForm__item"> + <DateField + fieldName="admDate" + fieldValue={formik.values.admDate} + disableFuture={true} + theme="regular" + format="dd/MM/yyyy" + isValid={isValid("admDate")} + errorText={getErrorText("admDate")} + label={t("admission.admDate")} + onChange={dateFieldHandleOnChange("admDate")} + disabled={isLoading} + /> + </div> + <div className="currentAdmissionForm__item"> + <AutocompleteField + fieldName="admType" + fieldValue={formik.values.admType} + label={t("admission.admType")} + isValid={isValid("admType")} + errorText={getErrorText("admType")} + onBlur={onBlurCallback("admType")} + options={renderOptions(admissionTypes)} + loading={admTypeStatus === "LOADING"} + disabled={isLoading} + /> + </div> + <div className="currentAdmissionForm__item"> + <AutocompleteField + fieldName="diseaseIn" + fieldValue={formik.values.diseaseIn} + label={t("admission.diseaseIn")} + isValid={isValid("diseaseIn")} + errorText={getErrorText("diseaseIn")} + onBlur={onBlurCallback("diseaseIn")} + options={renderOptions(diagnosisInList)} + loading={diagnosisInStatus === "LOADING"} + disabled={isLoading} + /> + </div> + <div className="currentAdmissionForm__item"> + <TextField + field={formik.getFieldProps("cliDiaryCharge")} + theme="regular" + label={t("admission.cliDiaryCharge")} + isValid={isValid("cliDiaryCharge")} + errorText={getErrorText("cliDiaryCharge")} + onBlur={formik.handleBlur} + type="text" + disabled={isLoading} + /> + </div> + <div className="currentAdmissionForm__item"> + <TextField + field={formik.getFieldProps("imageryCharge")} + theme="regular" + label={t("admission.imageryCharge")} + isValid={isValid("imageryCharge")} + errorText={getErrorText("imageryCharge")} + onBlur={formik.handleBlur} + type="text" + disabled={isLoading} + /> + </div> + <div className="fullWidth currentAdmissionForm__item"> + <TextField + field={formik.getFieldProps("note")} + theme="regular" + label={t("admission.note")} + multiline={true} + type="text" + isValid={isValid("note")} + errorText={getErrorText("note")} + onBlur={formik.handleBlur} + rows={5} + disabled={isLoading} + /> + </div> + </div> + <div className="currentAdmissionForm__buttonSet"> + <div className="submit_button"> + <Button type="submit" variant="contained" disabled={isLoading}> + {t("patient.savechanges")} + </Button> + </div> + <div className="reset_button"> + <Button + variant="text" + disabled={isLoading} + onClick={onDiscard} + type={undefined} + > + {t("patient.discardchanges")} + </Button> + </div> + </div> + </form> + {status === "FAIL" && ( + <div> + <InfoBox type="error" message={errorMessage} /> + </div> + )} + <ConfirmationDialog + isOpen={status === "SUCCESS"} + title={t("patient.dataupdated")} + icon={checkIcon} + info={t("patient.dataupdatedsuccessfully")} + primaryButtonLabel={t("common.ok")} + handlePrimaryButtonClick={() => setActivityTransitionState("TO_RESET")} + handleSecondaryButtonClick={() => {}} + /> + </> + ); +}; diff --git a/src/components/accessories/currentAdmission/currentAdmissionForm/consts.ts b/src/components/accessories/currentAdmission/currentAdmissionForm/consts.ts new file mode 100644 index 000000000..7cde8d91c --- /dev/null +++ b/src/components/accessories/currentAdmission/currentAdmissionForm/consts.ts @@ -0,0 +1,50 @@ +import { AdmissionDTO, PatientDTO } from "../../../../generated"; +import { parseDate } from "../../../../libraries/formDataHandling/functions"; +import { TFields } from "../../../../libraries/formDataHandling/types"; +import { TCurrentAdmissionFieldName } from "./types"; + +export const initialFields = ( + patient: AdmissionDTO | undefined +): TFields<TCurrentAdmissionFieldName> => { + return { + ward: { + value: "", + type: "text", + options: [], + }, + transUnit: { + value: "10", + type: "number", + }, + fhu: { + value: "", + type: "text", + }, + admDate: { + value: parseDate(Date.now().toString()), + type: "date", + }, + admType: { + value: "", + type: "text", + options: [], + }, + diseaseIn: { + value: "", + type: "text", + options: [], + }, + note: { + value: "", + type: "text", + }, + cliDiaryCharge: { + value: "", + type: "text", + }, + imageryCharge: { + value: "", + type: "text", + }, + }; +}; diff --git a/src/components/accessories/currentAdmission/currentAdmissionForm/types.ts b/src/components/accessories/currentAdmission/currentAdmissionForm/types.ts new file mode 100644 index 000000000..ca7147d8a --- /dev/null +++ b/src/components/accessories/currentAdmission/currentAdmissionForm/types.ts @@ -0,0 +1,23 @@ +import { AdmissionDTO } from "../../../../generated"; +import { IForm, TFields } from "../../../../libraries/formDataHandling/types"; + +export type TProps = IForm<TCurrentAdmissionFieldName, any>; + +export type TCurrentAdmissionFieldName = + | "ward" + | "transUnit" + | "admDate" + | "admType" + | "diseaseIn" + | "fhu" + | "note" + | "cliDiaryCharge" + | "imageryCharge"; + +export type TActivityTransitionState = "IDLE" | "TO_RESET" | "FAIL"; + +export interface IOwnProps { + onDiscard: () => void; + fields: TFields<TCurrentAdmissionFieldName>; + onSubmit: (adm: AdmissionDTO) => void; +} diff --git a/src/components/accessories/currentAdmission/styles.scss b/src/components/accessories/currentAdmission/styles.scss new file mode 100644 index 000000000..e837206e2 --- /dev/null +++ b/src/components/accessories/currentAdmission/styles.scss @@ -0,0 +1,111 @@ +@import "../../../styles/variables"; +@import "../../../../node_modules/susy/sass/susy"; + +.currentAdmission { + flex-direction: column; + display: flex; + row-gap: 4px; + padding: 4px 16px; + margin-bottom: 16px; + border-radius: 5px; + .currentAdmissionData { + display: flex; + flex-direction: column; + padding: 16px; + border-radius: 4px; + row-gap: 4px; + background-color: rgba($c-grey-light, 0.2); + .currentAdmission_leading { + display: flex; + justify-content: end; + } + .currentAdmissionData__content { + display: flex; + flex-direction: column; + flex-wrap: wrap; + column-gap: 4px; + .currentAdmissionData__item { + display: flex; + flex-direction: column; + justify-content: start; + flex-grow: 0; + + &.fullWidth { + width: 100%; + } + } + .item_label { + font-size: 0.85em; + font-weight: 600; + } + } + } + .currentAdmissionForm { + display: flex; + flex-direction: column; + row-gap: 4px; + .currentAdmissionForm__item { + margin: 7px 0px; + padding: 0px 15px; + width: 50%; + @include susy-media($narrow) { + padding: 0px 10px; + } + @include susy-media($tablet_land) { + padding: 0px 10px; + } + @include susy-media($tablet_port) { + width: 50%; + } + @include susy-media($smartphone) { + width: 100%; + } + .dateField, + .textField, + .selectField { + width: 100%; + } + + &.fullWidth { + width: 100%; + } + + &.halfWidth { + width: 50%; + @include susy-media($smartphone) { + width: 100%; + } + } + + &.compressed { + .textField { + float: left; + width: 50%; + &:nth-of-type(1) { + padding-right: 10px; + } + } + } + } + .currentAdmissionForm__buttonSet { + display: flex; + margin-top: 10px; + flex-direction: row-reverse; + justify-content: end; + + .submit_button { + .MuiButton-label { + font-size: smaller; + letter-spacing: 1px; + font-weight: 600; + } + button { + @include susy-media($smartphone_small) { + width: 100%; + margin-top: 10px; + } + } + } + } + } +} diff --git a/src/components/accessories/currentAdmission/types.ts b/src/components/accessories/currentAdmission/types.ts new file mode 100644 index 000000000..a5a849d04 --- /dev/null +++ b/src/components/accessories/currentAdmission/types.ts @@ -0,0 +1,3 @@ +export interface IOwnProps { + onEditChange: (value: boolean) => void; +}