From d78ab1c78b0523f35e8ac978a588414a10d26cfc Mon Sep 17 00:00:00 2001 From: Silevester Dongmo <58907550+SilverD3@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:02:54 +0100 Subject: [PATCH] OH2-295 | Types / Operation CRUD (#605) * feature:Operation types admin * fix:Fix type selector malfunction * remove unused import --- .../operationForm/OperationForm.tsx | 4 +- .../operationTable/OperationTable.tsx | 2 +- .../accessories/admin/types/TypesAdmin.tsx | 28 +-- .../admin/types/components/index.ts | 1 + .../components/operations/OperationTypes.tsx | 67 ++++++ .../editOperationType/EditOperationType.tsx | 49 +++++ .../operations/editOperationType/index.ts | 1 + .../operations/editOperationType/styles.scss | 5 + .../types/components/operations/index.ts | 7 + .../newOperationType/NewOperationType.tsx | 41 ++++ .../operations/newOperationType/index.ts | 1 + .../operations/newOperationType/styles.scss | 5 + .../operationTypesForm/OperationTypeForm.tsx | 207 ++++++++++++++++++ .../operations/operationTypesForm/consts.ts | 10 + .../operations/operationTypesForm/index.ts | 2 + .../operations/operationTypesForm/styles.scss | 77 +++++++ .../operations/operationTypesForm/types.ts | 13 ++ .../OperationTypesTable.tsx | 126 +++++++++++ .../operations/operationTypesTable/index.ts | 3 + .../operationTypesTable/styles.scss | 13 ++ .../types/components/operations/styles.scss | 3 + src/consts.ts | 3 + src/index.tsx | 2 - src/mockServer/routes/operationTypes.js | 30 +++ src/resources/i18n/en.json | 18 ++ src/routes/Admin/TypesRoutes.tsx | 16 ++ src/state/operationTypes/actions.ts | 41 ---- src/state/operationTypes/consts.ts | 3 - src/state/operationTypes/initial.ts | 6 - src/state/operationTypes/reducer.ts | 34 --- src/state/operationTypes/types.ts | 6 - src/state/types/operations/actions.ts | 146 ++++++++++++ src/state/types/operations/consts.ts | 35 +++ src/state/types/operations/index.ts | 5 + src/state/types/operations/initial.ts | 9 + src/state/types/operations/reducer.ts | 150 +++++++++++++ src/state/types/operations/types.ts | 9 + src/state/types/reducer.ts | 2 + src/state/types/types.ts | 2 + src/types.ts | 2 - 40 files changed, 1071 insertions(+), 113 deletions(-) create mode 100644 src/components/accessories/admin/types/components/operations/OperationTypes.tsx create mode 100644 src/components/accessories/admin/types/components/operations/editOperationType/EditOperationType.tsx create mode 100644 src/components/accessories/admin/types/components/operations/editOperationType/index.ts create mode 100644 src/components/accessories/admin/types/components/operations/editOperationType/styles.scss create mode 100644 src/components/accessories/admin/types/components/operations/index.ts create mode 100644 src/components/accessories/admin/types/components/operations/newOperationType/NewOperationType.tsx create mode 100644 src/components/accessories/admin/types/components/operations/newOperationType/index.ts create mode 100644 src/components/accessories/admin/types/components/operations/newOperationType/styles.scss create mode 100644 src/components/accessories/admin/types/components/operations/operationTypesForm/OperationTypeForm.tsx create mode 100644 src/components/accessories/admin/types/components/operations/operationTypesForm/consts.ts create mode 100644 src/components/accessories/admin/types/components/operations/operationTypesForm/index.ts create mode 100644 src/components/accessories/admin/types/components/operations/operationTypesForm/styles.scss create mode 100644 src/components/accessories/admin/types/components/operations/operationTypesForm/types.ts create mode 100644 src/components/accessories/admin/types/components/operations/operationTypesTable/OperationTypesTable.tsx create mode 100644 src/components/accessories/admin/types/components/operations/operationTypesTable/index.ts create mode 100644 src/components/accessories/admin/types/components/operations/operationTypesTable/styles.scss create mode 100644 src/components/accessories/admin/types/components/operations/styles.scss delete mode 100644 src/state/operationTypes/actions.ts delete mode 100644 src/state/operationTypes/consts.ts delete mode 100644 src/state/operationTypes/initial.ts delete mode 100644 src/state/operationTypes/reducer.ts delete mode 100644 src/state/operationTypes/types.ts create mode 100644 src/state/types/operations/actions.ts create mode 100644 src/state/types/operations/consts.ts create mode 100644 src/state/types/operations/index.ts create mode 100644 src/state/types/operations/initial.ts create mode 100644 src/state/types/operations/reducer.ts create mode 100644 src/state/types/operations/types.ts diff --git a/src/components/accessories/admin/operations/operationForm/OperationForm.tsx b/src/components/accessories/admin/operations/operationForm/OperationForm.tsx index 781d9ba8d..ed364ccf9 100644 --- a/src/components/accessories/admin/operations/operationForm/OperationForm.tsx +++ b/src/components/accessories/admin/operations/operationForm/OperationForm.tsx @@ -33,7 +33,7 @@ import { updateOperationReset, } from "../../../../../state/operations/actions"; import AutocompleteField from "../../../autocompleteField/AutocompleteField"; -import { getOperationTypes } from "../../../../../state/operationTypes/actions"; +import { getOperationTypes } from "../../../../../state/types/operations"; const OperationForm: FC = ({ fields, @@ -54,7 +54,7 @@ const OperationForm: FC = ({ ); const operationsTypeState = useSelector( - (state: IState) => state.operationTypes.getOperationTypes + (state: IState) => state.types.operations.getAll ); const operationsTypeOptions = useMemo( diff --git a/src/components/accessories/admin/operations/operationTable/OperationTable.tsx b/src/components/accessories/admin/operations/operationTable/OperationTable.tsx index 43b2aab3c..ca834b621 100644 --- a/src/components/accessories/admin/operations/operationTable/OperationTable.tsx +++ b/src/components/accessories/admin/operations/operationTable/OperationTable.tsx @@ -33,7 +33,7 @@ export const OperationTable: FunctionComponent = ({ { label: string; value: string }[] >( (state) => - state.operationTypes.getOperationTypes.data?.map((item) => ({ + state.types.operations.getAll.data?.map((item) => ({ label: item.description, value: item.code, })) ?? [] diff --git a/src/components/accessories/admin/types/TypesAdmin.tsx b/src/components/accessories/admin/types/TypesAdmin.tsx index 18240255d..51835a3ae 100644 --- a/src/components/accessories/admin/types/TypesAdmin.tsx +++ b/src/components/accessories/admin/types/TypesAdmin.tsx @@ -1,23 +1,23 @@ import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import AutocompleteField from "../../autocompleteField/AutocompleteField"; import "./styles.scss"; import { Outlet, useLocation, useNavigate } from "react-router"; import { PATHS } from "../../../../consts"; import { useSelector } from "react-redux"; import { IState } from "../../../../types"; import { TypeMode } from "../../../../state/types/config"; +import SelectField from "../../selectField/SelectField"; type TypeOption = { label: string; - routePath: string | null; + value: string; }; const TypesAdmin = () => { const { t } = useTranslation(); const location = useLocation(); const navigate = useNavigate(); - const defaultTypeOption: TypeOption = { label: "", routePath: null }; + const defaultTypeOption: TypeOption = { label: "", value: "" }; const [selectedOption, setSelectedOption] = useState(defaultTypeOption); const mode = useSelector( @@ -26,7 +26,8 @@ const TypesAdmin = () => { const typeOptions: TypeOption[] = [ defaultTypeOption, - { label: t("types.vaccines"), routePath: "vaccines" }, + { label: t("types.vaccines"), value: "vaccines" }, + { label: t("types.operations"), value: "operations" }, ]; useEffect(() => { @@ -36,8 +37,8 @@ const TypesAdmin = () => { ) { const typeFromUrl = typeOptions.find( (typeOption) => - typeOption.routePath !== null && - typeOption.routePath.includes( + typeOption.value !== null && + typeOption.value.includes( location.pathname.substring((PATHS.admin_types_base + "/").length) ) ); @@ -50,28 +51,23 @@ const TypesAdmin = () => { } }, [location]); - const handleTypeChange = (e: any, type: TypeOption | null) => { - if (type?.routePath) { - navigate(PATHS.admin_types_base + "/" + type.routePath); + const handleTypeChange = (value: string) => { + if (value?.length > 0) { + navigate(PATHS.admin_types_base + "/" + value); } else { navigate(PATHS.admin_types_base); } }; - const renderOption = (option: TypeOption) => { - return {option.label}; - }; - return ( <> {mode === "manage" && (
- { + const navigate = useNavigate(); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(getOperationTypes()); + dispatch(setTypeMode("manage")); + + return () => { + dispatch(deleteOperationTypeReset()); + }; + }, [dispatch]); + + const handleEdit = (row: OperationTypeDTO) => { + navigate(PATHS.admin_operations_types_edit.replace(":code", row.code!), { + state: row, + }); + }; + + const handleDelete = (row: OperationTypeDTO) => { + dispatch(deleteOperationType(row.code ?? "")); + }; + + const { t } = useTranslation(); + return ( + <> +

{t("operationTypes.title")}

+ +
+ { + navigate("./new"); + }} + type="button" + variant="contained" + color="primary" + > + {t("operationTypes.addOperationType")} + + } + /> +
+ + ); +}; + +export default OperationTypes; diff --git a/src/components/accessories/admin/types/components/operations/editOperationType/EditOperationType.tsx b/src/components/accessories/admin/types/components/operations/editOperationType/EditOperationType.tsx new file mode 100644 index 000000000..a708daff9 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/editOperationType/EditOperationType.tsx @@ -0,0 +1,49 @@ +import { useTranslation } from "react-i18next"; +import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { Navigate, useLocation, useParams } from "react-router"; +import { OperationTypeDTO } from "../../../../../../../generated"; +import { IState } from "../../../../../../../types"; +import { ApiResponse } from "../../../../../../../state/types"; +import { updateOperationType } from "../../../../../../../state/types/operations/actions"; +import { PATHS } from "../../../../../../../consts"; +import { setTypeMode } from "../../../../../../../state/types/config"; +import "./styles.scss"; +import OperationTypeForm from "../operationTypesForm/OperationTypeForm"; +import { getInitialFields } from "../operationTypesForm/consts"; + +export const EditOperationType = () => { + const dispatch = useDispatch(); + const { t } = useTranslation(); + const { state }: { state: OperationTypeDTO | undefined } = useLocation(); + const { code } = useParams(); + const update = useSelector>( + (state) => state.types.operations.update + ); + + const handleSubmit = (code: string, value: OperationTypeDTO) => { + dispatch(updateOperationType(code, value)); + }; + + useEffect(() => { + dispatch(setTypeMode("edit")); + }); + + if (state?.code !== code) { + return ; + } + + return ( +
+

{t("operationTypes.editOperationType")}

+ +
+ ); +}; diff --git a/src/components/accessories/admin/types/components/operations/editOperationType/index.ts b/src/components/accessories/admin/types/components/operations/editOperationType/index.ts new file mode 100644 index 000000000..cf7b088bc --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/editOperationType/index.ts @@ -0,0 +1 @@ +export * from "./EditOperationType"; diff --git a/src/components/accessories/admin/types/components/operations/editOperationType/styles.scss b/src/components/accessories/admin/types/components/operations/editOperationType/styles.scss new file mode 100644 index 000000000..9e41fb936 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/editOperationType/styles.scss @@ -0,0 +1,5 @@ +.editOperationType { + .title { + margin-bottom: 10px; + } +} diff --git a/src/components/accessories/admin/types/components/operations/index.ts b/src/components/accessories/admin/types/components/operations/index.ts new file mode 100644 index 000000000..47339bc7c --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/index.ts @@ -0,0 +1,7 @@ +import OperationTypes from "./OperationTypes"; + +export default OperationTypes; +export * from "./editOperationType"; +export * from "./newOperationType"; +export * from "./operationTypesForm"; +export * from "./operationTypesTable"; diff --git a/src/components/accessories/admin/types/components/operations/newOperationType/NewOperationType.tsx b/src/components/accessories/admin/types/components/operations/newOperationType/NewOperationType.tsx new file mode 100644 index 000000000..1f524cae5 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/newOperationType/NewOperationType.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 { OperationTypeDTO } from "../../../../../../../generated"; +import { createOperationType } from "../../../../../../../state/types/operations/actions"; +import { setTypeMode } from "../../../../../../../state/types/config"; +import "./styles.scss"; +import OperationTypeForm from "../operationTypesForm/OperationTypeForm"; +import { getInitialFields } from "../operationTypesForm/consts"; + +export const NewOperationType = () => { + const dispatch = useDispatch(); + const { t } = useTranslation(); + const create = useSelector>( + (state) => state.types.operations.create + ); + + useEffect(() => { + dispatch(setTypeMode("edit")); + }); + + const handleSubmit = (code: string, value: OperationTypeDTO) => { + dispatch(createOperationType(value)); + }; + + return ( +
+

{t("operationTypes.addOperationType")}

+ +
+ ); +}; diff --git a/src/components/accessories/admin/types/components/operations/newOperationType/index.ts b/src/components/accessories/admin/types/components/operations/newOperationType/index.ts new file mode 100644 index 000000000..2cb20b317 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/newOperationType/index.ts @@ -0,0 +1 @@ +export * from "./NewOperationType"; diff --git a/src/components/accessories/admin/types/components/operations/newOperationType/styles.scss b/src/components/accessories/admin/types/components/operations/newOperationType/styles.scss new file mode 100644 index 000000000..534830431 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/newOperationType/styles.scss @@ -0,0 +1,5 @@ +.newOperationType { + .title { + margin-bottom: 10px; + } +} diff --git a/src/components/accessories/admin/types/components/operations/operationTypesForm/OperationTypeForm.tsx b/src/components/accessories/admin/types/components/operations/operationTypesForm/OperationTypeForm.tsx new file mode 100644 index 000000000..b7b00ff08 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/operationTypesForm/OperationTypeForm.tsx @@ -0,0 +1,207 @@ +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 { IOperationTypeFormProps } from "./types"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router"; +import { IState } from "../../../../../../../types"; +import { IOperationTypesState } from "../../../../../../../state/types/operations/types"; +import { + formatAllFieldValues, + getFromFields, +} from "../../../../../../../libraries/formDataHandling/functions"; +import { + createOperationTypeReset, + updateOperationTypeReset, +} from "../../../../../../../state/types/operations/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 OperationTypeForm: 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 operationTypesStore = useSelector( + (state) => state.types.operations + ); + + const errorMessage = useMemo( + () => + (creationMode + ? operationTypesStore.create.error?.message + : operationTypesStore.update.error?.message) ?? + t("common.somethingwrong"), + [ + creationMode, + t, + operationTypesStore.create.error?.message, + operationTypesStore.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.code, 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(createOperationTypeReset()); + } else { + dispatch(updateOperationTypeReset()); + } + }, [creationMode, dispatch]); + + useEffect(() => { + return cleanUp; + }, [cleanUp]); + + return ( +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ setOpenResetConfirmation(false)} + /> + {(creationMode + ? operationTypesStore.create.status === "FAIL" + : operationTypesStore.update.status === "FAIL") && ( +
+ +
+ )} + { + navigate(PATHS.admin_operations_types); + }} + handleSecondaryButtonClick={() => ({})} + /> + +
+ ); +}; + +export default OperationTypeForm; diff --git a/src/components/accessories/admin/types/components/operations/operationTypesForm/consts.ts b/src/components/accessories/admin/types/components/operations/operationTypesForm/consts.ts new file mode 100644 index 000000000..b34e96d79 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/operationTypesForm/consts.ts @@ -0,0 +1,10 @@ +import { OperationTypeFormFieldName } from "."; +import { OperationTypeDTO } from "../../../../../../../generated"; +import { TFields } from "../../../../../../../libraries/formDataHandling/types"; + +export const getInitialFields: ( + operaationType: OperationTypeDTO | undefined +) => TFields = (operaationType) => ({ + code: { type: "text", value: operaationType?.code ?? "" }, + description: { type: "text", value: operaationType?.description ?? "" }, +}); diff --git a/src/components/accessories/admin/types/components/operations/operationTypesForm/index.ts b/src/components/accessories/admin/types/components/operations/operationTypesForm/index.ts new file mode 100644 index 000000000..9d17cd0e6 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/operationTypesForm/index.ts @@ -0,0 +1,2 @@ +export * from "./OperationTypeForm"; +export * from "./types"; diff --git a/src/components/accessories/admin/types/components/operations/operationTypesForm/styles.scss b/src/components/accessories/admin/types/components/operations/operationTypesForm/styles.scss new file mode 100644 index 000000000..f4318fcdf --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/operationTypesForm/styles.scss @@ -0,0 +1,77 @@ +@import "../../../../../../../../node_modules/susy/sass/susy"; +@import "../../../../../../../styles/variables"; + +.operationTypesForm { + display: inline-block; + flex-direction: column; + align-items: center; + width: 100%; + + .formInsertMode { + margin: 0px 0px 20px; + } + + .row { + justify-content: space-between; + } + + .operationTypesForm__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%; + } + } + + .operationTypesForm__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/operations/operationTypesForm/types.ts b/src/components/accessories/admin/types/components/operations/operationTypesForm/types.ts new file mode 100644 index 000000000..2b711397f --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/operationTypesForm/types.ts @@ -0,0 +1,13 @@ +import { OperationTypeDTO } from "../../../../../../../generated"; +import { TFields } from "../../../../../../../libraries/formDataHandling/types"; + +export interface IOperationTypeFormProps { + fields: TFields; + onSubmit: (code: string, adm: OperationTypeDTO) => void; + creationMode: boolean; + submitButtonLabel: string; + resetButtonLabel: string; + isLoading: boolean; +} + +export type OperationTypeFormFieldName = "code" | "description"; diff --git a/src/components/accessories/admin/types/components/operations/operationTypesTable/OperationTypesTable.tsx b/src/components/accessories/admin/types/components/operations/operationTypesTable/OperationTypesTable.tsx new file mode 100644 index 000000000..fbcacca62 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/operationTypesTable/OperationTypesTable.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 { OperationTypeDTO } from "../../../../../../../generated"; +import InfoBox from "../../../../../infoBox/InfoBox"; +import { CircularProgress } from "@material-ui/core"; +import Table from "../../../../../table/Table"; +import ConfirmationDialog from "../../../../../confirmationDialog/ConfirmationDialog"; +import { deleteOperationTypeReset } from "../../../../../../../state/types/operations/actions"; +import checkIcon from "../../../../../../../assets/check-icon.png"; +import "./styles.scss"; + +interface IOwnProps { + onEdit: (row: any) => void; + onDelete: (row: any) => void; + headerActions?: ReactNode; +} + +const OperationTypesTable = (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("operationTypes.code"), + description: t("operationTypes.description"), + }; + const order = ["code", "description"]; + + const { data, status, error } = useSelector< + IState, + ApiResponse + >((state) => state.types.operations.getAll); + + const deleteOperationType = useSelector>( + (state) => state.types.operations.delete + ); + + const handleEdit = (row: OperationTypeDTO) => { + onEdit((data ?? []).find((item) => item.code === row?.code)); + }; + + const handleDelete = (row: OperationTypeDTO) => { + onDelete(row); + }; + + const formatDataToDisplay = (data: OperationTypeDTO[]) => { + return data.map((item) => { + return { + code: item.code, + description: item.description, + }; + }); + }; + + return ( +
+ {(() => { + switch (status) { + case "FAIL": + return ( +
+ +
+ ); + case "LOADING": + return ; + + case "SUCCESS": + return ( + <> + + {deleteOperationType.status === "FAIL" && ( +
+ +
+ )} + { + dispatch(deleteOperationTypeReset()); + }} + handleSecondaryButtonClick={() => ({})} + /> + + ); + case "SUCCESS_EMPTY": + return ; + default: + return; + } + })()} + + ); +}; + +export default OperationTypesTable; diff --git a/src/components/accessories/admin/types/components/operations/operationTypesTable/index.ts b/src/components/accessories/admin/types/components/operations/operationTypesTable/index.ts new file mode 100644 index 000000000..a536b5177 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/operationTypesTable/index.ts @@ -0,0 +1,3 @@ +import OperationTypesTable from "./OperationTypesTable"; + +export default OperationTypesTable; diff --git a/src/components/accessories/admin/types/components/operations/operationTypesTable/styles.scss b/src/components/accessories/admin/types/components/operations/operationTypesTable/styles.scss new file mode 100644 index 000000000..f5b35a284 --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/operationTypesTable/styles.scss @@ -0,0 +1,13 @@ +.operationTypesTable { + display: grid; + margin-top: 50px; + + .fullWidth { + width: 100%; + } + + .loader { + margin-left: 50%; + position: relative; + } +} diff --git a/src/components/accessories/admin/types/components/operations/styles.scss b/src/components/accessories/admin/types/components/operations/styles.scss new file mode 100644 index 000000000..711ff2d2a --- /dev/null +++ b/src/components/accessories/admin/types/components/operations/styles.scss @@ -0,0 +1,3 @@ +.operationTypes { + margin-top: 50px; +} diff --git a/src/consts.ts b/src/consts.ts index 8778aa67d..66c8db5a9 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -37,4 +37,7 @@ export const PATHS = { admin_operations: "/admin/operations", admin_operations_edit: "/admin/operations/:id/edit", admin_operations_new: "/admin/operations/new", + admin_operations_types: "/admin/types/operations", + admin_operations_types_new: "/admin/types/operations/new", + admin_operations_types_edit: "/admin/types/operations/:code/edit", }; diff --git a/src/index.tsx b/src/index.tsx index 70b2d08cb..c9c2b01db 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -33,7 +33,6 @@ import ageTypes from "./state/ageTypes/reducer"; import hospital from "./state/hospital/reducer"; import layouts from "./state/layouts/reducer"; import dashboard from "./state/dashboard/reducer"; -import operationTypes from "./state/operationTypes/reducer"; import users from "./state/users/reducer"; import vaccines from "./state/vaccines/reducer"; import types from "./state/types/reducer"; @@ -69,7 +68,6 @@ const reducer = combineReducers({ hospital, layouts, dashboard, - operationTypes, users, vaccines, types, diff --git a/src/mockServer/routes/operationTypes.js b/src/mockServer/routes/operationTypes.js index 6f0cf28cd..56b917b05 100644 --- a/src/mockServer/routes/operationTypes.js +++ b/src/mockServer/routes/operationTypes.js @@ -5,5 +5,35 @@ export const operationTypeRoutes = (server) => { server.get("/").intercept((req, res) => { res.status(200).json(operationTypesDTO); }); + server.post("/").intercept((req, res) => { + const body = req.jsonBody(); + switch (body.code) { + case "FAIL": + res.status(400).json({ message: "Fail to create operation 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 operation 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 operation type" }); + break; + default: + res.status(200).json(true); + } + }); }); }; diff --git a/src/resources/i18n/en.json b/src/resources/i18n/en.json index 69d0e330a..1f72c2a03 100644 --- a/src/resources/i18n/en.json +++ b/src/resources/i18n/en.json @@ -830,6 +830,7 @@ "types": { "selectAType": "Select a type", "vaccines": "Type of vaccines", + "operations": "Type of operations", "chooseATypeToStart": "Choose a type to start..." }, "vaccineTypes": { @@ -849,6 +850,23 @@ "cancelCreation": "Are you sure to cancel the vaccine type creation?", "cancelUpdate": "Are you sure to cancel the vaccine type update?" }, + "operationTypes": { + "code": "Code", + "description": "Description", + "title": "Manage operation types", + "addOperationType": "New operation type", + "deleted": "Deleted", + "deleteSuccess": "The operation type has been deleted successfully!", + "saveOperationTypes": "Save", + "editOperationType": "Edit operation type", + "updateOperationType": "Save changes", + "created": "Operation type created", + "createSuccess": "The operation type has been created successfully!", + "updated": "Operation type updated", + "updateSuccess": "The operation type has been updated successfully!", + "cancelCreation": "Are you sure to cancel the operation type creation?", + "cancelUpdate": "Are you sure to cancel the operation type update?" + }, "supplier": { "id": "Supplier ID", "name": "Name", diff --git a/src/routes/Admin/TypesRoutes.tsx b/src/routes/Admin/TypesRoutes.tsx index e5fac7553..a8908a3ac 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 OperationTypes, { + EditOperationType, + NewOperationType, +} from "../../components/accessories/admin/types/components/operations"; const TypesRoutes = () => { const routes: { element: ReactNode; path: string }[] = [ @@ -22,6 +26,18 @@ const TypesRoutes = () => { path: "vaccines/:code/edit", element: , }, + { + path: "operations", + element: , + }, + { + path: "operations/new", + element: , + }, + { + path: "operations/:code/edit", + element: , + }, ]; return ( diff --git a/src/state/operationTypes/actions.ts b/src/state/operationTypes/actions.ts deleted file mode 100644 index 031f279c8..000000000 --- a/src/state/operationTypes/actions.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { isEmpty } from "lodash"; -import { Dispatch } from "redux"; -import { OperationTypeDTO, OperationsTypesApi } from "../../generated"; -import { customConfiguration } from "../../libraries/apiUtils/configuration"; -import { IAction } from "../types"; -import { - GET_OPERATIONTYPE_FAIL, - GET_OPERATIONTYPE_LOADING, - GET_OPERATIONTYPE_SUCCESS, -} from "./consts"; - -const operationTypesApi = new OperationsTypesApi(customConfiguration()); - -export const getOperationTypes = - () => - (dispatch: Dispatch>): void => { - dispatch({ - type: GET_OPERATIONTYPE_LOADING, - }); - operationTypesApi.getOperationTypes().subscribe( - (payload) => { - if (typeof payload === "object" && !isEmpty(payload)) { - dispatch({ - type: GET_OPERATIONTYPE_SUCCESS, - payload: payload, - }); - } else { - dispatch({ - type: GET_OPERATIONTYPE_SUCCESS, - payload: [], - }); - } - }, - (error) => { - dispatch({ - type: GET_OPERATIONTYPE_FAIL, - error: error?.response, - }); - } - ); - }; diff --git a/src/state/operationTypes/consts.ts b/src/state/operationTypes/consts.ts deleted file mode 100644 index 901459550..000000000 --- a/src/state/operationTypes/consts.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const GET_OPERATIONTYPE_LOADING = "operations/GET_OPERATIONTYPE_LOADING"; -export const GET_OPERATIONTYPE_SUCCESS = "operations/GET_OPERATIONTYPE_SUCCESS"; -export const GET_OPERATIONTYPE_FAIL = "operations/GET_OPERATIONTYPE_FAIL"; diff --git a/src/state/operationTypes/initial.ts b/src/state/operationTypes/initial.ts deleted file mode 100644 index d3ba48eab..000000000 --- a/src/state/operationTypes/initial.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IOperationTypeState } from "./types"; -import { ApiResponse } from "../types"; - -export const initial: IOperationTypeState = { - getOperationTypes: new ApiResponse({ status: "IDLE", data: [] }), -}; diff --git a/src/state/operationTypes/reducer.ts b/src/state/operationTypes/reducer.ts deleted file mode 100644 index 002a88a9c..000000000 --- a/src/state/operationTypes/reducer.ts +++ /dev/null @@ -1,34 +0,0 @@ -import produce from "immer"; -import { IAction } from "../types"; -import { - GET_OPERATIONTYPE_FAIL, - GET_OPERATIONTYPE_LOADING, - GET_OPERATIONTYPE_SUCCESS, -} from "./consts"; -import { initial } from "./initial"; -import { IOperationTypeState } from "./types"; - -export default produce( - (draft: IOperationTypeState, action: IAction) => { - switch (action.type) { - case GET_OPERATIONTYPE_LOADING: { - draft.getOperationTypes.status = "LOADING"; - break; - } - - case GET_OPERATIONTYPE_SUCCESS: { - draft.getOperationTypes.status = "SUCCESS"; - draft.getOperationTypes.data = action.payload; - delete draft.getOperationTypes.error; - break; - } - - case GET_OPERATIONTYPE_FAIL: { - draft.getOperationTypes.status = "FAIL"; - draft.getOperationTypes.error = action.error; - break; - } - } - }, - initial -); diff --git a/src/state/operationTypes/types.ts b/src/state/operationTypes/types.ts deleted file mode 100644 index cc63c7c13..000000000 --- a/src/state/operationTypes/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { OperationTypeDTO } from "../../generated"; -import { ApiResponse } from "../types"; - -export type IOperationTypeState = { - getOperationTypes: ApiResponse>; -}; diff --git a/src/state/types/operations/actions.ts b/src/state/types/operations/actions.ts new file mode 100644 index 000000000..e8f8cd6fc --- /dev/null +++ b/src/state/types/operations/actions.ts @@ -0,0 +1,146 @@ +import { isEmpty } from "lodash"; +import { Dispatch } from "redux"; +import { OperationsTypesApi, OperationTypeDTO } from "../../../generated"; +import { customConfiguration } from "../../../libraries/apiUtils/configuration"; +import { IAction } from "../../types"; +import { + CREATE_OPERATION_TYPES_FAIL, + CREATE_OPERATION_TYPES_LOADING, + CREATE_OPERATION_TYPES_RESET, + CREATE_OPERATION_TYPES_SUCCESS, + DELETE_OPERATION_TYPES_FAIL, + DELETE_OPERATION_TYPES_LOADING, + DELETE_OPERATION_TYPES_RESET, + DELETE_OPERATION_TYPES_SUCCESS, + GET_OPERATION_TYPES_FAIL, + GET_OPERATION_TYPES_LOADING, + GET_OPERATION_TYPES_SUCCESS, + GET_OPERATION_TYPES_SUCCESS_EMPTY, + UPDATE_OPERATION_TYPES_FAIL, + UPDATE_OPERATION_TYPES_LOADING, + UPDATE_OPERATION_TYPES_RESET, + UPDATE_OPERATION_TYPES_SUCCESS, +} from "./consts"; + +const operationTypesApi = new OperationsTypesApi(customConfiguration()); + +export const getOperationTypes = + () => + (dispatch: Dispatch>): void => { + dispatch({ + type: GET_OPERATION_TYPES_LOADING, + }); + operationTypesApi.getOperationTypes({}).subscribe( + (payload) => { + if (typeof payload === "object" && !isEmpty(payload)) { + dispatch({ + type: GET_OPERATION_TYPES_SUCCESS, + payload: payload, + }); + } else { + dispatch({ + type: GET_OPERATION_TYPES_SUCCESS_EMPTY, + payload: [], + }); + } + }, + (error) => { + dispatch({ + type: GET_OPERATION_TYPES_FAIL, + error: error?.response, + }); + } + ); + }; + +export const createOperationType = + (operationTypeDTO: OperationTypeDTO) => + (dispatch: Dispatch>): void => { + dispatch({ + type: CREATE_OPERATION_TYPES_LOADING, + }); + operationTypesApi.newOperationType({ operationTypeDTO }).subscribe( + (payload) => { + dispatch({ + type: CREATE_OPERATION_TYPES_SUCCESS, + payload: payload, + }); + }, + (error) => { + dispatch({ + type: CREATE_OPERATION_TYPES_FAIL, + error: error?.response, + }); + } + ); + }; + +export const createOperationTypeReset = + () => + (dispatch: Dispatch>): void => { + dispatch({ + type: CREATE_OPERATION_TYPES_RESET, + }); + }; + +export const updateOperationType = + (code: string, operationTypeDTO: OperationTypeDTO) => + (dispatch: Dispatch>): void => { + dispatch({ + type: UPDATE_OPERATION_TYPES_LOADING, + }); + operationTypesApi + .updateOperationTypes({ code, operationTypeDTO }) + .subscribe( + (payload) => { + dispatch({ + type: UPDATE_OPERATION_TYPES_SUCCESS, + payload: payload, + }); + }, + (error) => { + dispatch({ + type: UPDATE_OPERATION_TYPES_FAIL, + error: error?.response, + }); + } + ); + }; + +export const updateOperationTypeReset = + () => + (dispatch: Dispatch>): void => { + dispatch({ + type: UPDATE_OPERATION_TYPES_RESET, + }); + }; + +export const deleteOperationType = + (code: string) => + (dispatch: Dispatch>): void => { + dispatch({ + type: DELETE_OPERATION_TYPES_LOADING, + }); + operationTypesApi.deleteOperationType({ code }).subscribe( + (payload) => { + dispatch({ + type: DELETE_OPERATION_TYPES_SUCCESS, + payload: { deleted: payload, code }, + }); + }, + (error) => { + dispatch({ + type: DELETE_OPERATION_TYPES_FAIL, + error: error?.response, + }); + } + ); + }; + +export const deleteOperationTypeReset = + () => + (dispatch: Dispatch>): void => { + dispatch({ + type: DELETE_OPERATION_TYPES_RESET, + }); + }; diff --git a/src/state/types/operations/consts.ts b/src/state/types/operations/consts.ts new file mode 100644 index 000000000..7f3f60e1f --- /dev/null +++ b/src/state/types/operations/consts.ts @@ -0,0 +1,35 @@ +export const GET_OPERATION_TYPES_LOADING = + "operationTypes/GET_OPERATION_TYPES_LOADING"; +export const GET_OPERATION_TYPES_SUCCESS = + "operationTypes/GET_OPERATION_TYPES_SUCCESS"; +export const GET_OPERATION_TYPES_FAIL = + "operationTypes/GET_OPERATION_TYPES_FAIL"; +export const GET_OPERATION_TYPES_SUCCESS_EMPTY = + "operationTypes/GET_OPERATION_TYPES_SUCCESS_EMPTY"; + +export const CREATE_OPERATION_TYPES_LOADING = + "operationTypes/CREATE_OPERATION_TYPES_LOADING"; +export const CREATE_OPERATION_TYPES_SUCCESS = + "operationTypes/CREATE_OPERATION_TYPES_SUCCESS"; +export const CREATE_OPERATION_TYPES_FAIL = + "operationTypes/CREATE_OPERATION_TYPES_FAIL"; +export const CREATE_OPERATION_TYPES_RESET = + "operationTypes/CREATE_OPERATION_TYPES_RESET"; + +export const UPDATE_OPERATION_TYPES_LOADING = + "operationTypes/UPDATE_OPERATION_TYPES_LOADING"; +export const UPDATE_OPERATION_TYPES_SUCCESS = + "operationTypes/UPDATE_OPERATION_TYPES_SUCCESS"; +export const UPDATE_OPERATION_TYPES_FAIL = + "operationTypes/UPDATE_OPERATION_TYPES_FAIL"; +export const UPDATE_OPERATION_TYPES_RESET = + "operationTypes/UPDATE_OPERATION_TYPES_RESET"; + +export const DELETE_OPERATION_TYPES_LOADING = + "operationTypes/DELETE_OPERATION_TYPES_LOADING"; +export const DELETE_OPERATION_TYPES_SUCCESS = + "operationTypes/DELETE_OPERATION_TYPES_SUCCESS"; +export const DELETE_OPERATION_TYPES_FAIL = + "operationTypes/DELETE_OPERATION_TYPES_FAIL"; +export const DELETE_OPERATION_TYPES_RESET = + "operationTypes/DELETE_OPERATION_TYPES_RESET"; diff --git a/src/state/types/operations/index.ts b/src/state/types/operations/index.ts new file mode 100644 index 000000000..dbfb25277 --- /dev/null +++ b/src/state/types/operations/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/operations/initial.ts b/src/state/types/operations/initial.ts new file mode 100644 index 000000000..f9b1bd1b1 --- /dev/null +++ b/src/state/types/operations/initial.ts @@ -0,0 +1,9 @@ +import { ApiResponse } from "../../types"; +import { IOperationTypesState } from "./types"; + +export const initial: IOperationTypesState = { + 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/operations/reducer.ts b/src/state/types/operations/reducer.ts new file mode 100644 index 000000000..47bc324b9 --- /dev/null +++ b/src/state/types/operations/reducer.ts @@ -0,0 +1,150 @@ +import produce from "immer"; +import { VaccineTypeDTO } from "../../../generated"; +import { IAction } from "../../types"; +import { + CREATE_OPERATION_TYPES_FAIL, + CREATE_OPERATION_TYPES_LOADING, + CREATE_OPERATION_TYPES_RESET, + CREATE_OPERATION_TYPES_SUCCESS, + DELETE_OPERATION_TYPES_FAIL, + DELETE_OPERATION_TYPES_LOADING, + DELETE_OPERATION_TYPES_RESET, + DELETE_OPERATION_TYPES_SUCCESS, + GET_OPERATION_TYPES_FAIL, + GET_OPERATION_TYPES_LOADING, + GET_OPERATION_TYPES_SUCCESS, + GET_OPERATION_TYPES_SUCCESS_EMPTY, + UPDATE_OPERATION_TYPES_FAIL, + UPDATE_OPERATION_TYPES_LOADING, + UPDATE_OPERATION_TYPES_RESET, + UPDATE_OPERATION_TYPES_SUCCESS, +} from "./consts"; +import { initial } from "./initial"; +import { IOperationTypesState } from "./types"; + +export default produce( + (draft: IOperationTypesState, action: IAction) => { + switch (action.type) { + /** + * Create vaccine type + */ + case CREATE_OPERATION_TYPES_LOADING: { + draft.create.status = "LOADING"; + break; + } + + case CREATE_OPERATION_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_OPERATION_TYPES_FAIL: { + draft.create.status = "FAIL"; + draft.create.error = action.error; + break; + } + + case CREATE_OPERATION_TYPES_RESET: { + draft.create.status = "IDLE"; + delete draft.create.error; + break; + } + + /** + * Get vaccine types + */ + case GET_OPERATION_TYPES_LOADING: { + draft.getAll.status = "LOADING"; + break; + } + + case GET_OPERATION_TYPES_SUCCESS: { + draft.getAll.status = "SUCCESS"; + draft.getAll.data = action.payload; + delete draft.getAll.error; + break; + } + + case GET_OPERATION_TYPES_FAIL: { + draft.getAll.status = "FAIL"; + draft.getAll.error = action.error; + break; + } + + case GET_OPERATION_TYPES_SUCCESS_EMPTY: { + draft.getAll.status = "SUCCESS_EMPTY"; + draft.getAll.data = []; + delete draft.getAll.error; + break; + } + + /** + * Update vaccine type + */ + case UPDATE_OPERATION_TYPES_LOADING: { + draft.update.status = "LOADING"; + delete draft.update.error; + break; + } + + case UPDATE_OPERATION_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 VaccineTypeDTO) + : e; + }); + delete draft.update.error; + break; + } + + case UPDATE_OPERATION_TYPES_FAIL: { + draft.update.status = "FAIL"; + draft.update.error = action.error; + break; + } + + case UPDATE_OPERATION_TYPES_RESET: { + draft.update.status = "IDLE"; + delete draft.update.error; + break; + } + + /** + * Delete vaccine type + */ + case DELETE_OPERATION_TYPES_LOADING: { + draft.delete.status = "LOADING"; + delete draft.delete.error; + break; + } + + case DELETE_OPERATION_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_OPERATION_TYPES_FAIL: { + draft.delete.status = "FAIL"; + draft.delete.error = action.error; + break; + } + + case DELETE_OPERATION_TYPES_RESET: { + draft.delete.status = "IDLE"; + delete draft.delete.error; + break; + } + } + }, + initial +); diff --git a/src/state/types/operations/types.ts b/src/state/types/operations/types.ts new file mode 100644 index 000000000..fdfb861e1 --- /dev/null +++ b/src/state/types/operations/types.ts @@ -0,0 +1,9 @@ +import { OperationTypeDTO } from "../../../generated"; +import { ApiResponse } from "../../types"; + +export type IOperationTypesState = { + getAll: ApiResponse>; + create: ApiResponse; + update: ApiResponse; + delete: ApiResponse; +}; diff --git a/src/state/types/reducer.ts b/src/state/types/reducer.ts index 76d9c2d37..e36cf3653 100644 --- a/src/state/types/reducer.ts +++ b/src/state/types/reducer.ts @@ -1,9 +1,11 @@ import { combineReducers } from "redux"; import vaccineTypes from "./vaccines/reducer"; +import operationTypes from "./operations/reducer"; import config from "./config/reducer"; const typesReducer = combineReducers({ vaccines: vaccineTypes, + operations: operationTypes, config, }); diff --git a/src/state/types/types.ts b/src/state/types/types.ts index 28194fc5e..216829426 100644 --- a/src/state/types/types.ts +++ b/src/state/types/types.ts @@ -1,7 +1,9 @@ import { ITypeConfigsState } from "./config"; +import { IOperationTypesState } from "./operations"; import { IVaccineTypesState } from "./vaccines"; export type ITypesState = { vaccines: IVaccineTypesState; + operations: IOperationTypesState; config: ITypeConfigsState; }; diff --git a/src/types.ts b/src/types.ts index b03471879..e0f4b0385 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,6 @@ 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 { IOperationTypeState } from "./state/operationTypes/types"; import { IUserState } from "./state/users/types"; import { IVaccineState } from "./state/vaccines/types"; import { ISupplierState } from "./state/suppliers/types"; @@ -53,7 +52,6 @@ export interface IState { hospital: IHospitalState; layouts: ILayoutsState; dashboard: IDashboardState; - operationTypes: IOperationTypeState; users: IUserState; vaccines: IVaccineState; types: ITypesState;