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 (
+
+ );
+};
+
+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;