From 5cbe5620696ef901164c8bc41da45486beee5c66 Mon Sep 17 00:00:00 2001 From: fogouang <74138682+loique70@users.noreply.github.com> Date: Tue, 18 Jun 2024 07:04:37 -0700 Subject: [PATCH] OH2-294 | Types / Exams CRUD (#608) * Add exams types * Deleted examTypes state * Add admission path variable --------- Co-authored-by: Alessandro Domanico --- .../admin/exams/examsTable/ExamsTable.tsx | 4 +- .../accessories/admin/types/TypesAdmin.tsx | 1 + .../types/components/exams/ExamsTypes.tsx | 67 ++++++ .../exams/editExamType/EditExamType.tsx | 51 +++++ .../components/exams/editExamType/index.ts | 1 + .../components/exams/editExamType/styles.scss | 5 + .../exams/examTypesForm/ExamTypeForm.tsx | 202 ++++++++++++++++++ .../components/exams/examTypesForm/consts.ts | 10 + .../components/exams/examTypesForm/index.ts | 2 + .../exams/examTypesForm/styles.scss | 77 +++++++ .../components/exams/examTypesForm/types.ts | 13 ++ .../exams/examTypesTable/ExamTypesTable.tsx | 126 +++++++++++ .../components/exams/examTypesTable/index.ts | 3 + .../exams/examTypesTable/styles.scss | 13 ++ .../admin/types/components/exams/index.ts | 7 + .../exams/newExamType/NewExamType.tsx | 41 ++++ .../components/exams/newExamType/index.ts | 1 + .../components/exams/newExamType/styles.scss | 5 + .../admin/types/components/exams/styles.scss | 3 + .../admin/types/components/index.ts | 1 + src/consts.ts | 9 +- src/index.tsx | 4 +- src/mockServer/routes/examTypes.js | 30 +++ src/resources/i18n/en.json | 19 ++ src/routes/Admin/TypesRoutes.tsx | 16 ++ src/state/examTypes/actions.ts | 42 ---- src/state/examTypes/consts.ts | 3 - src/state/examTypes/initial.ts | 6 - src/state/examTypes/reducer.ts | 31 --- src/state/examTypes/types.ts | 6 - src/state/types/exams/actions.ts | 146 +++++++++++++ src/state/types/exams/consts.ts | 20 ++ src/state/types/exams/index.ts | 5 + src/state/types/exams/initial.ts | 9 + src/state/types/exams/reducer.ts | 147 +++++++++++++ src/state/types/exams/types.ts | 9 + src/state/types/reducer.ts | 2 + src/state/types/types.ts | 2 + src/types.ts | 6 +- 39 files changed, 1049 insertions(+), 96 deletions(-) create mode 100644 src/components/accessories/admin/types/components/exams/ExamsTypes.tsx create mode 100644 src/components/accessories/admin/types/components/exams/editExamType/EditExamType.tsx create mode 100644 src/components/accessories/admin/types/components/exams/editExamType/index.ts create mode 100644 src/components/accessories/admin/types/components/exams/editExamType/styles.scss create mode 100644 src/components/accessories/admin/types/components/exams/examTypesForm/ExamTypeForm.tsx create mode 100644 src/components/accessories/admin/types/components/exams/examTypesForm/consts.ts create mode 100644 src/components/accessories/admin/types/components/exams/examTypesForm/index.ts create mode 100644 src/components/accessories/admin/types/components/exams/examTypesForm/styles.scss create mode 100644 src/components/accessories/admin/types/components/exams/examTypesForm/types.ts create mode 100644 src/components/accessories/admin/types/components/exams/examTypesTable/ExamTypesTable.tsx create mode 100644 src/components/accessories/admin/types/components/exams/examTypesTable/index.ts create mode 100644 src/components/accessories/admin/types/components/exams/examTypesTable/styles.scss create mode 100644 src/components/accessories/admin/types/components/exams/index.ts create mode 100644 src/components/accessories/admin/types/components/exams/newExamType/NewExamType.tsx create mode 100644 src/components/accessories/admin/types/components/exams/newExamType/index.ts create mode 100644 src/components/accessories/admin/types/components/exams/newExamType/styles.scss create mode 100644 src/components/accessories/admin/types/components/exams/styles.scss delete mode 100644 src/state/examTypes/actions.ts delete mode 100644 src/state/examTypes/consts.ts delete mode 100644 src/state/examTypes/initial.ts delete mode 100644 src/state/examTypes/reducer.ts delete mode 100644 src/state/examTypes/types.ts create mode 100644 src/state/types/exams/actions.ts create mode 100644 src/state/types/exams/consts.ts create mode 100644 src/state/types/exams/index.ts create mode 100644 src/state/types/exams/initial.ts create mode 100644 src/state/types/exams/reducer.ts create mode 100644 src/state/types/exams/types.ts diff --git a/src/components/accessories/admin/exams/examsTable/ExamsTable.tsx b/src/components/accessories/admin/exams/examsTable/ExamsTable.tsx index 2afa044b0..f61f7e6ce 100644 --- a/src/components/accessories/admin/exams/examsTable/ExamsTable.tsx +++ b/src/components/accessories/admin/exams/examsTable/ExamsTable.tsx @@ -7,7 +7,7 @@ import Table from "../../../table/Table"; import { TFilterField } from "../../../table/filter/types"; import InfoBox from "../../../infoBox/InfoBox"; import { getExams } from "../../../../../state/exams/actions"; -import { getExamTypes } from "../../../../../state/examTypes/actions"; +import { getExamTypes } from "../../../../../state/types/exams"; import { IState } from "../../../../../types"; import { ExamDTO, ExamTypeDTO } from "../../../../../generated"; import { ApiResponse } from "../../../../../state/types"; @@ -27,7 +27,7 @@ export const ExamsTable = () => { { label: string; value: string }[] >( (state) => - state.examTypes.getExamTypes.data?.map((item: ExamTypeDTO) => ({ + state.types.exams.getAll.data?.map((item: ExamTypeDTO) => ({ value: item.code ?? "", label: item.description ?? item.code ?? "", })) ?? [] diff --git a/src/components/accessories/admin/types/TypesAdmin.tsx b/src/components/accessories/admin/types/TypesAdmin.tsx index 9efdb476a..0746c7fd6 100644 --- a/src/components/accessories/admin/types/TypesAdmin.tsx +++ b/src/components/accessories/admin/types/TypesAdmin.tsx @@ -26,6 +26,7 @@ const TypesAdmin = () => { const typeOptions: TypeOption[] = [ defaultTypeOption, + { label: t("types.exams"), value: "exams" }, { label: t("types.vaccines"), value: "vaccines" }, { label: t("types.operations"), value: "operations" }, { label: t("types.diseases"), value: "diseases" }, diff --git a/src/components/accessories/admin/types/components/exams/ExamsTypes.tsx b/src/components/accessories/admin/types/components/exams/ExamsTypes.tsx new file mode 100644 index 000000000..ed906e97a --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/ExamsTypes.tsx @@ -0,0 +1,67 @@ +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { useNavigate } from "react-router"; +import { + deleteExamType, + deleteExamTypeReset, + getExamTypes, +} from "../../../../../../state/types/exams/actions"; +import { ExamTypeDTO } from "../../../../../../generated"; +import { PATHS } from "../../../../../../consts"; +import ExamTypesTable from "./examTypesTable"; +import Button from "../../../../button/Button"; +import "./styles.scss"; +import { setTypeMode } from "../../../../../../state/types/config"; + +const ExamTypes = () => { + const navigate = useNavigate(); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(getExamTypes()); + dispatch(setTypeMode("manage")); + + return () => { + dispatch(deleteExamTypeReset()); + }; + }, [dispatch]); + + const handleEdit = (row: ExamTypeDTO) => { + navigate(PATHS.admin_exams_types_edit.replace(":code", row.code!), { + state: row, + }); + }; + + const handleDelete = (row: ExamTypeDTO) => { + dispatch(deleteExamType(row.code ?? "")); + }; + + const { t } = useTranslation(); + return ( + <> +

{t("examTypes.title")}

+ +
+ { + navigate(PATHS.admin_exams_types_new); + }} + type="button" + variant="contained" + color="primary" + > + {t("examTypes.addExamType")} + + } + /> +
+ + ); +}; + +export default ExamTypes; diff --git a/src/components/accessories/admin/types/components/exams/editExamType/EditExamType.tsx b/src/components/accessories/admin/types/components/exams/editExamType/EditExamType.tsx new file mode 100644 index 000000000..6ebfe71bc --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/editExamType/EditExamType.tsx @@ -0,0 +1,51 @@ +import { useTranslation } from "react-i18next"; +import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { Navigate, useLocation, useParams } from "react-router"; +import { ExamTypeDTO } from "../../../../../../../generated"; +import { IState } from "../../../../../../../types"; +import { ApiResponse } from "../../../../../../../state/types"; +import { PATHS } from "../../../../../../../consts"; +import { getInitialFields } from "../examTypesForm/consts"; +import ExamTypeForm from "../examTypesForm/ExamTypeForm"; +import { setTypeMode } from "../../../../../../../state/types/config"; +import "./styles.scss"; +import { updateExamType } from "../../../../../../../state/types/exams/actions"; + +export const EditExamType = () => { + const dispatch = useDispatch(); + const { t } = useTranslation(); + const { state }: { state: ExamTypeDTO | undefined } = useLocation(); + const { code } = useParams<{ code: string }>(); + const update = useSelector>( + (state) => state.types.exams.update + ); + + const handleSubmit = (value: ExamTypeDTO) => { + if (code) { + dispatch(updateExamType(value, code)); + } + }; + + useEffect(() => { + dispatch(setTypeMode("edit")); + }, [dispatch]); + + if (state?.code !== code) { + return ; + } + + return ( +
+

{t("examTypes.editExamType")}

+ +
+ ); +}; diff --git a/src/components/accessories/admin/types/components/exams/editExamType/index.ts b/src/components/accessories/admin/types/components/exams/editExamType/index.ts new file mode 100644 index 000000000..4c80ff0b6 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/editExamType/index.ts @@ -0,0 +1 @@ +export * from "./EditExamType"; diff --git a/src/components/accessories/admin/types/components/exams/editExamType/styles.scss b/src/components/accessories/admin/types/components/exams/editExamType/styles.scss new file mode 100644 index 000000000..7b4382213 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/editExamType/styles.scss @@ -0,0 +1,5 @@ +.editExamType { + .title { + margin-bottom: 10px; + } +} diff --git a/src/components/accessories/admin/types/components/exams/examTypesForm/ExamTypeForm.tsx b/src/components/accessories/admin/types/components/exams/examTypesForm/ExamTypeForm.tsx new file mode 100644 index 000000000..4ebe6d188 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/examTypesForm/ExamTypeForm.tsx @@ -0,0 +1,202 @@ +import { useFormik } from "formik"; +import { get, has } from "lodash"; +import React, { + FC, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useTranslation } from "react-i18next"; +import { object, string } from "yup"; +import warningIcon from "../../../../../../../assets/warning-icon.png"; +import checkIcon from "../../../../../../../assets/check-icon.png"; +import "./styles.scss"; +import { IExamTypeFormProps } from "./types"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router"; +import { IState } from "../../../../../../../types"; +import { IExamTypesState } from "../../../../../../../state/types/exams/types"; +import { + formatAllFieldValues, + getFromFields, +} from "../../../../../../../libraries/formDataHandling/functions"; +import { + createExamTypeReset, + updateExamTypeReset, +} from "../../../../../../../state/types/exams/actions"; +import TextField from "../../../../../textField/TextField"; +import Button from "../../../../../button/Button"; +import ConfirmationDialog from "../../../../../confirmationDialog/ConfirmationDialog"; +import InfoBox from "../../../../../infoBox/InfoBox"; +import { PATHS } from "../../../../../../../consts"; + +const ExamTypeForm: FC = ({ + fields, + onSubmit, + creationMode, + submitButtonLabel, + resetButtonLabel, + isLoading, +}) => { + const dispatch = useDispatch(); + const { t } = useTranslation(); + const navigate = useNavigate(); + const infoBoxRef = useRef(null); + const [openResetConfirmation, setOpenResetConfirmation] = useState(false); + + const examTypesStore = useSelector( + (state) => state.types.exams + ); + + const errorMessage = useMemo( + () => + (creationMode + ? examTypesStore.create.error?.message + : examTypesStore.update.error?.message) ?? t("common.somethingwrong"), + [ + creationMode, + t, + examTypesStore.create.error?.message, + examTypesStore.update.error?.message, + ] + ); + + const initialValues = getFromFields(fields, "value"); + + const validationSchema = object({ + code: string().required(t("common.required")), + description: string().required(t("common.required")), + }); + + const formik = useFormik({ + initialValues, + validationSchema, + enableReinitialize: true, + onSubmit: (values) => { + const formattedValues = formatAllFieldValues(fields, values); + onSubmit(formattedValues as any); + }, + }); + + const isValid = (fieldName: string): boolean => { + return has(formik.touched, fieldName) && has(formik.errors, fieldName); + }; + + const getErrorText = (fieldName: string): string => { + return has(formik.touched, fieldName) + ? (get(formik.errors, fieldName) as string) + : ""; + }; + + const handleResetConfirmation = () => { + setOpenResetConfirmation(false); + navigate(-1); + }; + + const cleanUp = useCallback(() => { + if (creationMode) { + dispatch(createExamTypeReset()); + } else { + dispatch(updateExamTypeReset()); + } + }, [creationMode, dispatch]); + + useEffect(() => { + return cleanUp; + }, [cleanUp]); + + return ( +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ setOpenResetConfirmation(false)} + /> + {(creationMode + ? examTypesStore.create.status === "FAIL" + : examTypesStore.update.status === "FAIL") && ( +
+ +
+ )} + { + navigate(PATHS.admin_exams_types); + }} + handleSecondaryButtonClick={() => ({})} + /> + +
+ ); +}; + +export default ExamTypeForm; diff --git a/src/components/accessories/admin/types/components/exams/examTypesForm/consts.ts b/src/components/accessories/admin/types/components/exams/examTypesForm/consts.ts new file mode 100644 index 000000000..fc3507423 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/examTypesForm/consts.ts @@ -0,0 +1,10 @@ +import { ExamTypeFormFieldName } from "."; +import { ExamTypeDTO } from "../../../../../../../generated"; +import { TFields } from "../../../../../../../libraries/formDataHandling/types"; + +export const getInitialFields: ( + examType: ExamTypeDTO | undefined +) => TFields = (examType) => ({ + code: { type: "text", value: examType?.code ?? "" }, + description: { type: "text", value: examType?.description ?? "" }, +}); diff --git a/src/components/accessories/admin/types/components/exams/examTypesForm/index.ts b/src/components/accessories/admin/types/components/exams/examTypesForm/index.ts new file mode 100644 index 000000000..957de1d9a --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/examTypesForm/index.ts @@ -0,0 +1,2 @@ +export * from "./ExamTypeForm"; +export * from "./types"; diff --git a/src/components/accessories/admin/types/components/exams/examTypesForm/styles.scss b/src/components/accessories/admin/types/components/exams/examTypesForm/styles.scss new file mode 100644 index 000000000..d73c53a20 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/examTypesForm/styles.scss @@ -0,0 +1,77 @@ +@import "../../../../../../../../node_modules/susy/sass/susy"; +@import "../../../../../../../styles/variables"; + +.examTypesForm { + display: inline-block; + flex-direction: column; + align-items: center; + width: 100%; + + .formInsertMode { + margin: 0px 0px 20px; + } + + .row { + justify-content: space-between; + } + + .examTypesForm__item { + margin: 7px 0px; + padding: 0px 15px; + width: 50%; + @include susy-media($narrow) { + padding: 0px 10px; + } + @include susy-media($tablet_land) { + padding: 0px 10px; + } + @include susy-media($medium-up) { + width: 25%; + } + @include susy-media($tablet_port) { + width: 50%; + } + @include susy-media($smartphone) { + width: 100%; + } + .textField, + .selectField { + width: 100%; + } + + &.halfWidth { + width: 50%; + @include susy-media($smartphone) { + width: 100%; + } + } + &.fullWidth { + width: 100%; + } + } + + .examTypesForm__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/exams/examTypesForm/types.ts b/src/components/accessories/admin/types/components/exams/examTypesForm/types.ts new file mode 100644 index 000000000..3dac6abb3 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/examTypesForm/types.ts @@ -0,0 +1,13 @@ +import { ExamTypeDTO } from "../../../../../../../generated"; +import { TFields } from "../../../../../../../libraries/formDataHandling/types"; + +export interface IExamTypeFormProps { + fields: TFields; + onSubmit: (adm: ExamTypeDTO) => void; + creationMode: boolean; + submitButtonLabel: string; + resetButtonLabel: string; + isLoading: boolean; +} + +export type ExamTypeFormFieldName = "code" | "description"; diff --git a/src/components/accessories/admin/types/components/exams/examTypesTable/ExamTypesTable.tsx b/src/components/accessories/admin/types/components/exams/examTypesTable/ExamTypesTable.tsx new file mode 100644 index 000000000..dfb35ef47 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/examTypesTable/ExamTypesTable.tsx @@ -0,0 +1,126 @@ +import React, { ReactNode, useRef } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { ApiResponse } from "../../../../../../../state/types"; +import { IState } from "../../../../../../../types"; +import { ExamTypeDTO } from "../../../../../../../generated"; +import InfoBox from "../../../../../infoBox/InfoBox"; +import { CircularProgress } from "@material-ui/core"; +import Table from "../../../../../table/Table"; +import ConfirmationDialog from "../../../../../confirmationDialog/ConfirmationDialog"; +import { deleteExamTypeReset } from "../../../../../../../state/types/exams/actions"; +import checkIcon from "../../../../../../../assets/check-icon.png"; +import "./styles.scss"; + +interface IOwnProps { + onEdit: (row: any) => void; + onDelete: (row: any) => void; + headerActions?: ReactNode; +} + +const ExamTypesTable = (props: IOwnProps) => { + const { onDelete, onEdit, headerActions } = props; + const dispatch = useDispatch(); + const { t } = useTranslation(); + const infoBoxRef = useRef(null); + + const header = ["code", "description"]; + + const label = { + code: t("examTypes.code"), + description: t("examTypes.description"), + }; + const order = ["code", "description"]; + + const { data, status, error } = useSelector< + IState, + ApiResponse + >((state) => state.types.exams.getAll); + + const deleteExamType = useSelector>( + (state) => state.types.exams.delete + ); + + const handleEdit = (row: ExamTypeDTO) => { + onEdit((data ?? []).find((item) => item.code === row?.code)); + }; + + const handleDelete = (row: ExamTypeDTO) => { + onDelete(row); + }; + + const formatDataToDisplay = (data: ExamTypeDTO[]) => { + return data.map((item) => { + return { + code: item.code, + description: item.description, + }; + }); + }; + + return ( +
+ {(() => { + switch (status) { + case "FAIL": + return ( +
+ +
+ ); + case "LOADING": + return ; + + case "SUCCESS": + return ( + <> + + {deleteExamType.status === "FAIL" && ( +
+ +
+ )} + { + dispatch(deleteExamTypeReset()); + }} + handleSecondaryButtonClick={() => ({})} + /> + + ); + case "SUCCESS_EMPTY": + return ; + default: + return; + } + })()} + + ); +}; + +export default ExamTypesTable; diff --git a/src/components/accessories/admin/types/components/exams/examTypesTable/index.ts b/src/components/accessories/admin/types/components/exams/examTypesTable/index.ts new file mode 100644 index 000000000..760dfa463 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/examTypesTable/index.ts @@ -0,0 +1,3 @@ +import ExamTypesTable from "./ExamTypesTable"; + +export default ExamTypesTable; diff --git a/src/components/accessories/admin/types/components/exams/examTypesTable/styles.scss b/src/components/accessories/admin/types/components/exams/examTypesTable/styles.scss new file mode 100644 index 000000000..8c00bd686 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/examTypesTable/styles.scss @@ -0,0 +1,13 @@ +.examTypesTable { + display: grid; + margin-top: 50px; + + .fullWidth { + width: 100%; + } + + .loader { + margin-left: 50%; + position: relative; + } +} diff --git a/src/components/accessories/admin/types/components/exams/index.ts b/src/components/accessories/admin/types/components/exams/index.ts new file mode 100644 index 000000000..60176c711 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/index.ts @@ -0,0 +1,7 @@ +import ExamsTypes from "./ExamsTypes"; + +export default ExamsTypes; +export * from "./editExamType"; +export * from "./newExamType"; +export * from "./examTypesForm"; +export * from "./examTypesTable"; diff --git a/src/components/accessories/admin/types/components/exams/newExamType/NewExamType.tsx b/src/components/accessories/admin/types/components/exams/newExamType/NewExamType.tsx new file mode 100644 index 000000000..cf6c3cea6 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/newExamType/NewExamType.tsx @@ -0,0 +1,41 @@ +import { useTranslation } from "react-i18next"; +import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { IState } from "../../../../../../../types"; +import { ApiResponse } from "../../../../../../../state/types"; +import { ExamTypeDTO } from "../../../../../../../generated"; +import { createExamType } from "../../../../../../../state/types/exams/actions"; +import ExamTypeForm from "../examTypesForm/ExamTypeForm"; +import { getInitialFields } from "../examTypesForm/consts"; +import { setTypeMode } from "../../../../../../../state/types/config"; +import "./styles.scss"; + +export const NewExamType = () => { + const dispatch = useDispatch(); + const { t } = useTranslation(); + const create = useSelector>( + (state) => state.types.exams.create + ); + + useEffect(() => { + dispatch(setTypeMode("edit")); + }); + + const handleSubmit = (value: ExamTypeDTO) => { + dispatch(createExamType(value)); + }; + + return ( +
+

{t("examTypes.addExamType")}

+ +
+ ); +}; diff --git a/src/components/accessories/admin/types/components/exams/newExamType/index.ts b/src/components/accessories/admin/types/components/exams/newExamType/index.ts new file mode 100644 index 000000000..c2e8a876d --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/newExamType/index.ts @@ -0,0 +1 @@ +export * from "./NewExamType"; diff --git a/src/components/accessories/admin/types/components/exams/newExamType/styles.scss b/src/components/accessories/admin/types/components/exams/newExamType/styles.scss new file mode 100644 index 000000000..80a36a76c --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/newExamType/styles.scss @@ -0,0 +1,5 @@ +.newExamType { + .title { + margin-bottom: 10px; + } +} diff --git a/src/components/accessories/admin/types/components/exams/styles.scss b/src/components/accessories/admin/types/components/exams/styles.scss new file mode 100644 index 000000000..0505a8e81 --- /dev/null +++ b/src/components/accessories/admin/types/components/exams/styles.scss @@ -0,0 +1,3 @@ +.examTypes { + margin-top: 50px; +} diff --git a/src/components/accessories/admin/types/components/index.ts b/src/components/accessories/admin/types/components/index.ts index 8dfca1a2e..6d91356d1 100644 --- a/src/components/accessories/admin/types/components/index.ts +++ b/src/components/accessories/admin/types/components/index.ts @@ -1,2 +1,3 @@ export * from "./vaccines"; +export * from "./exams"; export * from "./operations"; diff --git a/src/consts.ts b/src/consts.ts index c886143ef..61f38f525 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -39,9 +39,9 @@ export const PATHS = { admin_operations: "/admin/operations", admin_operations_edit: "/admin/operations/:id/edit", admin_operations_new: "/admin/operations/new", - admin_admissions_types: "/admin/types/admissions", - admin_admissions_types_new: "/admin/types/admissions/new", - admin_admissions_types_edit: "/admin/types/admissions/:code/edit", + admin_exams_types: "/admin/types/exams", + admin_exams_types_new: "/admin/types/exams/new", + admin_exams_types_edit: "/admin/types/exams/:code/edit", admin_diseases_types: "/admin/types/diseases", admin_diseases_types_new: "/admin/types/diseases/new", admin_diseases_types_edit: "/admin/types/diseases/:code/edit", @@ -54,4 +54,7 @@ export const PATHS = { admin_deliveries_types: "/admin/types/deliveries", admin_deliveries_types_new: "/admin/types/deliveries/new", admin_deliveries_types_edit: "/admin/types/deliveries/:code/edit", + admin_admissions_types: "/admin/types/admissions", + admin_admissions_types_new: "/admin/types/admissions/new", + admin_admissions_types_edit: "/admin/types/admissions/:code/edit", }; diff --git a/src/index.tsx b/src/index.tsx index b3d365d62..816976c93 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -25,7 +25,8 @@ import bills from "./state/bills/reducer"; import prices from "./state/prices/reducer"; import visits from "./state/visits/reducer"; import operations from "./state/operations/reducer"; -import examTypes from "./state/examTypes/reducer"; +import diseaseTypes from "./state/types/diseases/reducer"; +import examTypes from "./state/types/exams/reducer"; import ageTypes from "./state/ageTypes/reducer"; import hospital from "./state/hospital/reducer"; import layouts from "./state/layouts/reducer"; @@ -57,6 +58,7 @@ const reducer = combineReducers({ prices, visits, operations, + diseaseTypes, examTypes, ageTypes, hospital, diff --git a/src/mockServer/routes/examTypes.js b/src/mockServer/routes/examTypes.js index 4872f6711..010e882a7 100644 --- a/src/mockServer/routes/examTypes.js +++ b/src/mockServer/routes/examTypes.js @@ -5,5 +5,35 @@ export const examTypesRoutes = (server) => { server.get("/").intercept((_req, res) => { res.status(200).json(examTypesDTO); }); + server.post("/").intercept((req, res) => { + const body = req.jsonBody(); + switch (body.code) { + case "FAIL": + res.status(400).json({ message: "Fail to create exam type" }); + break; + default: + res.status(200).json(body); + } + }); + server.put("/:code").intercept((req, res) => { + const body = req.jsonBody(); + switch (body.code) { + case "FAIL": + res.status(400).json({ message: "Fail to update exam type" }); + break; + default: + res.status(200).json(body); + } + }); + server.delete("/:code").intercept((req, res) => { + const code = req.params.code; + switch (code) { + case "FAIL": + res.status(400).json({ message: "Fail to delete exam type" }); + break; + default: + res.status(200).json(true); + } + }); }); }; diff --git a/src/resources/i18n/en.json b/src/resources/i18n/en.json index 8d87545b5..250e7a638 100644 --- a/src/resources/i18n/en.json +++ b/src/resources/i18n/en.json @@ -826,6 +826,7 @@ "code": "Code", "vaccinetype": "Vaccine Type", "description": "Name", + "exams": "Type of exam", "addVaccine": "Add Vaccine", "editVaccine": "Edit Vaccine", "updateVaccine": "update Vaccine", @@ -842,6 +843,7 @@ "types": { "selectAType": "Select a type", "vaccines": "Type of vaccines", + "exams": "Type of exams", "admissions": "Types of admission", "diseases": "Type of diseases", "discharges": "Types of discharge", @@ -1010,5 +1012,22 @@ "updated": "Disease updated", "createSuccess": "Disease has been created successfully!", "updateSuccess": "Disease {{code}} has been updated successfully!" + }, + "examTypes": { + "code": "Code", + "description": "Description", + "title": "Manage exam types", + "addExamType": "New exam type", + "deleted": "Deleted", + "deleteSuccess": "The exam type has been deleted successfully!", + "saveExamTypes": "Save", + "editVaccineType": "Edit exam type", + "updateExamType": "Save changes", + "created": "Exam type created", + "createSuccess": "The exam type has been created successfully!", + "updated": "Exam type updated", + "updateSuccess": "The exam type has been updated successfully!", + "cancelCreation": "Are you sure to cancel the exam type creation?", + "cancelUpdate": "Are you sure to cancel the exam type update?" } } diff --git a/src/routes/Admin/TypesRoutes.tsx b/src/routes/Admin/TypesRoutes.tsx index 15326ee1c..a24bd4953 100644 --- a/src/routes/Admin/TypesRoutes.tsx +++ b/src/routes/Admin/TypesRoutes.tsx @@ -7,6 +7,10 @@ import { Route, Routes } from "react-router"; import NotFound from "../../components/activities/notFound/NotFound"; import Empty from "../../components/accessories/admin/types/Empty"; import TypesAdmin from "../../components/accessories/admin/types/TypesAdmin"; +import ExamTypes, { + EditExamType, + NewExamType, +} from "../../components/accessories/admin/types/components/exams"; import AdmissionTypes, { EditAdmissionType, NewAdmissionType, @@ -42,6 +46,18 @@ const TypesRoutes = () => { path: "vaccines/:code/edit", element: , }, + { + path: "exams", + element: , + }, + { + path: "exams/new", + element: , + }, + { + path: "exams/:code/edit", + element: , + }, { path: "admissions", element: , diff --git a/src/state/examTypes/actions.ts b/src/state/examTypes/actions.ts deleted file mode 100644 index ebee4e838..000000000 --- a/src/state/examTypes/actions.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { isEmpty } from "lodash"; -import { Dispatch } from "redux"; -import { ExamTypeDTO } from "../../generated"; -import { ExamTypesApi } from "../../generated/apis/ExamTypesApi"; -import { customConfiguration } from "../../libraries/apiUtils/configuration"; -import { IAction } from "../types"; -import { - GET_EXAMTYPE_FAIL, - GET_EXAMTYPE_LOADING, - GET_EXAMTYPE_SUCCESS, -} from "./consts"; - -const desaseTypesApi = new ExamTypesApi(customConfiguration()); - -export const getExamTypes = - () => - (dispatch: Dispatch>): void => { - dispatch({ - type: GET_EXAMTYPE_LOADING, - }); - desaseTypesApi.getExamTypes().subscribe( - (payload) => { - if (typeof payload === "object" && !isEmpty(payload)) { - dispatch({ - type: GET_EXAMTYPE_SUCCESS, - payload: payload, - }); - } else { - dispatch({ - type: GET_EXAMTYPE_SUCCESS, - payload: [], - }); - } - }, - (error) => { - dispatch({ - type: GET_EXAMTYPE_FAIL, - error: error?.response, - }); - } - ); - }; diff --git a/src/state/examTypes/consts.ts b/src/state/examTypes/consts.ts deleted file mode 100644 index c883346c7..000000000 --- a/src/state/examTypes/consts.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const GET_EXAMTYPE_LOADING = "diseases/GET_EXAMTYPE_LOADING"; -export const GET_EXAMTYPE_SUCCESS = "diseases/GET_EXAMTYPE_SUCCESS"; -export const GET_EXAMTYPE_FAIL = "diseases/GET_EXAMTYPE_FAIL"; diff --git a/src/state/examTypes/initial.ts b/src/state/examTypes/initial.ts deleted file mode 100644 index 84c19b2cf..000000000 --- a/src/state/examTypes/initial.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IExamTypeState } from "./types"; -import { ApiResponse } from "../types"; - -export const initial: IExamTypeState = { - getExamTypes: new ApiResponse({ status: "IDLE", data: [] }), -}; diff --git a/src/state/examTypes/reducer.ts b/src/state/examTypes/reducer.ts deleted file mode 100644 index 28d3334b6..000000000 --- a/src/state/examTypes/reducer.ts +++ /dev/null @@ -1,31 +0,0 @@ -import produce from "immer"; -import { IAction } from "../types"; -import { - GET_EXAMTYPE_FAIL, - GET_EXAMTYPE_LOADING, - GET_EXAMTYPE_SUCCESS, -} from "./consts"; -import { initial } from "./initial"; -import { IExamTypeState } from "./types"; - -export default produce((draft: IExamTypeState, action: IAction) => { - switch (action.type) { - case GET_EXAMTYPE_LOADING: { - draft.getExamTypes.status = "LOADING"; - break; - } - - case GET_EXAMTYPE_SUCCESS: { - draft.getExamTypes.status = "SUCCESS"; - draft.getExamTypes.data = action.payload; - delete draft.getExamTypes.error; - break; - } - - case GET_EXAMTYPE_FAIL: { - draft.getExamTypes.status = "FAIL"; - draft.getExamTypes.error = action.error; - break; - } - } -}, initial); diff --git a/src/state/examTypes/types.ts b/src/state/examTypes/types.ts deleted file mode 100644 index 51b22d67c..000000000 --- a/src/state/examTypes/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ExamTypeDTO } from "../../generated"; -import { ApiResponse } from "../types"; - -export type IExamTypeState = { - getExamTypes: ApiResponse>; -}; diff --git a/src/state/types/exams/actions.ts b/src/state/types/exams/actions.ts new file mode 100644 index 000000000..af90b74c7 --- /dev/null +++ b/src/state/types/exams/actions.ts @@ -0,0 +1,146 @@ +import { isEmpty } from "lodash"; +import { Dispatch } from "redux"; +import { ExamTypeDTO, ExamTypesApi } from "../../../generated"; +import { customConfiguration } from "../../../libraries/apiUtils/configuration"; +import { IAction } from "../../types"; +import { + CREATE_EXAM_TYPES_FAIL, + CREATE_EXAM_TYPES_LOADING, + CREATE_EXAM_TYPES_RESET, + CREATE_EXAM_TYPES_SUCCESS, + DELETE_EXAM_TYPES_FAIL, + DELETE_EXAM_TYPES_LOADING, + DELETE_EXAM_TYPES_RESET, + DELETE_EXAM_TYPES_SUCCESS, + GET_EXAM_TYPES_FAIL, + GET_EXAM_TYPES_LOADING, + GET_EXAM_TYPES_SUCCESS, + GET_EXAM_TYPES_SUCCESS_EMPTY, + UPDATE_EXAM_TYPES_FAIL, + UPDATE_EXAM_TYPES_LOADING, + UPDATE_EXAM_TYPES_RESET, + UPDATE_EXAM_TYPES_SUCCESS, +} from "./consts"; + +const examTypesApi = new ExamTypesApi(customConfiguration()); + +export const getExamTypes = + () => + (dispatch: Dispatch>): void => { + dispatch({ + type: GET_EXAM_TYPES_LOADING, + }); + examTypesApi.getExamTypes({}).subscribe( + (payload) => { + if (typeof payload === "object" && !isEmpty(payload)) { + dispatch({ + type: GET_EXAM_TYPES_SUCCESS, + payload: payload, + }); + } else { + dispatch({ + type: GET_EXAM_TYPES_SUCCESS_EMPTY, + payload: [], + }); + } + }, + (error) => { + dispatch({ + type: GET_EXAM_TYPES_FAIL, + error: error?.response, + }); + } + ); + }; + +export const createExamType = + (newExamType: ExamTypeDTO) => + (dispatch: Dispatch>): void => { + dispatch({ + type: CREATE_EXAM_TYPES_LOADING, + }); + examTypesApi.newExamType({ examTypeDTO: newExamType }).subscribe( + (payload) => { + dispatch({ + type: CREATE_EXAM_TYPES_SUCCESS, + payload: payload, + }); + }, + (error) => { + dispatch({ + type: CREATE_EXAM_TYPES_FAIL, + error: error?.response, + }); + } + ); + }; + +export const createExamTypeReset = + () => + (dispatch: Dispatch>): void => { + dispatch({ + type: CREATE_EXAM_TYPES_RESET, + }); + }; + +export const updateExamType = + (updateExamType: ExamTypeDTO, code: string) => + (dispatch: Dispatch>): void => { + dispatch({ + type: UPDATE_EXAM_TYPES_LOADING, + }); + examTypesApi + .updateExamType({ code, examTypeDTO: updateExamType }) + .subscribe( + (payload) => { + dispatch({ + type: UPDATE_EXAM_TYPES_SUCCESS, + payload: payload, + }); + }, + (error) => { + dispatch({ + type: UPDATE_EXAM_TYPES_FAIL, + error: error?.response, + }); + } + ); + }; + +export const updateExamTypeReset = + () => + (dispatch: Dispatch>): void => { + dispatch({ + type: UPDATE_EXAM_TYPES_RESET, + }); + }; + +export const deleteExamType = + (code: string) => + (dispatch: Dispatch>): void => { + dispatch({ + type: DELETE_EXAM_TYPES_LOADING, + }); + examTypesApi.deleteExamType({ code }).subscribe( + (payload) => { + dispatch({ + type: DELETE_EXAM_TYPES_SUCCESS, + payload: { deleted: payload, code }, + }); + }, + (error) => { + dispatch({ + type: DELETE_EXAM_TYPES_FAIL, + error: error?.response, + }); + } + ); + }; + +export const deleteExamTypeReset = + () => + (dispatch: Dispatch>): void => { + dispatch({ + type: DELETE_EXAM_TYPES_RESET, + }); + }; diff --git a/src/state/types/exams/consts.ts b/src/state/types/exams/consts.ts new file mode 100644 index 000000000..1e3cffd3c --- /dev/null +++ b/src/state/types/exams/consts.ts @@ -0,0 +1,20 @@ +export const GET_EXAM_TYPES_LOADING = "examTypes/GET_EXAM_TYPES_LOADING"; +export const GET_EXAM_TYPES_SUCCESS = "examTypes/GET_EXAM_TYPES_SUCCESS"; +export const GET_EXAM_TYPES_FAIL = "examTypes/GET_EXAM_TYPES_FAIL"; +export const GET_EXAM_TYPES_SUCCESS_EMPTY = + "examTypes/GET_EXAM_TYPES_SUCCESS_EMPTY"; + +export const CREATE_EXAM_TYPES_LOADING = "examTypes/CREATE_EXAM_TYPES_LOADING"; +export const CREATE_EXAM_TYPES_SUCCESS = "examTypes/CREATE_EXAM_TYPES_SUCCESS"; +export const CREATE_EXAM_TYPES_FAIL = "examTypes/CREATE_EXAM_TYPES_FAIL"; +export const CREATE_EXAM_TYPES_RESET = "examTypes/CREATE_EXAM_TYPES_RESET"; + +export const UPDATE_EXAM_TYPES_LOADING = "examTypes/UPDATE_EXAM_TYPES_LOADING"; +export const UPDATE_EXAM_TYPES_SUCCESS = "examTypes/UPDATE_EXAM_TYPES_SUCCESS"; +export const UPDATE_EXAM_TYPES_FAIL = "examTypes/UPDATE_EXAM_TYPES_FAIL"; +export const UPDATE_EXAM_TYPES_RESET = "examTypes/UPDATE_EXAM_TYPES_RESET"; + +export const DELETE_EXAM_TYPES_LOADING = "examTypes/DELETE_EXAM_TYPES_LOADING"; +export const DELETE_EXAM_TYPES_SUCCESS = "examTypes/DELETE_EXAM_TYPES_SUCCESS"; +export const DELETE_EXAM_TYPES_FAIL = "examTypes/DELETE_EXAM_TYPES_FAIL"; +export const DELETE_EXAM_TYPES_RESET = "examTypes/DELETE_EXAM_TYPES_RESET"; diff --git a/src/state/types/exams/index.ts b/src/state/types/exams/index.ts new file mode 100644 index 000000000..dbfb25277 --- /dev/null +++ b/src/state/types/exams/index.ts @@ -0,0 +1,5 @@ +export * from "./types"; +export * from "./actions"; +export * from "./initial"; +export * from "./reducer"; +export * from "./consts"; diff --git a/src/state/types/exams/initial.ts b/src/state/types/exams/initial.ts new file mode 100644 index 000000000..f2ba8ce2a --- /dev/null +++ b/src/state/types/exams/initial.ts @@ -0,0 +1,9 @@ +import { ApiResponse } from "../../types"; +import { IExamTypesState } from "./types"; + +export const initial: IExamTypesState = { + getAll: new ApiResponse({ status: "IDLE", data: [] }), + create: new ApiResponse({ status: "IDLE" }), + update: new ApiResponse({ status: "IDLE" }), + delete: new ApiResponse({ status: "IDLE" }), +}; diff --git a/src/state/types/exams/reducer.ts b/src/state/types/exams/reducer.ts new file mode 100644 index 000000000..4640c78bd --- /dev/null +++ b/src/state/types/exams/reducer.ts @@ -0,0 +1,147 @@ +import produce from "immer"; +import { ExamTypeDTO } from "../../../generated"; +import { IAction } from "../../types"; +import { + CREATE_EXAM_TYPES_FAIL, + CREATE_EXAM_TYPES_LOADING, + CREATE_EXAM_TYPES_RESET, + CREATE_EXAM_TYPES_SUCCESS, + DELETE_EXAM_TYPES_FAIL, + DELETE_EXAM_TYPES_LOADING, + DELETE_EXAM_TYPES_RESET, + DELETE_EXAM_TYPES_SUCCESS, + GET_EXAM_TYPES_FAIL, + GET_EXAM_TYPES_LOADING, + GET_EXAM_TYPES_SUCCESS, + GET_EXAM_TYPES_SUCCESS_EMPTY, + UPDATE_EXAM_TYPES_FAIL, + UPDATE_EXAM_TYPES_LOADING, + UPDATE_EXAM_TYPES_RESET, + UPDATE_EXAM_TYPES_SUCCESS, +} from "./consts"; +import { initial } from "./initial"; +import { IExamTypesState } from "./types"; + +export default produce((draft: IExamTypesState, action: IAction) => { + switch (action.type) { + /** + * Create exam type + */ + case CREATE_EXAM_TYPES_LOADING: { + draft.create.status = "LOADING"; + break; + } + + case CREATE_EXAM_TYPES_SUCCESS: { + draft.create.status = "SUCCESS"; + draft.create.data = action.payload; + draft.getAll.data = [...(draft.getAll.data ?? []), action.payload]; + delete draft.create.error; + break; + } + + case CREATE_EXAM_TYPES_FAIL: { + draft.create.status = "FAIL"; + draft.create.error = action.error; + break; + } + + case CREATE_EXAM_TYPES_RESET: { + draft.create.status = "IDLE"; + delete draft.create.error; + break; + } + + /** + * Get exam types + */ + case GET_EXAM_TYPES_LOADING: { + draft.getAll.status = "LOADING"; + break; + } + + case GET_EXAM_TYPES_SUCCESS: { + draft.getAll.status = "SUCCESS"; + draft.getAll.data = action.payload; + delete draft.getAll.error; + break; + } + + case GET_EXAM_TYPES_FAIL: { + draft.getAll.status = "FAIL"; + draft.getAll.error = action.error; + break; + } + + case GET_EXAM_TYPES_SUCCESS_EMPTY: { + draft.getAll.status = "SUCCESS_EMPTY"; + draft.getAll.data = []; + delete draft.getAll.error; + break; + } + + /** + * Update exam type + */ + case UPDATE_EXAM_TYPES_LOADING: { + draft.update.status = "LOADING"; + delete draft.update.error; + break; + } + + case UPDATE_EXAM_TYPES_SUCCESS: { + draft.update.status = "SUCCESS"; + draft.update.data = action.payload; + draft.getAll.data = draft.getAll.data?.map((e) => { + return e.code === action.payload.code + ? (action.payload as ExamTypeDTO) + : e; + }); + delete draft.update.error; + break; + } + + case UPDATE_EXAM_TYPES_FAIL: { + draft.update.status = "FAIL"; + draft.update.error = action.error; + break; + } + + case UPDATE_EXAM_TYPES_RESET: { + draft.update.status = "IDLE"; + delete draft.update.error; + break; + } + + /** + * Delete exam type + */ + case DELETE_EXAM_TYPES_LOADING: { + draft.delete.status = "LOADING"; + delete draft.delete.error; + break; + } + + case DELETE_EXAM_TYPES_SUCCESS: { + draft.delete.status = "SUCCESS"; + draft.delete.data = action.payload.deleted; + draft.getAll.data = draft.getAll.data?.filter((e) => { + return e.code !== action.payload.code; + }); + delete draft.delete.error; + break; + } + + case DELETE_EXAM_TYPES_FAIL: { + draft.delete.status = "FAIL"; + draft.delete.error = action.error; + break; + } + + case DELETE_EXAM_TYPES_RESET: { + draft.delete.status = "IDLE"; + delete draft.delete.error; + break; + } + } +}, initial); diff --git a/src/state/types/exams/types.ts b/src/state/types/exams/types.ts new file mode 100644 index 000000000..d5c874513 --- /dev/null +++ b/src/state/types/exams/types.ts @@ -0,0 +1,9 @@ +import { ExamTypeDTO } from "../../../generated"; +import { ApiResponse } from "../../types"; + +export type IExamTypesState = { + getAll: ApiResponse>; + create: ApiResponse; + update: ApiResponse; + delete: ApiResponse; +}; diff --git a/src/state/types/reducer.ts b/src/state/types/reducer.ts index 81049cc39..751d621f9 100644 --- a/src/state/types/reducer.ts +++ b/src/state/types/reducer.ts @@ -3,6 +3,7 @@ import vaccineTypes from "./vaccines/reducer"; import admissions from "./admissions/reducer"; import operationTypes from "./operations/reducer"; import config from "./config/reducer"; +import exams from "./exams/reducer"; import diseases from "./diseases/reducer"; import discharges from "./discharges/reducer"; import deliveries from "./deliveries/reducer"; @@ -13,6 +14,7 @@ const typesReducer = combineReducers({ diseases, operations: operationTypes, config, + exams, discharges, deliveries, }); diff --git a/src/state/types/types.ts b/src/state/types/types.ts index bb4c01d5d..f21f99ad7 100644 --- a/src/state/types/types.ts +++ b/src/state/types/types.ts @@ -5,6 +5,7 @@ import { IDeliveryTypesState } from "./deliveries"; import { IDiseaseTypesState } from "./diseases"; import { IOperationTypesState } from "./operations"; import { IVaccineTypesState } from "./vaccines"; +import { IExamTypesState } from "./exams"; export type ITypesState = { vaccines: IVaccineTypesState; @@ -12,6 +13,7 @@ export type ITypesState = { diseases: IDiseaseTypesState; operations: IOperationTypesState; config: ITypeConfigsState; + exams: IExamTypesState; discharges: IDischargeTypesState; deliveries: IDeliveryTypesState; }; diff --git a/src/types.ts b/src/types.ts index dbbd1ea2f..1595eb9fd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,7 +14,8 @@ 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 { IExamTypeState } from "./state/examTypes/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"; @@ -41,7 +42,8 @@ export interface IState { prices: IPricesState; visits: IVisitState; operations: IOperationState; - examTypes: IExamTypeState; + diseaseTypes: IDiseaseTypesState; + examTypes: IExamTypesState; ageTypes: IAgeTypeState; hospital: IHospitalState; layouts: ILayoutsState;