Skip to content

Commit

Permalink
OH2-294 | Types / Exams CRUD (#608)
Browse files Browse the repository at this point in the history
* Add exams types

* Deleted examTypes state

* Add admission path variable

---------

Co-authored-by: Alessandro Domanico <[email protected]>
  • Loading branch information
fogouang and mwithi authored Jun 18, 2024
1 parent 04f1ba5 commit 5cbe562
Show file tree
Hide file tree
Showing 39 changed files with 1,049 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 ?? "",
})) ?? []
Expand Down
1 change: 1 addition & 0 deletions src/components/accessories/admin/types/TypesAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<>
<h3>{t("examTypes.title")}</h3>

<div className="examTypes">
<ExamTypesTable
onEdit={handleEdit}
onDelete={handleDelete}
headerActions={
<Button
onClick={() => {
navigate(PATHS.admin_exams_types_new);
}}
type="button"
variant="contained"
color="primary"
>
{t("examTypes.addExamType")}
</Button>
}
/>
</div>
</>
);
};

export default ExamTypes;
Original file line number Diff line number Diff line change
@@ -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<IState, ApiResponse<ExamTypeDTO>>(
(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 <Navigate to={PATHS.admin_exams_types} />;
}

return (
<div className="editExamType">
<h3 className="title">{t("examTypes.editExamType")}</h3>
<ExamTypeForm
creationMode={false}
onSubmit={handleSubmit}
isLoading={!!update.isLoading}
resetButtonLabel={t("common.cancel")}
submitButtonLabel={t("examTypes.updateExamType")}
fields={getInitialFields(state)}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./EditExamType";
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.editExamType {
.title {
margin-bottom: 10px;
}
}
Original file line number Diff line number Diff line change
@@ -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<IExamTypeFormProps> = ({
fields,
onSubmit,
creationMode,
submitButtonLabel,
resetButtonLabel,
isLoading,
}) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const navigate = useNavigate();
const infoBoxRef = useRef<HTMLDivElement>(null);
const [openResetConfirmation, setOpenResetConfirmation] = useState(false);

const examTypesStore = useSelector<IState, IExamTypesState>(
(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 (
<div className="examTypesForm">
<form className="examTypesForm__form" onSubmit={formik.handleSubmit}>
<div className="row start-sm center-xs">
<div className="examTypesForm__item halfWidth">
<TextField
field={formik.getFieldProps("code")}
theme="regular"
label={t("examTypes.code")}
isValid={isValid("code")}
errorText={getErrorText("code")}
onBlur={formik.handleBlur}
type="text"
disabled={isLoading || !creationMode}
/>
</div>
<div className="examTypesForm__item halfWidth">
<TextField
field={formik.getFieldProps("description")}
theme="regular"
label={t("examTypes.description")}
isValid={isValid("description")}
errorText={getErrorText("description")}
onBlur={formik.handleBlur}
type="text"
disabled={isLoading}
/>
</div>
</div>

<div className="examTypesForm__buttonSet">
<div className="submit_button">
<Button type="submit" variant="contained" disabled={isLoading}>
{submitButtonLabel}
</Button>
</div>
<div className="reset_button">
<Button
type="reset"
variant="text"
disabled={isLoading}
onClick={() => setOpenResetConfirmation(true)}
>
{resetButtonLabel}
</Button>
</div>
</div>
<ConfirmationDialog
isOpen={openResetConfirmation}
title={resetButtonLabel.toUpperCase()}
info={
creationMode
? t("examTypes.cancelCreation")
: t("examTypes.cancelUpdate")
}
icon={warningIcon}
primaryButtonLabel={t("common.ok")}
secondaryButtonLabel={t("common.discard")}
handlePrimaryButtonClick={handleResetConfirmation}
handleSecondaryButtonClick={() => setOpenResetConfirmation(false)}
/>
{(creationMode
? examTypesStore.create.status === "FAIL"
: examTypesStore.update.status === "FAIL") && (
<div ref={infoBoxRef} className="info-box-container">
<InfoBox type="error" message={errorMessage} />
</div>
)}
<ConfirmationDialog
isOpen={
!!(creationMode
? examTypesStore.create.hasSucceeded
: examTypesStore.update.hasSucceeded)
}
title={creationMode ? t("examTypes.created") : t("examTypes.updated")}
icon={checkIcon}
info={
creationMode
? t("examTypes.createSuccess")
: t("examTypes.updateSuccess", { code: formik.values.code })
}
primaryButtonLabel="Ok"
handlePrimaryButtonClick={() => {
navigate(PATHS.admin_exams_types);
}}
handleSecondaryButtonClick={() => ({})}
/>
</form>
</div>
);
};

export default ExamTypeForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ExamTypeFormFieldName } from ".";
import { ExamTypeDTO } from "../../../../../../../generated";
import { TFields } from "../../../../../../../libraries/formDataHandling/types";

export const getInitialFields: (
examType: ExamTypeDTO | undefined
) => TFields<ExamTypeFormFieldName> = (examType) => ({
code: { type: "text", value: examType?.code ?? "" },
description: { type: "text", value: examType?.description ?? "" },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./ExamTypeForm";
export * from "./types";
Loading

0 comments on commit 5cbe562

Please sign in to comment.