Skip to content

Commit

Permalink
(fix)O3-3618: Required Field Message Alert for Labs is Not Clear and …
Browse files Browse the repository at this point in the history
…is Not Noticeable
  • Loading branch information
jwnasambu committed Jul 25, 2024
1 parent 7f0ce7c commit caa54ae
Show file tree
Hide file tree
Showing 3 changed files with 2,063 additions and 577 deletions.
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@
},
"dependencies": {
"@carbon/react": "^1.14.0",
"@hookform/resolvers": "^3.9.0",
"lodash-es": "^4.17.21",
"react-hook-form": "^7.49.3"
"react-hook-form": "^7.52.1",
"zod": "^3.23.8"
},
"peerDependencies": {
"@openmrs/esm-framework": "*",
Expand All @@ -55,7 +57,7 @@
"react-router-dom": "6.x"
},
"devDependencies": {
"@openmrs/esm-framework": "next",
"@openmrs/esm-framework": "5.7.2-pre.2101",
"@openmrs/esm-patient-common-lib": "next",
"@openmrs/esm-styleguide": "next",
"@swc/cli": "^0.1.62",
Expand Down Expand Up @@ -87,7 +89,7 @@
"jest-cli": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
"lint-staged": "^14.0.1",
"openmrs": "next",
"openmrs": "5.7.2-pre.2101",
"prettier": "^2.8.8",
"pretty-quick": "^3.1.3",
"raw-loader": "^4.0.2",
Expand Down
236 changes: 140 additions & 96 deletions src/results/result-form-field.component.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,148 @@
import React from "react";
import styles from "./result-form.scss";
import { TextInput, Select, SelectItem } from "@carbon/react";
import { useTranslation } from "react-i18next";
import { ConceptReference } from "./result-form.resource";
import { Controller } from "react-hook-form";
import { useTranslation, TFunction } from "react-i18next";
import { Controller, FieldErrors, Control, useForm } from "react-hook-form";
import { z, ZodSchema } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";

interface ResultFormFieldProps {
concept: ConceptReference;
control: any;
register: any;
errors: any;
control: Control<any>;
errors: FieldErrors;
}

type ConceptReference = {
uuid: string;
display: string;
datatype?: {
display: string;
};
setMembers?: ConceptReference[];
answers?: {
uuid: string;
display: string;
}[];
hiAbsolute?: number;
hiCritical?: number;
hiNormal?: number;
lowAbsolute?: number;
lowCritical?: number;
lowNormal?: number;
units?: string;
};

const createResultFormFieldSchema = (
concept: ConceptReference,
t: TFunction
): ZodSchema<any> => {
switch (concept.datatype?.display) {
case "Text":
return z.object({
[concept.uuid]: z
.string()
.min(1, t("fieldRequiredError", "This field is required")),
});
case "Numeric":
return z.object({
[concept.uuid]: z.coerce.number().refine((value) => !isNaN(value), {
message: t("numericFieldError", "Enter a valid number"),
}),
});
case "Coded":
return z.object({
[concept.uuid]: z
.string()
.min(1, t("fieldRequiredError", "This field is required")),
});
default:
return z.object({
[concept.uuid]: z.string(),
});
}
};

const isTextOrNumeric = (concept: ConceptReference) =>
concept.datatype?.display === "Text" ||
concept.datatype?.display === "Numeric";

const isCoded = (concept: ConceptReference) =>
concept.datatype?.display === "Coded";

const isPanel = (concept: ConceptReference) => concept.setMembers?.length > 0;

const printValueRange = (concept: ConceptReference) => {
if (concept?.datatype?.display === "Numeric") {
const maxVal = Math.max(
concept?.hiAbsolute ?? -Infinity,
concept?.hiCritical ?? -Infinity,
concept?.hiNormal ?? -Infinity
);
const minVal = Math.min(
concept?.lowAbsolute ?? Number.MAX_SAFE_INTEGER,
concept?.lowCritical ?? Number.MAX_SAFE_INTEGER,
concept?.lowNormal ?? Number.MAX_SAFE_INTEGER
);
return `${minVal === Number.MAX_SAFE_INTEGER ? 0 : minVal} - ${
maxVal === -Infinity ? "--" : maxVal
} ${concept?.units ?? ""}`;
}
return "";
};

const CodedAnswerSelect: React.FC<{
answers: ConceptReference["answers"];
field: any;
t: TFunction;
}> = ({ answers, field, t }) => (
<Select
key={field.name}
className={styles.textInput}
{...field}
type="text"
labelText={field.label}
>
<SelectItem text={t("chooseOption", "Choose an Option")} value="" />
{answers?.map((answer) => (
<SelectItem key={answer.uuid} text={answer.display} value={answer.uuid}>
{answer.display}
</SelectItem>
))}
</Select>
);

const ResultFormField: React.FC<ResultFormFieldProps> = ({
concept,
control,
errors,
}) => {
const { t } = useTranslation();
const isTextOrNumeric = (concept) =>
concept.datatype?.display === "Text" ||
concept.datatype?.display === "Numeric";
const isCoded = (concept) => concept.datatype?.display === "Coded";
const isPanel = (concept) => concept.setMembers?.length > 0;

const printValueRange = (concept: ConceptReference) => {
if (concept?.datatype?.display === "Numeric") {
const maxVal = Math.max(
concept?.hiAbsolute,
concept?.hiCritical,
concept?.hiNormal
);
const minVal = Math.min(
concept?.lowAbsolute,
concept?.lowCritical,
concept?.lowNormal
);
return ` (${minVal ?? 0} - ${maxVal > 0 ? maxVal : "--"} ${
concept?.units ?? ""
})`;
}
return "";
};

const schema = createResultFormFieldSchema(concept, t);

const {
handleSubmit,
control: formControl,
formState: { errors: formErrors },
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
[concept.uuid]: "",
},
});

return (
<>
{Object.keys(errors).length > 0 && (
{Object.keys(formErrors).length > 0 && (
<div className={styles.errorDiv}>
{t("allFieldsRequired", "All fields are required")}
</div>
)}
{isTextOrNumeric(concept) && (
<Controller
control={control}
rules={{
required: true,
}}
control={formControl}
name={concept.uuid}
rules={{ required: true }}
render={({ field }) => (
<TextInput
key={concept.uuid}
Expand All @@ -74,51 +161,26 @@ const ResultFormField: React.FC<ResultFormFieldProps> = ({
/>
)}

{isCoded(concept) && (
{isCoded(concept) && concept.answers?.length > 0 && (
<Controller
name={concept.uuid}
control={control}
rules={{
required: true,
}}
control={formControl}
rules={{ required: true }}
render={({ field }) => (
<Select
key={concept.uuid}
className={styles.textInput}
{...field}
type="text"
labelText={concept?.display}
rules={{ required: true }}
>
<SelectItem
text={t("chooseOption", "Choose an Option")}
value=""
/>

{concept?.answers?.map((answer) => (
<SelectItem
key={answer.uuid}
text={answer.display}
value={answer.uuid}
>
{answer.display}
</SelectItem>
))}
</Select>
<CodedAnswerSelect answers={concept.answers} field={field} t={t} />
)}
/>
)}

{isPanel(concept) &&
concept.setMembers.map((member, index) => {
concept.setMembers?.map((member, index) => {
if (isTextOrNumeric(member)) {
return (
<Controller
control={control}
key={member.uuid}
control={formControl}
name={member.uuid}
rules={{
required: true,
}}
rules={{ required: true }}
render={({ field }) => (
<TextInput
key={member.uuid}
Expand All @@ -140,42 +202,24 @@ const ResultFormField: React.FC<ResultFormFieldProps> = ({
);
}

if (isCoded(member)) {
if (isCoded(member) && member.answers?.length > 0) {
return (
<Controller
key={member.uuid}
name={member.uuid}
control={control}
rules={{
required: true,
}}
control={formControl}
rules={{ required: true }}
render={({ field }) => (
<Select
key={member.uuid}
className={styles.textInput}
{...field}
type="text"
labelText={member?.display}
autoFocus={index === 0}
>
<SelectItem
text={t("option", "Choose an Option")}
value=""
/>

{member?.answers?.map((answer) => (
<SelectItem
key={answer.uuid}
text={answer.display}
value={answer.uuid}
>
{answer.display}
</SelectItem>
))}
</Select>
<CodedAnswerSelect
answers={member.answers}
field={field}
t={t}
/>
)}
/>
);
}
return null;
})}
</>
);
Expand Down
Loading

0 comments on commit caa54ae

Please sign in to comment.