Skip to content

Commit

Permalink
Merge branch 'develop' into feature/OP-1043-users-and-groups-soft-delete
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveGT96 committed Nov 13, 2024
2 parents 2bf0651 + 72918b4 commit 57cd95a
Show file tree
Hide file tree
Showing 56 changed files with 954 additions and 184 deletions.
8 changes: 6 additions & 2 deletions api/oh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1904,15 +1904,19 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/AgeTypeDTO"
type: array
items:
$ref: "#/components/schemas/AgeTypeDTO"
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/AgeTypeDTO"
type: array
items:
$ref: "#/components/schemas/AgeTypeDTO"
security:
- bearerAuth: []
/admissiontypes:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// <reference types="cypress" />

const AGE_TYPE_START_PATH = "/admin/types/ages";

describe("Admission types Edit Activity specs", () => {
it("should render the ui", () => {
cy.authenticate(AGE_TYPE_START_PATH);
cy.dataCy("sub-activity-title").contains("Manage age types");
});

it("should show age types edit form", () => {
cy.dataCy("edit-age-types").click();
cy.dataCy("sub-activity-title").contains("Edit age types");
});

it("should fail to edit the age type", () => {
cy.byId("ageTypes\\[0\\]\\.to").type("1");
cy.dataCy("submit-form").click();
cy.dataCy("dialog-info").should("not.exist");
});

it("should successfully save age types changes", () => {
cy.byId("ageTypes\\[0\\]\\.to").clear().type("0");
cy.byId("ageTypes\\[5\\]\\.to").clear().type("104");
cy.dataCy("submit-form").click();
cy.dataCy("dialog-info").contains("have been updated successfully!");
cy.dataCy("approve-dialog").click();
});

it("should redirect after age types update", () => {
cy.dataCy("sub-activity-title").contains("Manage age types");
});

it("should cancel the cancellation of the age types update", () => {
cy.dataCy("edit-age-types").click();
cy.dataCy("cancel-form").click();
cy.dataCy("dialog-info").contains(
"Are you sure to cancel the age types update?"
);
cy.dataCy("close-dialog").click();
cy.dataCy("dialog-info").should("not.exist");
});

it("should cancel the age types update", () => {
cy.dataCy("cancel-form").click();
cy.dataCy("approve-dialog").click();
cy.dataCy("dialog-info").should("not.exist");
cy.dataCy("sub-activity-title").contains("Manage age types");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// <reference types="cypress" />

const AGE_TYPES_START_PATH = "/admin/types/ages";

describe("Age types Activity specs", () => {
it("should render the ui", () => {
cy.authenticate(AGE_TYPES_START_PATH);
cy.dataCy("sub-activity-title").contains("Manage age types");
});

it("should present the table with 6 rows", () => {
cy.dataCy("age-types-table")
.find("table")
.then(($table) => {
const rows = $table.find("tbody tr");
expect(rows.length).equal(6);
});
});
});
9 changes: 5 additions & 4 deletions src/components/accessories/admin/types/TypesAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ const TypesAdmin = () => {
[
defaultTypeOption,
{ label: t("types.exams"), value: "exams" },
{ label: t("types.ages"), value: "ages" },
{ label: t("types.vaccines"), value: "vaccines" },
{ label: t("types.operations"), value: "operations" },
{ label: t("types.medicals"), value: "medicals" },
{ label: t("types.diseases"), value: "diseases" },
{ label: t("types.deliveries"), value: "deliveries" },
{ label: t("types.admissions"), value: "admissions" },
{ label: t("types.deliveryResultType"), value: "deliveryresulttypes" },
{ label: t("types.deliveries"), value: "deliveries" },
{ label: t("types.discharges"), value: "discharges" },
{ label: t("types.medicals"), value: "medicals" },
{ label: t("types.operations"), value: "operations" },
{ label: t("types.deliveryResultType"), value: "deliveryresulttypes" },
{ label: t("types.pregnantTreatment"), value: "pregnanttreatmenttypes" },
],
(type) => type.label
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useAppDispatch } from "libraries/hooks/redux";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
import { getAgeTypes } from "state/types/ageTypes";
import { setTypeMode } from "../../../../../../state/types/config";
import Button from "../../../../button/Button";
import AgeTypesTable from "./ageTypesTable";
import "./styles.scss";

const AgeTypes = () => {
const navigate = useNavigate();
const dispatch = useAppDispatch();

useEffect(() => {
dispatch(getAgeTypes());
dispatch(setTypeMode("manage"));
}, [dispatch]);

const { t } = useTranslation();

return (
<>
<h3 data-cy="sub-activity-title">{t("ageTypes.title")}</h3>

<div className="ageTypes" data-cy="age-types-table">
<AgeTypesTable
headerActions={
<Button
onClick={() => {
navigate("./edit");
}}
type="button"
variant="contained"
color="primary"
dataCy="edit-age-types"
>
{t("ageTypes.editAgeTypes")}
</Button>
}
/>
</div>
</>
);
};

export default AgeTypes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import TextField from "components/accessories/textField/TextField";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { IAgeTypeFieldsProps } from "./types";

const AgeTypeFields: FC<IAgeTypeFieldsProps> = ({
formik,
getErrorText,
isValid,
index,
}) => {
const { t } = useTranslation();

return (
<tr className="ageTypeFormRow">
<td>{formik.values.ageTypes[index].code}</td>
<td className="fromField">
<TextField
field={formik.getFieldProps(`ageTypes[${index}].from`)}
theme="regular"
label={t("ageTypes.from")}
isValid={isValid("from", index)}
errorText={getErrorText("from", index)}
onBlur={formik.handleBlur}
type="number"
/>
</td>
<td className="toField">
<TextField
field={formik.getFieldProps(`ageTypes[${index}].to`)}
theme="regular"
label={t("ageTypes.to")}
isValid={isValid("to", index)}
errorText={getErrorText("to", index)}
onBlur={formik.handleBlur}
type="number"
/>
</td>
<td>{t(formik.values.ageTypes[index].description)}</td>
</tr>
);
};

export default AgeTypeFields;
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { useFormik } from "formik";
import { AgeTypeDTO } from "generated";
import { useAppDispatch, useAppSelector } from "libraries/hooks/redux";
import { get, has } from "lodash";
import React, { FC, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
import { updateAgeTypeReset } from "state/types/ageTypes";
import { array, number, object, ref, string } from "yup";
import checkIcon from "../../../../../../../assets/check-icon.png";
import warningIcon from "../../../../../../../assets/warning-icon.png";
import { PATHS } from "../../../../../../../consts";
import {
formatAllFieldValues,
getFromFields,
} from "../../../../../../../libraries/formDataHandling/functions";
import Button from "../../../../../button/Button";
import ConfirmationDialog from "../../../../../confirmationDialog/ConfirmationDialog";
import InfoBox from "../../../../../infoBox/InfoBox";
import AgeTypeFields from "./AgeTypeFields";
import { validateRange } from "./consts";
import "./styles.scss";
import { IAgeTypesFormProps } from "./types";

const AgeTypesForm: FC<IAgeTypesFormProps> = ({
onSubmit,
rows,
submitButtonLabel,
resetButtonLabel,
isLoading,
}) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const navigate = useNavigate();
const infoBoxRef = useRef<HTMLDivElement>(null);
const [openResetConfirmation, setOpenResetConfirmation] = useState(false);
const [validationErrors, setValidationErrors] = useState<string[]>([]);

const updateAgeTypes = useAppSelector((state) => state.types.ageTypes.update);

const errorMessage = useMemo(
() => updateAgeTypes.error?.message ?? t("common.somethingwrong"),
[t, updateAgeTypes.error?.message]
);

const initialValues = {
ageTypes: rows.map((fields) => getFromFields(fields, "value")),
};

const validationSchema = object({
ageTypes: array(
object({
code: string().required(t("common.required")),
description: string().required(t("common.required")),
from: number()
.required(t("common.required"))
.min(0, t("common.greaterthan", { value: 0 })),
to: number()
.required(t("common.required"))
.min(ref("from"), t("ageTypes.shouldbegreaterthanfrom")),
})
),
});

const formik = useFormik({
initialValues,
validationSchema,
enableReinitialize: true,
onSubmit: (values) => {
const formattedValues = rows.map((fields, index) =>
formatAllFieldValues(fields, values.ageTypes[index])
);

const errors = validateRange(formattedValues as AgeTypeDTO[], t);
setValidationErrors(errors);
if (errors.length === 0) {
onSubmit(formattedValues as any);
}
},
});

const isValid = (fieldName: string, index: number): boolean => {
return (
has(formik.touched.ageTypes?.[index], fieldName) &&
has(formik.errors.ageTypes?.[index], fieldName)
);
};

const getErrorText = (fieldName: string, index: number): string => {
return has(formik.touched.ageTypes?.[index], fieldName)
? (get(formik.errors.ageTypes?.[index], fieldName) as string)
: "";
};

const handleResetConfirmation = () => {
setOpenResetConfirmation(false);
navigate(-1);
};

useEffect(() => {
return () => {
dispatch(updateAgeTypeReset());
};
}, [dispatch]);

return (
<div className="ageTypesForm">
<form className="ageTypesForm__form" onSubmit={formik.handleSubmit}>
<div className="row">
<table className="ageTypesFormTable">
<thead>
<tr>
<th>{t("ageTypes.code")}</th>
<th>{t("ageTypes.from")}</th>
<th>{t("ageTypes.to")}</th>
<th>{t("ageTypes.description")}</th>
</tr>
</thead>
<tbody>
{rows.map((fields, index) => (
<AgeTypeFields
formik={formik}
getErrorText={getErrorText}
index={index}
isValid={isValid}
key={index}
/>
))}
</tbody>
</table>
</div>

<div className="ageTypesForm__buttonSet">
<div className="submit_button">
<Button
type="submit"
dataCy="submit-form"
variant="contained"
disabled={isLoading}
>
{submitButtonLabel}
</Button>
</div>
<div className="reset_button">
<Button
type="reset"
variant="text"
dataCy="cancel-form"
disabled={isLoading}
onClick={() => setOpenResetConfirmation(true)}
>
{resetButtonLabel}
</Button>
</div>
</div>
<ConfirmationDialog
isOpen={openResetConfirmation}
title={resetButtonLabel.toUpperCase()}
info={t("ageTypes.cancelUpdate")}
icon={warningIcon}
primaryButtonLabel={t("common.ok")}
secondaryButtonLabel={t("common.discard")}
handlePrimaryButtonClick={handleResetConfirmation}
handleSecondaryButtonClick={() => setOpenResetConfirmation(false)}
/>
{updateAgeTypes.status === "FAIL" && (
<div ref={infoBoxRef} className="info-box-container">
<InfoBox type="error" message={errorMessage} />
</div>
)}
{validationErrors.length > 0 && (
<div ref={infoBoxRef} className="info-box-container">
<InfoBox type="error" message={validationErrors.join("; ")} />
</div>
)}
<ConfirmationDialog
isOpen={!!updateAgeTypes.hasSucceeded}
title={t("ageTypes.updated")}
icon={checkIcon}
info={t("ageTypes.updateSuccess")}
primaryButtonLabel="Ok"
handlePrimaryButtonClick={() => {
navigate(PATHS.admin_age_types);
}}
handleSecondaryButtonClick={() => ({})}
/>
</form>
</div>
);
};

export default AgeTypesForm;
Loading

0 comments on commit 57cd95a

Please sign in to comment.