From 72918b4a10e013a7fdba5ff518643b4ecd5e2442 Mon Sep 17 00:00:00 2001 From: Silevester Dongmo <58907550+SilverD3@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:41:14 +0100 Subject: [PATCH] OH2-415 | OH2-316 | Types / Ages CRUD (#689) * chore:align API spec file * chore:align API spec file * chore:align with API spec * feat:add age types management * chore:add e2e tests * chore:add validation * Update src/resources/i18n/en.json * chore:code refactoring --- api/oh.yaml | 12 +- .../ages_activities/edit_ages_activity.cy.ts | 50 +++++ .../manage_ages_activity.cy.ts | 19 ++ .../accessories/admin/types/TypesAdmin.tsx | 9 +- .../types/components/agetypes/AgeTypes.tsx | 47 +++++ .../agetypes/ageTypesForm/AgeTypeFields.tsx | 44 ++++ .../agetypes/ageTypesForm/AgeTypesForm.tsx | 192 ++++++++++++++++++ .../agetypes/ageTypesForm/consts.test.ts | 41 ++++ .../agetypes/ageTypesForm/consts.ts | 44 ++++ .../components/agetypes/ageTypesForm/index.ts | 2 + .../agetypes/ageTypesForm/styles.scss | 73 +++++++ .../components/agetypes/ageTypesForm/types.ts | 20 ++ .../agetypes/ageTypesTable/AgeTypesTable.tsx | 87 ++++++++ .../agetypes/ageTypesTable/index.ts | 3 + .../agetypes/ageTypesTable/styles.scss | 13 ++ .../agetypes/editAgeTypes/EditAgeTypes.tsx | 67 ++++++ .../components/agetypes/editAgeTypes/index.ts | 1 + .../agetypes/editAgeTypes/styles.scss | 14 ++ .../admin/types/components/agetypes/index.ts | 6 + .../types/components/agetypes/styles.scss | 3 + .../dashboard/admissions/Admissions.tsx | 2 +- .../admissionByAgeType/AdmissionByAgeType.tsx | 2 +- .../dashboard/admissions/useData.ts | 4 +- .../dashboard/discharges/Discharges.tsx | 2 +- .../DischargesByAgeTypes.tsx | 2 +- .../dashboard/discharges/useData.ts | 4 +- .../accessories/dashboard/opds/Opds.tsx | 2 +- .../opds/opdByAgeTypes/OpdByAgeTypes.tsx | 2 +- .../accessories/dashboard/opds/useData.ts | 4 +- .../patientDataForm/PatientDataForm.tsx | 6 +- src/consts.ts | 2 + src/generated/apis/AgeTypesApi.ts | 10 +- .../admissions/useAdmByAgeTypeData.ts | 4 +- .../discharges/useDisByAgeTypeData.ts | 4 +- .../opds/useOpdByAgeTypeData.ts | 4 +- src/mockServer/fixtures/ageTypeDTO.js | 20 +- src/mockServer/routes/ageTypes.js | 10 + src/resources/i18n/en.json | 17 ++ src/routes/Admin/TypesRoutes.tsx | 11 + src/state/ageTypes/initial.ts | 6 - src/state/ageTypes/slice.ts | 24 --- src/state/ageTypes/thunk.ts | 14 -- src/state/ageTypes/types.ts | 6 - src/state/store.ts | 2 - src/state/{ => types}/ageTypes/index.ts | 6 +- src/state/types/ageTypes/initial.ts | 7 + src/state/types/ageTypes/slice.ts | 41 ++++ src/state/types/ageTypes/thunk.ts | 23 +++ src/state/types/ageTypes/types.ts | 7 + src/state/types/slice.ts | 16 +- src/state/types/types.ts | 10 +- src/types.ts | 36 ++-- 52 files changed, 928 insertions(+), 129 deletions(-) create mode 100644 cypress/integrations/admin_activities/types_activities/ages_activities/edit_ages_activity.cy.ts create mode 100644 cypress/integrations/admin_activities/types_activities/ages_activities/manage_ages_activity.cy.ts create mode 100644 src/components/accessories/admin/types/components/agetypes/AgeTypes.tsx create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesForm/AgeTypeFields.tsx create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesForm/AgeTypesForm.tsx create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesForm/consts.test.ts create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesForm/consts.ts create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesForm/index.ts create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesForm/styles.scss create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesForm/types.ts create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesTable/AgeTypesTable.tsx create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesTable/index.ts create mode 100644 src/components/accessories/admin/types/components/agetypes/ageTypesTable/styles.scss create mode 100644 src/components/accessories/admin/types/components/agetypes/editAgeTypes/EditAgeTypes.tsx create mode 100644 src/components/accessories/admin/types/components/agetypes/editAgeTypes/index.ts create mode 100644 src/components/accessories/admin/types/components/agetypes/editAgeTypes/styles.scss create mode 100644 src/components/accessories/admin/types/components/agetypes/index.ts create mode 100644 src/components/accessories/admin/types/components/agetypes/styles.scss delete mode 100644 src/state/ageTypes/initial.ts delete mode 100644 src/state/ageTypes/slice.ts delete mode 100644 src/state/ageTypes/thunk.ts delete mode 100644 src/state/ageTypes/types.ts rename src/state/{ => types}/ageTypes/index.ts (96%) create mode 100644 src/state/types/ageTypes/initial.ts create mode 100644 src/state/types/ageTypes/slice.ts create mode 100644 src/state/types/ageTypes/thunk.ts create mode 100644 src/state/types/ageTypes/types.ts diff --git a/api/oh.yaml b/api/oh.yaml index b5234e957..5f67ee97e 100644 --- a/api/oh.yaml +++ b/api/oh.yaml @@ -1904,7 +1904,9 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/AgeTypeDTO" + type: array + items: + $ref: "#/components/schemas/AgeTypeDTO" required: true responses: "200": @@ -1912,7 +1914,9 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/AgeTypeDTO" + type: array + items: + $ref: "#/components/schemas/AgeTypeDTO" security: - bearerAuth: [] /admissiontypes: @@ -6949,11 +6953,11 @@ components: type: string description: "Flag record deleted, values are 'Y' OR 'N' " example: "N" + fhu: + type: string yprog: type: integer format: int32 - fhu: - type: string description: The admission AdmissionTypeDTO: required: diff --git a/cypress/integrations/admin_activities/types_activities/ages_activities/edit_ages_activity.cy.ts b/cypress/integrations/admin_activities/types_activities/ages_activities/edit_ages_activity.cy.ts new file mode 100644 index 000000000..95506cffc --- /dev/null +++ b/cypress/integrations/admin_activities/types_activities/ages_activities/edit_ages_activity.cy.ts @@ -0,0 +1,50 @@ +/// + +const AGE_TYPE_START_PATH = "/admin/types/ages"; + +describe("Admission types Edit Activity specs", () => { + it("should render the ui", () => { + cy.authenticate(AGE_TYPE_START_PATH); + cy.dataCy("sub-activity-title").contains("Manage age types"); + }); + + it("should show age types edit form", () => { + cy.dataCy("edit-age-types").click(); + cy.dataCy("sub-activity-title").contains("Edit age types"); + }); + + it("should fail to edit the age type", () => { + cy.byId("ageTypes\\[0\\]\\.to").type("1"); + cy.dataCy("submit-form").click(); + cy.dataCy("dialog-info").should("not.exist"); + }); + + it("should successfully save age types changes", () => { + cy.byId("ageTypes\\[0\\]\\.to").clear().type("0"); + cy.byId("ageTypes\\[5\\]\\.to").clear().type("104"); + cy.dataCy("submit-form").click(); + cy.dataCy("dialog-info").contains("have been updated successfully!"); + cy.dataCy("approve-dialog").click(); + }); + + it("should redirect after age types update", () => { + cy.dataCy("sub-activity-title").contains("Manage age types"); + }); + + it("should cancel the cancellation of the age types update", () => { + cy.dataCy("edit-age-types").click(); + cy.dataCy("cancel-form").click(); + cy.dataCy("dialog-info").contains( + "Are you sure to cancel the age types update?" + ); + cy.dataCy("close-dialog").click(); + cy.dataCy("dialog-info").should("not.exist"); + }); + + it("should cancel the age types update", () => { + cy.dataCy("cancel-form").click(); + cy.dataCy("approve-dialog").click(); + cy.dataCy("dialog-info").should("not.exist"); + cy.dataCy("sub-activity-title").contains("Manage age types"); + }); +}); diff --git a/cypress/integrations/admin_activities/types_activities/ages_activities/manage_ages_activity.cy.ts b/cypress/integrations/admin_activities/types_activities/ages_activities/manage_ages_activity.cy.ts new file mode 100644 index 000000000..034172dbd --- /dev/null +++ b/cypress/integrations/admin_activities/types_activities/ages_activities/manage_ages_activity.cy.ts @@ -0,0 +1,19 @@ +/// + +const AGE_TYPES_START_PATH = "/admin/types/ages"; + +describe("Age types Activity specs", () => { + it("should render the ui", () => { + cy.authenticate(AGE_TYPES_START_PATH); + cy.dataCy("sub-activity-title").contains("Manage age types"); + }); + + it("should present the table with 6 rows", () => { + cy.dataCy("age-types-table") + .find("table") + .then(($table) => { + const rows = $table.find("tbody tr"); + expect(rows.length).equal(6); + }); + }); +}); diff --git a/src/components/accessories/admin/types/TypesAdmin.tsx b/src/components/accessories/admin/types/TypesAdmin.tsx index fc1c40d32..0d1c30b38 100644 --- a/src/components/accessories/admin/types/TypesAdmin.tsx +++ b/src/components/accessories/admin/types/TypesAdmin.tsx @@ -29,14 +29,15 @@ const TypesAdmin = () => { [ defaultTypeOption, { label: t("types.exams"), value: "exams" }, + { label: t("types.ages"), value: "ages" }, { label: t("types.vaccines"), value: "vaccines" }, - { label: t("types.operations"), value: "operations" }, + { label: t("types.medicals"), value: "medicals" }, { label: t("types.diseases"), value: "diseases" }, - { label: t("types.deliveries"), value: "deliveries" }, { label: t("types.admissions"), value: "admissions" }, - { label: t("types.deliveryResultType"), value: "deliveryresulttypes" }, + { label: t("types.deliveries"), value: "deliveries" }, { label: t("types.discharges"), value: "discharges" }, - { label: t("types.medicals"), value: "medicals" }, + { label: t("types.operations"), value: "operations" }, + { label: t("types.deliveryResultType"), value: "deliveryresulttypes" }, { label: t("types.pregnantTreatment"), value: "pregnanttreatmenttypes" }, ], (type) => type.label diff --git a/src/components/accessories/admin/types/components/agetypes/AgeTypes.tsx b/src/components/accessories/admin/types/components/agetypes/AgeTypes.tsx new file mode 100644 index 000000000..fb683a32f --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/AgeTypes.tsx @@ -0,0 +1,47 @@ +import { useAppDispatch } from "libraries/hooks/redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router"; +import { getAgeTypes } from "state/types/ageTypes"; +import { setTypeMode } from "../../../../../../state/types/config"; +import Button from "../../../../button/Button"; +import AgeTypesTable from "./ageTypesTable"; +import "./styles.scss"; + +const AgeTypes = () => { + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + + useEffect(() => { + dispatch(getAgeTypes()); + dispatch(setTypeMode("manage")); + }, [dispatch]); + + const { t } = useTranslation(); + + return ( + <> +

{t("ageTypes.title")}

+ +
+ { + navigate("./edit"); + }} + type="button" + variant="contained" + color="primary" + dataCy="edit-age-types" + > + {t("ageTypes.editAgeTypes")} + + } + /> +
+ + ); +}; + +export default AgeTypes; diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesForm/AgeTypeFields.tsx b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/AgeTypeFields.tsx new file mode 100644 index 000000000..f66b218d5 --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/AgeTypeFields.tsx @@ -0,0 +1,44 @@ +import TextField from "components/accessories/textField/TextField"; +import React, { FC } from "react"; +import { useTranslation } from "react-i18next"; +import { IAgeTypeFieldsProps } from "./types"; + +const AgeTypeFields: FC = ({ + formik, + getErrorText, + isValid, + index, +}) => { + const { t } = useTranslation(); + + return ( + + {formik.values.ageTypes[index].code} + + + + + + + {t(formik.values.ageTypes[index].description)} + + ); +}; + +export default AgeTypeFields; diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesForm/AgeTypesForm.tsx b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/AgeTypesForm.tsx new file mode 100644 index 000000000..4b0f97bd6 --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/AgeTypesForm.tsx @@ -0,0 +1,192 @@ +import { useFormik } from "formik"; +import { AgeTypeDTO } from "generated"; +import { useAppDispatch, useAppSelector } from "libraries/hooks/redux"; +import { get, has } from "lodash"; +import React, { FC, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router"; +import { updateAgeTypeReset } from "state/types/ageTypes"; +import { array, number, object, ref, string } from "yup"; +import checkIcon from "../../../../../../../assets/check-icon.png"; +import warningIcon from "../../../../../../../assets/warning-icon.png"; +import { PATHS } from "../../../../../../../consts"; +import { + formatAllFieldValues, + getFromFields, +} from "../../../../../../../libraries/formDataHandling/functions"; +import Button from "../../../../../button/Button"; +import ConfirmationDialog from "../../../../../confirmationDialog/ConfirmationDialog"; +import InfoBox from "../../../../../infoBox/InfoBox"; +import AgeTypeFields from "./AgeTypeFields"; +import { validateRange } from "./consts"; +import "./styles.scss"; +import { IAgeTypesFormProps } from "./types"; + +const AgeTypesForm: FC = ({ + onSubmit, + rows, + submitButtonLabel, + resetButtonLabel, + isLoading, +}) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const navigate = useNavigate(); + const infoBoxRef = useRef(null); + const [openResetConfirmation, setOpenResetConfirmation] = useState(false); + const [validationErrors, setValidationErrors] = useState([]); + + const updateAgeTypes = useAppSelector((state) => state.types.ageTypes.update); + + const errorMessage = useMemo( + () => updateAgeTypes.error?.message ?? t("common.somethingwrong"), + [t, updateAgeTypes.error?.message] + ); + + const initialValues = { + ageTypes: rows.map((fields) => getFromFields(fields, "value")), + }; + + const validationSchema = object({ + ageTypes: array( + object({ + code: string().required(t("common.required")), + description: string().required(t("common.required")), + from: number() + .required(t("common.required")) + .min(0, t("common.greaterthan", { value: 0 })), + to: number() + .required(t("common.required")) + .min(ref("from"), t("ageTypes.shouldbegreaterthanfrom")), + }) + ), + }); + + const formik = useFormik({ + initialValues, + validationSchema, + enableReinitialize: true, + onSubmit: (values) => { + const formattedValues = rows.map((fields, index) => + formatAllFieldValues(fields, values.ageTypes[index]) + ); + + const errors = validateRange(formattedValues as AgeTypeDTO[], t); + setValidationErrors(errors); + if (errors.length === 0) { + onSubmit(formattedValues as any); + } + }, + }); + + const isValid = (fieldName: string, index: number): boolean => { + return ( + has(formik.touched.ageTypes?.[index], fieldName) && + has(formik.errors.ageTypes?.[index], fieldName) + ); + }; + + const getErrorText = (fieldName: string, index: number): string => { + return has(formik.touched.ageTypes?.[index], fieldName) + ? (get(formik.errors.ageTypes?.[index], fieldName) as string) + : ""; + }; + + const handleResetConfirmation = () => { + setOpenResetConfirmation(false); + navigate(-1); + }; + + useEffect(() => { + return () => { + dispatch(updateAgeTypeReset()); + }; + }, [dispatch]); + + return ( +
+
+
+ + + + + + + + + + + {rows.map((fields, index) => ( + + ))} + +
{t("ageTypes.code")}{t("ageTypes.from")}{t("ageTypes.to")}{t("ageTypes.description")}
+
+ +
+
+ +
+
+ +
+
+ setOpenResetConfirmation(false)} + /> + {updateAgeTypes.status === "FAIL" && ( +
+ +
+ )} + {validationErrors.length > 0 && ( +
+ +
+ )} + { + navigate(PATHS.admin_age_types); + }} + handleSecondaryButtonClick={() => ({})} + /> + +
+ ); +}; + +export default AgeTypesForm; diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesForm/consts.test.ts b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/consts.test.ts new file mode 100644 index 000000000..91bf325c2 --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/consts.test.ts @@ -0,0 +1,41 @@ +import { AgeTypeDTO } from "generated"; +import { validateRange } from "./consts"; + +const validRange: AgeTypeDTO[] = [ + { from: 0, to: 0, description: "Lorem ipsum" }, + { from: 1, to: 4, description: "Lorem ipsum" }, + { from: 5, to: 10, description: "Lorem ipsum" }, +]; + +const overlapRange: AgeTypeDTO[] = [ + { from: 0, to: 0, description: "Lorem ipsum" }, + { from: 1, to: 5, description: "Lorem ipsum" }, + { from: 5, to: 10, description: "Lorem ipsum" }, +]; + +const notProperlyDefinedRange: AgeTypeDTO[] = [ + { from: 0, to: 0, description: "Lorem ipsum" }, + { from: 1, to: 5, description: "Lorem ipsum" }, + { from: 6, to: 0, description: "Lorem ipsum" }, +]; + +describe("Age types range validation", () => { + it("should return empty errors list when empty range", () => { + expect(validateRange([], null).length).toEqual(0); + }); + it("should return empty errors list when valid range", () => { + expect(validateRange(validRange, null).length).toEqual(0); + }); + it("should have overlap error in list when overlapped range", () => { + expect(validateRange(overlapRange, null).length).toEqual(1); + expect(validateRange(overlapRange, null)[0]).toEqual( + "ageTypes.somerangesareoverlapped" + ); + }); + it("should have not defined error in list when not properly defined range", () => { + expect(validateRange(notProperlyDefinedRange, null).length).toEqual(1); + expect(validateRange(notProperlyDefinedRange, null)[0]).toEqual( + "ageTypes.somerangesarenotdefined" + ); + }); +}); diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesForm/consts.ts b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/consts.ts new file mode 100644 index 000000000..6ecd5074e --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/consts.ts @@ -0,0 +1,44 @@ +import { TFunction } from "i18next"; +import { AgeTypeFormFieldName } from "."; +import { AgeTypeDTO } from "../../../../../../../generated"; +import { TFields } from "../../../../../../../libraries/formDataHandling/types"; + +export const getInitialFields: ( + ageType: AgeTypeDTO[] +) => TFields[] = (ageTypes) => + ageTypes.map((ageType) => ({ + code: { type: "text", value: ageType?.code ?? "" }, + description: { type: "text", value: ageType?.description ?? "" }, + from: { type: "number", value: ageType?.from ? `${ageType?.from}` : "0" }, + to: { type: "number", value: ageType?.to ? `${ageType?.to}` : "0" }, + })); + +export const validateRange = ( + ranges: AgeTypeDTO[], + t: TFunction | null +): string[] => { + const validationErrors: string[] = []; + + ranges.forEach((ageType, index) => { + if (index > 0) { + const prev = ranges.at(index - 1); + if (ageType.from <= prev!.to) { + validationErrors.push( + t + ? t("ageTypes.somerangesareoverlapped") + : "ageTypes.somerangesareoverlapped" + ); + } + + if (ageType.to < ageType.from) { + validationErrors.push( + t + ? t("ageTypes.somerangesarenotdefined") + : "ageTypes.somerangesarenotdefined" + ); + } + } + }); + + return validationErrors; +}; diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesForm/index.ts b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/index.ts new file mode 100644 index 000000000..404687f8f --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/index.ts @@ -0,0 +1,2 @@ +export * from "./AgeTypesForm"; +export * from "./types"; diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesForm/styles.scss b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/styles.scss new file mode 100644 index 000000000..acd47257d --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/styles.scss @@ -0,0 +1,73 @@ +@import "../../../../../../../../node_modules/susy/sass/susy"; +@import "../../../../../../../styles/variables"; + +.ageTypesForm { + display: inline-block; + flex-direction: column; + align-items: center; + width: 100%; + margin-top: 20px; + + .row { + justify-content: space-between; + } + + table.ageTypesFormTable { + min-width: 100%; + overflow-x: auto; + border-collapse: collapse; + + th { + padding: 10px 0; + text-align: left; + border-bottom: solid 2px #999; + } + td { + border-bottom: solid 1px #999; + } + + .fromField, + .toField { + width: 175px; + padding: 1px 6px; + + .textField { + width: 175px; + } + } + } + + td.empty { + span { + padding: 5px 13px; + font-weight: bold; + font-size: 19px; + color: #555; + } + } + + .ageTypesForm__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/admin/types/components/agetypes/ageTypesForm/types.ts b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/types.ts new file mode 100644 index 000000000..84b357cce --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesForm/types.ts @@ -0,0 +1,20 @@ +import { FormikProps } from "formik"; +import { AgeTypeDTO } from "../../../../../../../generated"; +import { TFields } from "../../../../../../../libraries/formDataHandling/types"; + +export interface IAgeTypesFormProps { + rows: TFields[]; + onSubmit: (ageType: AgeTypeDTO[]) => void; + submitButtonLabel: string; + resetButtonLabel: string; + isLoading: boolean; +} + +export interface IAgeTypeFieldsProps { + isValid: (fieldName: string, index: number) => boolean; + getErrorText: (fieldName: string, index: number) => string; + formik: FormikProps<{ ageTypes: Record[] }>; + index: number; +} + +export type AgeTypeFormFieldName = "code" | "description" | "from" | "to"; diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesTable/AgeTypesTable.tsx b/src/components/accessories/admin/types/components/agetypes/ageTypesTable/AgeTypesTable.tsx new file mode 100644 index 000000000..62d7a8d23 --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesTable/AgeTypesTable.tsx @@ -0,0 +1,87 @@ +import { CircularProgress } from "@mui/material"; +import { useAppSelector } from "libraries/hooks/redux"; +import React, { ReactNode } from "react"; +import { useTranslation } from "react-i18next"; +import { AgeTypeDTO } from "../../../../../../../generated"; +import InfoBox from "../../../../../infoBox/InfoBox"; +import Table from "../../../../../table/Table"; +import "./styles.scss"; + +interface IOwnProps { + headerActions?: ReactNode; +} + +const AgeTypesTable = (props: IOwnProps) => { + const { headerActions } = props; + const { t } = useTranslation(); + + const header = ["code", "description", "from", "to"]; + + const label = { + code: t("ageTypes.code"), + description: t("ageTypes.description"), + from: t("ageTypes.from"), + to: t("ageTypes.to"), + }; + const order = ["code", "description", "from", "to"]; + + const { data, status, error } = useAppSelector( + (state) => state.types.ageTypes.getAll + ); + + const formatDataToDisplay = (data: AgeTypeDTO[]) => { + return data.map((item) => { + return { + code: item.code, + description: t(item.description), + from: item.from, + to: item.to, + }; + }); + }; + + return ( +
+ {(() => { + switch (status) { + case "FAIL": + return ( +
+ +
+ ); + case "LOADING": + return ; + + case "SUCCESS": + return ( + <> + + + ); + case "SUCCESS_EMPTY": + return ; + default: + return; + } + })()} + + ); +}; + +export default AgeTypesTable; diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesTable/index.ts b/src/components/accessories/admin/types/components/agetypes/ageTypesTable/index.ts new file mode 100644 index 000000000..6fe938765 --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesTable/index.ts @@ -0,0 +1,3 @@ +import AgeTypesTable from "./AgeTypesTable"; + +export default AgeTypesTable; diff --git a/src/components/accessories/admin/types/components/agetypes/ageTypesTable/styles.scss b/src/components/accessories/admin/types/components/agetypes/ageTypesTable/styles.scss new file mode 100644 index 000000000..ec40dd883 --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/ageTypesTable/styles.scss @@ -0,0 +1,13 @@ +.admissionTypesTable { + display: grid; + margin-top: 50px; + + .fullWidth { + width: 100%; + } + + .loader { + margin-left: 50%; + position: relative; + } +} diff --git a/src/components/accessories/admin/types/components/agetypes/editAgeTypes/EditAgeTypes.tsx b/src/components/accessories/admin/types/components/agetypes/editAgeTypes/EditAgeTypes.tsx new file mode 100644 index 000000000..d0ffa4953 --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/editAgeTypes/EditAgeTypes.tsx @@ -0,0 +1,67 @@ +import { CircularProgress } from "@mui/material"; +import InfoBox from "components/accessories/infoBox/InfoBox"; +import { useAppDispatch, useAppSelector } from "libraries/hooks/redux"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { getAgeTypes, updateAgeTypes } from "state/types/ageTypes"; +import { setTypeMode } from "state/types/config"; +import { AgeTypeDTO } from "../../../../../../../generated"; +import AgeTypesForm from "../ageTypesForm/AgeTypesForm"; +import { getInitialFields } from "../ageTypesForm/consts"; +import "./styles.scss"; + +export const EditAgeTypes = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const update = useAppSelector((state) => state.types.ageTypes.update); + const ageTypesState = useAppSelector((state) => state.types.ageTypes.getAll); + const mode = useAppSelector((state) => state.types.config.mode); + + const handleSubmit = (ageTypes: AgeTypeDTO[]) => { + const raws = ageTypesState.data; + + const payload = ageTypes.map((ageType) => { + const raw = raws?.find((item) => item.code === ageType.code); + ageType.lock = raw?.lock ?? 0; + return ageType; + }); + + dispatch(updateAgeTypes(payload)); + }; + + useEffect(() => { + dispatch(getAgeTypes()); + }, [dispatch]); + + useEffect(() => { + if (mode !== "edit") { + dispatch(setTypeMode("edit")); + } + }, [mode, dispatch]); + + return ( +
+

+ {t("ageTypes.editAgeTypes")} +

+ {ageTypesState.isLoading && } + {ageTypesState.hasFailed && ( +
+ +
+ )} + {ageTypesState.hasSucceeded && !!ageTypesState.data && ( + + )} +
+ ); +}; diff --git a/src/components/accessories/admin/types/components/agetypes/editAgeTypes/index.ts b/src/components/accessories/admin/types/components/agetypes/editAgeTypes/index.ts new file mode 100644 index 000000000..f16636245 --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/editAgeTypes/index.ts @@ -0,0 +1 @@ +export * from "./EditAgeTypes"; diff --git a/src/components/accessories/admin/types/components/agetypes/editAgeTypes/styles.scss b/src/components/accessories/admin/types/components/agetypes/editAgeTypes/styles.scss new file mode 100644 index 000000000..8027d702d --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/editAgeTypes/styles.scss @@ -0,0 +1,14 @@ +.editAgeTypes { + .title { + margin-bottom: 10px; + } + + .fullWidth { + width: 100%; + } + + .loader { + margin-left: 50%; + position: relative; + } +} diff --git a/src/components/accessories/admin/types/components/agetypes/index.ts b/src/components/accessories/admin/types/components/agetypes/index.ts new file mode 100644 index 000000000..496cebb0e --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/index.ts @@ -0,0 +1,6 @@ +import AgeTypes from "./AgeTypes"; + +export default AgeTypes; +export * from "./ageTypesForm"; +export * from "./ageTypesTable"; +export * from "./editAgeTypes"; diff --git a/src/components/accessories/admin/types/components/agetypes/styles.scss b/src/components/accessories/admin/types/components/agetypes/styles.scss new file mode 100644 index 000000000..88535560b --- /dev/null +++ b/src/components/accessories/admin/types/components/agetypes/styles.scss @@ -0,0 +1,3 @@ +.ageTypes { + margin-top: 50px; +} diff --git a/src/components/accessories/dashboard/admissions/Admissions.tsx b/src/components/accessories/dashboard/admissions/Admissions.tsx index d2e74c7f3..3abfe09b1 100644 --- a/src/components/accessories/dashboard/admissions/Admissions.tsx +++ b/src/components/accessories/dashboard/admissions/Admissions.tsx @@ -2,8 +2,8 @@ import { Skeleton } from "@mui/material"; import { useAppDispatch } from "libraries/hooks/redux"; import React, { FC, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import { getAgeTypes } from "state/types/ageTypes"; import { getAdmissions } from "../../../../state/admissions"; -import { getAgeTypes } from "../../../../state/ageTypes"; import { getAdmissionTypes } from "../../../../state/types/admissions"; import { getWards } from "../../../../state/ward"; import { Barchart } from "../../charts/bar/Barchart"; diff --git a/src/components/accessories/dashboard/admissions/admissionByAgeType/AdmissionByAgeType.tsx b/src/components/accessories/dashboard/admissions/admissionByAgeType/AdmissionByAgeType.tsx index eb043d99e..665bd756c 100644 --- a/src/components/accessories/dashboard/admissions/admissionByAgeType/AdmissionByAgeType.tsx +++ b/src/components/accessories/dashboard/admissions/admissionByAgeType/AdmissionByAgeType.tsx @@ -3,7 +3,6 @@ import React, { FC, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useAdmByAgeTypeData } from "../../../../../libraries/dashboardUtils/admissions/useAdmByAgeTypeData"; import { getAdmissions } from "../../../../../state/admissions"; -import { getAgeTypes } from "../../../../../state/ageTypes"; import { Barchart } from "../../../charts/bar/Barchart"; import DataDownloadButton from "../../../dataDownloadButton/DataDownloadButton"; import { DashboardCard } from "../../card/DashboardCard"; @@ -13,6 +12,7 @@ import { DataSummary } from "../../summary/DataSummary"; import { IOwnProps } from "../types"; import { Skeleton } from "@mui/material"; +import { getAgeTypes } from "state/types/ageTypes"; import "../../card/styles.scss"; export const AdmissionsByAgeType: FC = ({ diff --git a/src/components/accessories/dashboard/admissions/useData.ts b/src/components/accessories/dashboard/admissions/useData.ts index d51204d29..fcdbd90af 100644 --- a/src/components/accessories/dashboard/admissions/useData.ts +++ b/src/components/accessories/dashboard/admissions/useData.ts @@ -12,10 +12,10 @@ export const useData = () => { (state) => state.types.admissions.getAll.data ?? [] ); const ageTypes = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.data ?? [] + (state) => state.types.ageTypes.getAll.data ?? [] ); const ageTypeStatus = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.status ?? "IDLE" + (state) => state.types.ageTypes.getAll.status ?? "IDLE" ); const admissionTypeStatus = useAppSelector( (state) => state.types.admissions.getAll.status ?? "IDLE" diff --git a/src/components/accessories/dashboard/discharges/Discharges.tsx b/src/components/accessories/dashboard/discharges/Discharges.tsx index 4092bcd8d..19e0d7444 100644 --- a/src/components/accessories/dashboard/discharges/Discharges.tsx +++ b/src/components/accessories/dashboard/discharges/Discharges.tsx @@ -2,8 +2,8 @@ import { Skeleton } from "@mui/material"; import { useAppDispatch } from "libraries/hooks/redux"; import React, { FC, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import { getAgeTypes } from "state/types/ageTypes"; import { getDischarges } from "../../../../state/admissions"; -import { getAgeTypes } from "../../../../state/ageTypes"; import { getDischargeTypes } from "../../../../state/types/discharges"; import { getWards } from "../../../../state/ward"; import { Barchart } from "../../charts/bar/Barchart"; diff --git a/src/components/accessories/dashboard/discharges/dischargesByAgeTypes/DischargesByAgeTypes.tsx b/src/components/accessories/dashboard/discharges/dischargesByAgeTypes/DischargesByAgeTypes.tsx index ad2da2c2d..d47926f5a 100644 --- a/src/components/accessories/dashboard/discharges/dischargesByAgeTypes/DischargesByAgeTypes.tsx +++ b/src/components/accessories/dashboard/discharges/dischargesByAgeTypes/DischargesByAgeTypes.tsx @@ -2,9 +2,9 @@ import { Skeleton } from "@mui/material"; import { useAppDispatch } from "libraries/hooks/redux"; import React, { FC, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { getAgeTypes } from "state/types/ageTypes"; import { useDisByAgeTypeData } from "../../../../../libraries/dashboardUtils/discharges/useDisByAgeTypeData"; import { getDischarges } from "../../../../../state/admissions"; -import { getAgeTypes } from "../../../../../state/ageTypes"; import { Barchart } from "../../../charts/bar/Barchart"; import DataDownloadButton from "../../../dataDownloadButton/DataDownloadButton"; import { DashboardCard } from "../../card/DashboardCard"; diff --git a/src/components/accessories/dashboard/discharges/useData.ts b/src/components/accessories/dashboard/discharges/useData.ts index d00b32448..94695b023 100644 --- a/src/components/accessories/dashboard/discharges/useData.ts +++ b/src/components/accessories/dashboard/discharges/useData.ts @@ -15,10 +15,10 @@ export const useData = () => { (state) => state.types.discharges.getAll.data ?? [] ); const ageTypes = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.data ?? [] + (state) => state.types.ageTypes.getAll.data ?? [] ); const ageTypeStatus = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.status ?? "IDLE" + (state) => state.types.ageTypes.getAll.status ?? "IDLE" ); const dischargeTypeStatus = useAppSelector( (state) => state.types.discharges.getAll.status ?? "IDLE" diff --git a/src/components/accessories/dashboard/opds/Opds.tsx b/src/components/accessories/dashboard/opds/Opds.tsx index 1e47dd368..5c452e6c7 100644 --- a/src/components/accessories/dashboard/opds/Opds.tsx +++ b/src/components/accessories/dashboard/opds/Opds.tsx @@ -2,7 +2,7 @@ import { Skeleton } from "@mui/material"; import { useAppDispatch } from "libraries/hooks/redux"; import React, { FC, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { getAgeTypes } from "../../../../state/ageTypes"; +import { getAgeTypes } from "state/types/ageTypes"; import { searchOpds } from "../../../../state/opds"; import { Barchart } from "../../charts/bar/Barchart"; import { Piechart } from "../../charts/pie/Piechart"; diff --git a/src/components/accessories/dashboard/opds/opdByAgeTypes/OpdByAgeTypes.tsx b/src/components/accessories/dashboard/opds/opdByAgeTypes/OpdByAgeTypes.tsx index a29b40f22..11fd77e74 100644 --- a/src/components/accessories/dashboard/opds/opdByAgeTypes/OpdByAgeTypes.tsx +++ b/src/components/accessories/dashboard/opds/opdByAgeTypes/OpdByAgeTypes.tsx @@ -3,7 +3,6 @@ import { useAppDispatch } from "libraries/hooks/redux"; import React, { FC, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useOpdByAgeTypeData } from "../../../../../libraries/dashboardUtils/opds/useOpdByAgeTypeData"; -import { getAgeTypes } from "../../../../../state/ageTypes"; import { searchOpds } from "../../../../../state/opds"; import { Barchart } from "../../../charts/bar/Barchart"; import DataDownloadButton from "../../../dataDownloadButton/DataDownloadButton"; @@ -13,6 +12,7 @@ import { TDashboardComponentProps } from "../../layouts/types"; import { DataSummary } from "../../summary/DataSummary"; import { IOwnProps } from "../types"; +import { getAgeTypes } from "state/types/ageTypes"; import "../../card/styles.scss"; export const OpdByAgeTypes: FC = ({ diff --git a/src/components/accessories/dashboard/opds/useData.ts b/src/components/accessories/dashboard/opds/useData.ts index ca9997e1d..7c1670054 100644 --- a/src/components/accessories/dashboard/opds/useData.ts +++ b/src/components/accessories/dashboard/opds/useData.ts @@ -7,10 +7,10 @@ export const useData = () => { (state) => state.opds.searchOpds.data?.data ?? [] ); const ageTypes = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.data ?? [] + (state) => state.types.ageTypes.getAll.data ?? [] ); const ageTypeStatus = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.status ?? "IDLE" + (state) => state.types.ageTypes.getAll.status ?? "IDLE" ); const opdStatus = useAppSelector( (state) => state.opds.searchOpds.status ?? "IDLE" diff --git a/src/components/accessories/patientDataForm/PatientDataForm.tsx b/src/components/accessories/patientDataForm/PatientDataForm.tsx index 04a6935e3..8d1f02b2b 100644 --- a/src/components/accessories/patientDataForm/PatientDataForm.tsx +++ b/src/components/accessories/patientDataForm/PatientDataForm.tsx @@ -12,6 +12,7 @@ import React, { } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; +import { getAgeTypes } from "state/types/ageTypes"; import { number, object, string } from "yup"; import warningIcon from "../../../assets/warning-icon.png"; import { formCustomization } from "../../../customization/formCustomization"; @@ -21,7 +22,6 @@ import { getFromFields, isFieldSuggested, } from "../../../libraries/formDataHandling/functions"; -import { getAgeTypes } from "../../../state/ageTypes"; import { getCities } from "../../../state/patients"; import { FIELD_VALIDATION, IState } from "../../../types"; import AutocompleteField from "../autocompleteField/AutocompleteField"; @@ -94,14 +94,14 @@ const PatientDataForm: FunctionComponent = ({ const cityOptions = useCityOptions(cities); const ageRangeOptions = useAppSelector((state: IState) => - state.ageTypes.getAllAgeTypes.data?.map((e) => ({ + state.types.ageTypes.getAll.data?.map((e) => ({ value: e.code ?? "", label: e.code ? t("patient.agetypes." + e.code) : "", })) ); const allAgeTypes = useAppSelector( - (state: IState) => state.ageTypes.getAllAgeTypes.data + (state: IState) => state.types.ageTypes.getAll.data ); const ageTypeOptions = [ diff --git a/src/consts.ts b/src/consts.ts index 636a66ee8..f9a522ec1 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -76,4 +76,6 @@ export const PATHS = { admin_admissions_types_new: "/admin/types/admissions/new", admin_admissions_types_edit: "/admin/types/admissions/:code/edit", admin_hospital_edit: "/admin/hospital/edit", + admin_age_types: "/admin/types/ages", + admin_age_types_edit: "/admin/types/ages/edit", }; diff --git a/src/generated/apis/AgeTypesApi.ts b/src/generated/apis/AgeTypesApi.ts index 6daff1b11..478615ed7 100644 --- a/src/generated/apis/AgeTypesApi.ts +++ b/src/generated/apis/AgeTypesApi.ts @@ -26,7 +26,7 @@ export interface GetAgeTypeCodeByAgeRequest { } export interface UpdateAgeTypeRequest { - ageTypeDTO: AgeTypeDTO; + ageTypeDTO: Array; } /** @@ -93,9 +93,9 @@ export class AgeTypesApi extends BaseAPI { /** */ - updateAgeType({ ageTypeDTO }: UpdateAgeTypeRequest): Observable - updateAgeType({ ageTypeDTO }: UpdateAgeTypeRequest, opts?: OperationOpts): Observable> - updateAgeType({ ageTypeDTO }: UpdateAgeTypeRequest, opts?: OperationOpts): Observable> { + updateAgeType({ ageTypeDTO }: UpdateAgeTypeRequest): Observable> + updateAgeType({ ageTypeDTO }: UpdateAgeTypeRequest, opts?: OperationOpts): Observable>> + updateAgeType({ ageTypeDTO }: UpdateAgeTypeRequest, opts?: OperationOpts): Observable | RawAjaxResponse>> { throwIfNullOrUndefined(ageTypeDTO, 'ageTypeDTO', 'updateAgeType'); const headers: HttpHeaders = { @@ -103,7 +103,7 @@ export class AgeTypesApi extends BaseAPI { ...(this.configuration.username != null && this.configuration.password != null ? { Authorization: `Basic ${btoa(this.configuration.username + ':' + this.configuration.password)}` } : undefined), }; - return this.request({ + return this.request>({ url: '/agetypes', method: 'PUT', headers, diff --git a/src/libraries/dashboardUtils/admissions/useAdmByAgeTypeData.ts b/src/libraries/dashboardUtils/admissions/useAdmByAgeTypeData.ts index 6e94ac9b2..b290f2143 100644 --- a/src/libraries/dashboardUtils/admissions/useAdmByAgeTypeData.ts +++ b/src/libraries/dashboardUtils/admissions/useAdmByAgeTypeData.ts @@ -7,10 +7,10 @@ export const useAdmByAgeTypeData = () => { (state) => state.admissions.getAdmissions.data?.data ?? [] ); const ageTypes = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.data ?? [] + (state) => state.types.ageTypes.getAll.data ?? [] ); const ageTypeStatus = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.status ?? "IDLE" + (state) => state.types.ageTypes.getAll.status ?? "IDLE" ); const status = useAppSelector( (state) => state.admissions.getAdmissions.status ?? "IDLE" diff --git a/src/libraries/dashboardUtils/discharges/useDisByAgeTypeData.ts b/src/libraries/dashboardUtils/discharges/useDisByAgeTypeData.ts index aeeac06d5..72b7e387c 100644 --- a/src/libraries/dashboardUtils/discharges/useDisByAgeTypeData.ts +++ b/src/libraries/dashboardUtils/discharges/useDisByAgeTypeData.ts @@ -7,10 +7,10 @@ export const useDisByAgeTypeData = () => { (state) => state.admissions.getDischarges.data?.data ?? [] ); const ageTypes = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.data ?? [] + (state) => state.types.ageTypes.getAll.data ?? [] ); const ageTypeStatus = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.status ?? "IDLE" + (state) => state.types.ageTypes.getAll.status ?? "IDLE" ); const status = useAppSelector( (state) => state.admissions.getDischarges.status ?? "IDLE" diff --git a/src/libraries/dashboardUtils/opds/useOpdByAgeTypeData.ts b/src/libraries/dashboardUtils/opds/useOpdByAgeTypeData.ts index 4a16eabb1..ae821892b 100644 --- a/src/libraries/dashboardUtils/opds/useOpdByAgeTypeData.ts +++ b/src/libraries/dashboardUtils/opds/useOpdByAgeTypeData.ts @@ -7,10 +7,10 @@ export const useOpdByAgeTypeData = () => { (state) => state.opds.searchOpds.data?.data ?? [] ); const ageTypes = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.data ?? [] + (state) => state.types.ageTypes.getAll.data ?? [] ); const ageTypeStatus = useAppSelector( - (state) => state.ageTypes.getAllAgeTypes.status ?? "IDLE" + (state) => state.types.ageTypes.getAll.status ?? "IDLE" ); const status = useAppSelector( (state) => state.opds.searchOpds.status ?? "IDLE" diff --git a/src/mockServer/fixtures/ageTypeDTO.js b/src/mockServer/fixtures/ageTypeDTO.js index 3556d4f4e..2f48ec5b3 100644 --- a/src/mockServer/fixtures/ageTypeDTO.js +++ b/src/mockServer/fixtures/ageTypeDTO.js @@ -3,36 +3,36 @@ export const ageTypeDTO = [ code: "d0", description: "New Born", from: 0, - to: 1 + to: 0 }, { code: "d1", description: "Early Child Hood", - from: 6, - to: 17 + from: 1, + to: 5 }, { code: "d2", description: "Late Child Hood", - from: 18, - to: 200 + from: 6, + to: 15 }, { code: "d3", description: "Adolescent", - from: 18, - to: 200 + from: 16, + to: 50 }, { code: "d4", description: "Adult", - from: 18, - to: 200 + from: 51, + to: 80 }, { code: "d5", description: "Elderly", - from: 18, + from: 81, to: 200 }, ]; diff --git a/src/mockServer/routes/ageTypes.js b/src/mockServer/routes/ageTypes.js index 586108c72..30bcb9f63 100644 --- a/src/mockServer/routes/ageTypes.js +++ b/src/mockServer/routes/ageTypes.js @@ -5,5 +5,15 @@ export const ageTypeRoutes = (server) => { server.get("/").intercept((req, res) => { res.status(200).json(ageTypeDTO); }); + server.put("/").intercept((req, res) => { + const body = req.jsonBody(); + switch (body[0].to) { + case 1: + res.status(400).json({ message: "Fail to update age types" }); + break; + default: + res.status(200).json(body); + } + }); }); }; diff --git a/src/resources/i18n/en.json b/src/resources/i18n/en.json index 2afc1492e..72d478b5c 100644 --- a/src/resources/i18n/en.json +++ b/src/resources/i18n/en.json @@ -929,6 +929,7 @@ "vaccines": "Types of vaccine", "exams": "Types of exam", "admissions": "Types of admission", + "ages": "Types of age", "diseases": "Types of disease", "discharges": "Types of discharge", "deliveries": "Types of delivery", @@ -1057,6 +1058,22 @@ "cancelCreation": "Are you sure to cancel the operation type creation?", "cancelUpdate": "Are you sure to cancel the operation type update?" }, + "ageTypes": { + "code": "Code", + "description": "Description", + "from": "From", + "to": "To", + "title": "Manage age types", + "editAgeTypes": "Edit age types", + "updateAgeType": "Save changes", + "created": "Age type created", + "updated": "Age types updated", + "updateSuccess": "The age types have been updated successfully!", + "cancelUpdate": "Are you sure to cancel the age types update?", + "shouldbegreaterthanfrom": "Should be great than from", + "somerangesareoverlapped": "Some ranges overlap", + "somerangesarenotdefined": "Some ranges are not defined properly" + }, "supplier": { "id": "Supplier ID", "name": "Name", diff --git a/src/routes/Admin/TypesRoutes.tsx b/src/routes/Admin/TypesRoutes.tsx index 6a9bdd722..dd2238f95 100644 --- a/src/routes/Admin/TypesRoutes.tsx +++ b/src/routes/Admin/TypesRoutes.tsx @@ -1,3 +1,6 @@ +import AgeTypes, { + EditAgeTypes, +} from "components/accessories/admin/types/components/agetypes"; import React, { ReactNode } from "react"; import { Route, Routes } from "react-router"; import Empty from "../../components/accessories/admin/types/Empty"; @@ -166,6 +169,14 @@ const TypesRoutes = () => { path: "deliveryresulttypes/:code/edit", element: , }, + { + path: "ages", + element: , + }, + { + path: "ages/edit", + element: , + }, ]; return ( diff --git a/src/state/ageTypes/initial.ts b/src/state/ageTypes/initial.ts deleted file mode 100644 index 2eecc27b6..000000000 --- a/src/state/ageTypes/initial.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IAgeTypeState } from "./types"; -import { ApiResponse } from "../types"; - -export const initial: IAgeTypeState = { - getAllAgeTypes: new ApiResponse({ status: "IDLE", data: [] }), -}; diff --git a/src/state/ageTypes/slice.ts b/src/state/ageTypes/slice.ts deleted file mode 100644 index ec0470298..000000000 --- a/src/state/ageTypes/slice.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { initial } from "./initial"; -import * as thunks from "./thunk"; -import { ApiResponse } from "state/types"; -import { isEmpty } from "lodash"; - -export const ageTypeSlice = createSlice({ - name: "ageTypes", - initialState: initial, - reducers: {}, - extraReducers: (builder) => - builder - // Get All Age Types - .addCase(thunks.getAgeTypes.pending, (state) => { - state.getAllAgeTypes = ApiResponse.loading(); - }) - .addCase(thunks.getAgeTypes.fulfilled, (state, action) => { - state.getAllAgeTypes = isEmpty(action.payload) - ? ApiResponse.empty() : ApiResponse.value(action.payload); - }) - .addCase(thunks.getAgeTypes.rejected, (state, action) => { - state.getAllAgeTypes = ApiResponse.error(action.payload); - }), -}); diff --git a/src/state/ageTypes/thunk.ts b/src/state/ageTypes/thunk.ts deleted file mode 100644 index 1873f579c..000000000 --- a/src/state/ageTypes/thunk.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import { AgeTypesApi } from "../../generated"; -import { customConfiguration } from "../../libraries/apiUtils/configuration"; - -const api = new AgeTypesApi(customConfiguration()); - -export const getAgeTypes = createAsyncThunk( - "ageTypes/getAgeTypes", - async (_, thunkApi) => - api - .getAllAgeTypes() - .toPromise() - .catch((error) => thunkApi.rejectWithValue(error.response)) -); diff --git a/src/state/ageTypes/types.ts b/src/state/ageTypes/types.ts deleted file mode 100644 index 8620652e6..000000000 --- a/src/state/ageTypes/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { AgeTypeDTO } from "../../generated"; -import { ApiResponse } from "../types"; - -export type IAgeTypeState = { - getAllAgeTypes: ApiResponse>; -}; diff --git a/src/state/store.ts b/src/state/store.ts index 9e1606814..531e25fe2 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -1,6 +1,5 @@ import { combineReducers, configureStore } from "@reduxjs/toolkit"; import { admissionSlice } from "./admissions"; -import { ageTypeSlice } from "./ageTypes"; import { billSlice } from "./bills"; import { dashboardSlice } from "./dashboard"; import { diseaseSlice } from "./diseases"; @@ -48,7 +47,6 @@ const reducer = combineReducers({ operations: operationSlice.reducer, diseaseTypes: diseaseTypeSlice.reducer, examTypes: examTypeSlice.reducer, - ageTypes: ageTypeSlice.reducer, hospital: hospitalSlice.reducer, layouts: layoutSlice.reducer, dashboard: dashboardSlice.reducer, diff --git a/src/state/ageTypes/index.ts b/src/state/types/ageTypes/index.ts similarity index 96% rename from src/state/ageTypes/index.ts rename to src/state/types/ageTypes/index.ts index 4403a13f3..66ec8de7c 100644 --- a/src/state/ageTypes/index.ts +++ b/src/state/types/ageTypes/index.ts @@ -1,3 +1,3 @@ -export * from "./thunk"; -export * from "./slice"; -export * from "./types"; +export * from "./thunk"; +export * from "./slice"; +export * from "./types"; diff --git a/src/state/types/ageTypes/initial.ts b/src/state/types/ageTypes/initial.ts new file mode 100644 index 000000000..3e08805b4 --- /dev/null +++ b/src/state/types/ageTypes/initial.ts @@ -0,0 +1,7 @@ +import { ApiResponse } from "../../types"; +import { IAgeTypesState } from "./types"; + +export const initial: IAgeTypesState = { + getAll: new ApiResponse({ status: "IDLE", data: [] }), + update: new ApiResponse({ status: "IDLE", data: [] }), +}; diff --git a/src/state/types/ageTypes/slice.ts b/src/state/types/ageTypes/slice.ts new file mode 100644 index 000000000..ff0b0efb7 --- /dev/null +++ b/src/state/types/ageTypes/slice.ts @@ -0,0 +1,41 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { isEmpty } from "lodash"; +import { ApiResponse } from "state/types"; +import { initial } from "./initial"; +import * as thunks from "./thunk"; + +export const ageTypeSlice = createSlice({ + name: "aageTypes", + initialState: initial, + reducers: { + updateAgeTypeReset: (state) => { + state.update = initial.update; + }, + }, + extraReducers: (builder) => + builder + // Get Age Types + .addCase(thunks.getAgeTypes.pending, (state) => { + state.getAll = ApiResponse.loading(); + }) + .addCase(thunks.getAgeTypes.fulfilled, (state, action) => { + state.getAll = isEmpty(action.payload) + ? ApiResponse.empty() + : ApiResponse.value(action.payload); + }) + .addCase(thunks.getAgeTypes.rejected, (state, action) => { + state.getAll = ApiResponse.error(action.payload); + }) + // Update Age Type + .addCase(thunks.updateAgeTypes.pending, (state) => { + state.update = ApiResponse.loading(); + }) + .addCase(thunks.updateAgeTypes.fulfilled, (state, action) => { + state.update = ApiResponse.value(action.payload); + }) + .addCase(thunks.updateAgeTypes.rejected, (state, action) => { + state.update = ApiResponse.error(action.payload); + }), +}); + +export const { updateAgeTypeReset } = ageTypeSlice.actions; diff --git a/src/state/types/ageTypes/thunk.ts b/src/state/types/ageTypes/thunk.ts new file mode 100644 index 000000000..d220efa07 --- /dev/null +++ b/src/state/types/ageTypes/thunk.ts @@ -0,0 +1,23 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { AgeTypeDTO, AgeTypesApi } from "../../../generated"; +import { customConfiguration } from "../../../libraries/apiUtils/configuration"; + +const api = new AgeTypesApi(customConfiguration()); + +export const getAgeTypes = createAsyncThunk( + "ageTypes/getAll", + async (_, thunkApi) => + api + .getAllAgeTypes() + .toPromise() + .catch((error) => thunkApi.rejectWithValue(error.response)) +); + +export const updateAgeTypes = createAsyncThunk( + "ageTypes/update", + async (ageTypeDTO: AgeTypeDTO[], thunkApi) => + api + .updateAgeType({ ageTypeDTO }) + .toPromise() + .catch((error) => thunkApi.rejectWithValue(error.response)) +); diff --git a/src/state/types/ageTypes/types.ts b/src/state/types/ageTypes/types.ts new file mode 100644 index 000000000..b5c4d0467 --- /dev/null +++ b/src/state/types/ageTypes/types.ts @@ -0,0 +1,7 @@ +import { AgeTypeDTO } from "../../../generated"; +import { ApiResponse } from "../../types"; + +export type IAgeTypesState = { + getAll: ApiResponse>; + update: ApiResponse>; +}; diff --git a/src/state/types/slice.ts b/src/state/types/slice.ts index 63d140240..5d9d249ea 100644 --- a/src/state/types/slice.ts +++ b/src/state/types/slice.ts @@ -1,19 +1,21 @@ -import { vaccineTypeSlice } from "./vaccines"; +import { combineReducers } from "@reduxjs/toolkit"; import { admissionTypeSlice } from "./admissions"; -import { diseaseTypeSlice } from "./diseases"; -import { operationTypeSlice } from "./operations"; +import { ageTypeSlice } from "./ageTypes"; import { configSlice } from "./config"; -import { examTypeSlice } from "./exams"; -import { dischargeTypeSlice } from "./discharges"; import { deliveryTypeSlice } from "./deliveries"; +import { deliveryResultTypeSlice } from "./deliveryResults"; +import { dischargeTypeSlice } from "./discharges"; +import { diseaseTypeSlice } from "./diseases"; +import { examTypeSlice } from "./exams"; import { medicalTypeSlice } from "./medicals"; +import { operationTypeSlice } from "./operations"; import { pregnantTreatmentTypeSlice } from "./pregnantTreatment"; -import { deliveryResultTypeSlice } from "./deliveryResults"; -import { combineReducers } from "@reduxjs/toolkit"; +import { vaccineTypeSlice } from "./vaccines"; const typesReducer = combineReducers({ vaccines: vaccineTypeSlice.reducer, admissions: admissionTypeSlice.reducer, + ageTypes: ageTypeSlice.reducer, diseases: diseaseTypeSlice.reducer, operations: operationTypeSlice.reducer, config: configSlice.reducer, diff --git a/src/state/types/types.ts b/src/state/types/types.ts index b84d323a3..f3af34819 100644 --- a/src/state/types/types.ts +++ b/src/state/types/types.ts @@ -1,18 +1,20 @@ import { IAdmissionTypesState } from "./admissions"; +import { IAgeTypesState } from "./ageTypes"; import { ITypeConfigsState } from "./config"; -import { IDischargeTypesState } from "./discharges"; import { IDeliveryTypesState } from "./deliveries"; import { IDeliveryResultTypeState } from "./deliveryResults"; +import { IDischargeTypesState } from "./discharges"; import { IDiseaseTypesState } from "./diseases"; -import { IOperationTypesState } from "./operations"; -import { IVaccineTypesState } from "./vaccines"; -import { IMedicalTypesState } from "./medicals"; import { IExamTypesState } from "./exams"; +import { IMedicalTypesState } from "./medicals"; +import { IOperationTypesState } from "./operations"; import { IPregnantTreatmentTypesState } from "./pregnantTreatment"; +import { IVaccineTypesState } from "./vaccines"; export type ITypesState = { vaccines: IVaccineTypesState; admissions: IAdmissionTypesState; + ageTypes: IAgeTypesState; diseases: IDiseaseTypesState; operations: IOperationTypesState; config: ITypeConfigsState; diff --git a/src/types.ts b/src/types.ts index 37bd233f8..07dde2f7e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,31 +1,30 @@ +import { IAdmissionsState } from "./state/admissions/types"; +import { IBillsState } from "./state/bills/types"; +import { IDashboardState } from "./state/dashboard/types"; +import { IDiseaseState } from "./state/diseases/types"; import { IExaminationsState } from "./state/examinations/types"; +import { IExamState } from "./state/exams/types"; +import { IHospitalState } from "./state/hospital/types"; +import { ILaboratoriesState } from "./state/laboratories/types"; +import { ILayoutsState } from "./state/layouts/types"; import { IMainState } from "./state/main/types"; -import { IOpdState } from "./state/opds/types"; import { IMedicalState } from "./state/medicals/types"; +import { IOpdState } from "./state/opds/types"; +import { IOperationState } from "./state/operations/types"; import { IPatientsState } from "./state/patients/types"; +import { IPermissionsState } from "./state/permissions/types"; +import { IPricesState } from "./state/prices/types"; import { ISummaryState } from "./state/summary/types"; +import { ISupplierState } from "./state/suppliers/types"; import { ITherapiesState } from "./state/therapies/types"; -import { IDiseaseState } from "./state/diseases/types"; -import { IAdmissionsState } from "./state/admissions/types"; -import { IWardState } from "./state/ward/types"; -import { ILaboratoriesState } from "./state/laboratories/types"; -import { IExamState } from "./state/exams/types"; -import { IBillsState } from "./state/bills/types"; -import { IPricesState } from "./state/prices/types"; -import { IVisitState } from "./state/visits/types"; -import { IOperationState } from "./state/operations/types"; import { IDiseaseTypesState } from "./state/types/diseases/types"; import { IExamTypesState } from "./state/types/exams/types"; -import { IAgeTypeState } from "./state/ageTypes/types"; -import { IHospitalState } from "./state/hospital/types"; -import { ILayoutsState } from "./state/layouts/types"; -import { IDashboardState } from "./state/dashboard/types"; -import { IUserState } from "./state/users/types"; +import { ITypesState } from "./state/types/types"; import { IUserGroupState } from "./state/usergroups/types"; +import { IUserState } from "./state/users/types"; import { IVaccineState } from "./state/vaccines/types"; -import { ISupplierState } from "./state/suppliers/types"; -import { ITypesState } from "./state/types/types"; -import { IPermissionsState } from "./state/permissions/types"; +import { IVisitState } from "./state/visits/types"; +import { IWardState } from "./state/ward/types"; export interface IState { main: IMainState; @@ -46,7 +45,6 @@ export interface IState { operations: IOperationState; diseaseTypes: IDiseaseTypesState; examTypes: IExamTypesState; - ageTypes: IAgeTypeState; hospital: IHospitalState; layouts: ILayoutsState; dashboard: IDashboardState;