diff --git a/src/components/accessories/admission/PatientAdmission.tsx b/src/components/accessories/admission/PatientAdmission.tsx index 3f9435caf..1c2a1ae5e 100644 --- a/src/components/accessories/admission/PatientAdmission.tsx +++ b/src/components/accessories/admission/PatientAdmission.tsx @@ -6,7 +6,7 @@ import { scrollToElement } from "../../../libraries/uiUtils/scrollToElement"; import { useDispatch, useSelector } from "react-redux"; import { IState } from "../../../types"; import { AdmissionTransitionState } from "./types"; -import { AdmissionDTO, OpdDTO } from "../../../generated"; +import { AdmissionDTO, OpdDTO, PatientDTOStatusEnum } from "../../../generated"; import InfoBox from "../infoBox/InfoBox"; import ConfirmationDialog from "../confirmationDialog/ConfirmationDialog"; import checkIcon from "../../../assets/check-icon.png"; @@ -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(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(); @@ -188,11 +190,16 @@ const PatientAdmission: FC = () => { scrollToElement(null); }; + const onCurrentAdmissionChange = (value: boolean) => { + setIsEditingCurrent(value); + }; + return (
- {!showForm && ( + {patient?.status === PatientDTOStatusEnum.I && ( )} + {!open && } {open && ( { /> = ({ formattedValues.admType = admissionTypes?.find( (item) => item.code === formattedValues.admType ); + formattedValues.type = formattedValues.admType?.code; formattedValues.ward = wards?.find( (item) => item.code === formattedValues.ward ); 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 = ({ const data = useSelector((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/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 = ({ + onEditChange, +}) => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [editionMode, setEditionMode] = useState(false); + const currentAdmission = useSelector( + (state: IState) => state.admissions.currentAdmissionByPatientId.data + ); + const lastOpd = useSelector( + (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 ( +
+ {currentAdmission && !editionMode && ( + + )} + {currentAdmission && editionMode && ( + + )} +
+ ); +}; 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 = ({ + onEdit, + admission, +}) => { + const { t } = useTranslation(); + + return ( +
+
+ + + +
+
+ {!isEmpty(admission?.ward?.description) && ( +
+ {t("admission.ward")} +

{admission?.ward?.description}

+
+ )} + {!isEmpty(admission?.fhu) && ( +
+ {t("admission.fhu")} +

{admission?.fhu}

+
+ )} + {!isEmpty(admission?.admDate) && ( +
+ {t("admission.admDate")} +

{renderDate(admission?.admDate)}

+
+ )} + {!isEmpty(admission?.admType?.description) && ( +
+ {t("admission.admType")} +

{admission?.admType?.description}

+
+ )} + {!isEmpty(admission?.diseaseIn?.description) && ( +
+ {t("admission.diseaseIn")} +

{admission?.diseaseIn?.description}

+
+ )} + {!isEmpty(admission?.note) && ( +
+ {t("admission.note")} +

{admission?.note}

+
+ )} +
+
+ ); +}; diff --git a/src/components/accessories/currentAdmission/currentAdmissionForm/CurrentAdmissionForm.tsx b/src/components/accessories/currentAdmission/currentAdmissionForm/CurrentAdmissionForm.tsx new file mode 100644 index 000000000..010ac8d3d --- /dev/null +++ b/src/components/accessories/currentAdmission/currentAdmissionForm/CurrentAdmissionForm.tsx @@ -0,0 +1,297 @@ +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 = ({ + onDiscard, + onSubmit, + fields, +}) => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [activityTransitionState, setActivityTransitionState] = + useState("IDLE"); + const patient = useSelector( + (state) => state.patients.selectedPatient.data + ); + const currentAdmission = useSelector( + (state: IState) => state.admissions.currentAdmissionByPatientId.data + ); + const status = useSelector( + (state) => state.admissions.updateAdmission.status + ); + + const errorMessage = useSelector( + (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.type = formattedValues.admType?.code; + 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, value: string) => { + handleBlur(e); + setFieldValue(fieldName, value); + }, + [setFieldValue, handleBlur] + ); + + const isLoading = status === "LOADING"; + + return ( + <> +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ {status === "FAIL" && ( +
+ +
+ )} + 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..4763bff7f --- /dev/null +++ b/src/components/accessories/currentAdmission/currentAdmissionForm/consts.ts @@ -0,0 +1,42 @@ +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 => { + 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", + }, + }; +}; diff --git a/src/components/accessories/currentAdmission/currentAdmissionForm/types.ts b/src/components/accessories/currentAdmission/currentAdmissionForm/types.ts new file mode 100644 index 000000000..a55a86afc --- /dev/null +++ b/src/components/accessories/currentAdmission/currentAdmissionForm/types.ts @@ -0,0 +1,21 @@ +import { AdmissionDTO } from "../../../../generated"; +import { IForm, TFields } from "../../../../libraries/formDataHandling/types"; + +export type TProps = IForm; + +export type TCurrentAdmissionFieldName = + | "ward" + | "transUnit" + | "admDate" + | "admType" + | "diseaseIn" + | "fhu" + | "note"; + +export type TActivityTransitionState = "IDLE" | "TO_RESET" | "FAIL"; + +export interface IOwnProps { + onDiscard: () => void; + fields: TFields; + 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..ddd65ea28 --- /dev/null +++ b/src/components/accessories/currentAdmission/styles.scss @@ -0,0 +1,114 @@ +@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-wrap: wrap; + column-gap: 4px; + .currentAdmissionData__item { + display: flex; + flex-direction: column; + justify-content: start; + flex-grow: 0; + width: 48%; + @include susy-media($smartphone) { + width: 100%; + } + + &.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; +} diff --git a/src/mockServer/routes/admissions.js b/src/mockServer/routes/admissions.js index 926c4c95f..05df73ad0 100644 --- a/src/mockServer/routes/admissions.js +++ b/src/mockServer/routes/admissions.js @@ -79,7 +79,7 @@ export const admissionRoutes = (server) => { res.body = null; break; default: - res.status(200).json(admissionDTO); + res.status(200).json({ ...admissionDTO, id: 0 }); } }); server.post("/discharge").intercept((req, res) => { diff --git a/src/resources/i18n/en.json b/src/resources/i18n/en.json index ccc5dea76..fc15bbe40 100644 --- a/src/resources/i18n/en.json +++ b/src/resources/i18n/en.json @@ -583,7 +583,7 @@ "validatelastdate": "should be greater or equal to admission date {{admDate}}", "currentadmissionexists": "The patient has an ongoing admission dating from {{date}}. Discharge him first.", "patientnotadmitted": "No current admission found !", - "patientalreadyadmitted": "The patient has already been admitted, here you will find the history of admissions. To resign, go to the dismission page.", + "patientalreadyadmitted": "The patient has already been admitted, here you will find the history of admissions. To discharge, go to the Discharge page.", "updated": "Admission updated", "updatesuccess": "Admimission updated successfully", "datebefore": "Admission date must be before the discharge date",