Skip to content

Commit

Permalink
feat(PackagingList) : handle validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
benoitguigal committed Feb 10, 2025
1 parent 02feef9 commit 78af58f
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 225 deletions.
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
import Select from "@codegouvfr/react-dsfr/Select";
import Select, { SelectProps } from "@codegouvfr/react-dsfr/Select";
import { PackagingInfoInput, Packagings } from "@td/codegen-ui";
import React from "react";
import NonScrollableInput from "../../../../Apps/common/Components/NonScrollableInput/NonScrollableInput";
import Input from "@codegouvfr/react-dsfr/Input";
import { numberToString } from "../../../../Apps/Dashboard/Creation/bspaoh/utils/numbers";
import TagsInput from "../../../../Apps/Forms/Components/TagsInput/TagsInput";
import NonScrollableInput from "../../../common/Components/NonScrollableInput/NonScrollableInput";
import Input, { InputProps } from "@codegouvfr/react-dsfr/Input";
import { numberToString } from "../../../Dashboard/Creation/bspaoh/utils/numbers";
import TagsInput from "../TagsInput/TagsInput";
import { pluralize } from "@td/constants";
import Decimal from "decimal.js";

type PackagingFormProps = {
packaging: PackagingInfoInput;
setPackaging: (packaging: PackagingInfoInput) => void;
inputProps: {
type: SelectProps["nativeSelectProps"];
volume: InputProps["nativeInputProps"];
quantity: InputProps["nativeInputProps"];
other: InputProps["nativeInputProps"];
identificationNumbers: {
push: (v: string) => void;
remove: (index: number) => void;
};
};
packagingTypeOptions: { value: Packagings; label: string }[];
disabled?: boolean;
errors?: Partial<Record<keyof PackagingInfoInput, string>>;
touched?: Partial<Record<keyof PackagingInfoInput, boolean>>;
};

function PackagingForm({
packaging,
setPackaging,
inputProps,
packagingTypeOptions,
disabled = false
disabled = false,
errors,
touched
}: PackagingFormProps) {
const maxQuantity =
packaging.type === Packagings.Citerne || packaging.type === Packagings.Benne
? 2
: null;

const quantityError =
maxQuantity && packaging.quantity > maxQuantity
? `Impossible de saisir plus de 2 ${packaging.type.toLocaleLowerCase()}s`
: null;

const volumeUnit = packaging.type === Packagings.Benne ? "m3" : "litres";
const packagingVolume =
packaging.type === Packagings.Benne && packaging.volume
Expand All @@ -48,17 +56,9 @@ function PackagingForm({
<Select
label="Type"
disabled={disabled}
nativeSelectProps={{
value: packaging.type,
onChange: event => {
const packagingType = event.target.value as Packagings;
setPackaging({
...packaging,
type: packagingType,
other: packagingType === Packagings.Autre ? "" : null
});
}
}}
state={errors?.type && touched?.type ? "error" : "default"}
stateRelatedMessage={errors?.type}
nativeSelectProps={inputProps.type}
className="fr-mb-2w"
>
<option value="">...</option>
Expand All @@ -81,27 +81,14 @@ function PackagingForm({
label={`Volume en ${volumeUnit} (optionnel)`}
className="fr-mb-2w"
disabled={disabled}
state={errors?.volume && touched?.volume ? "error" : "default"}
stateRelatedMessage={errors?.volume}
nativeInputProps={{
type: "number",
inputMode: "decimal",
step: "0.001", // mili-litres
value: packagingVolume ?? "",
onChange: event => {
const volume = () => {
const v = event.target.value;
if (v === "") return v;
if (packaging.type === Packagings.Benne) {
// le volume doit être passé en litres à l'API
return new Decimal(v).times(1000).toNumber();
}
return Number(v);
};

setPackaging({
...packaging,
volume: volume() as number
});
}
...inputProps.volume
}}
/>

Expand All @@ -115,23 +102,15 @@ function PackagingForm({
<NonScrollableInput
label="Nombre"
className="fr-mb-2w"
state={quantityError ? "error" : "default"}
stateRelatedMessage={quantityError}
state={errors?.quantity && touched?.quantity ? "error" : "default"}
stateRelatedMessage={errors?.quantity}
disabled={disabled}
nativeInputProps={{
type: "number",
inputMode: "numeric",
step: "1", // mili-litres
...(maxQuantity ? { max: maxQuantity } : {}),
value: packaging.quantity,
onChange: event => {
const quantity = event.target.value;
setPackaging({
...packaging,
quantity:
quantity === "" ? (quantity as any) : Number(quantity)
});
}
...inputProps.quantity
}}
/>
</div>
Expand All @@ -142,11 +121,9 @@ function PackagingForm({
<Input
label="Nom du type de contenant"
disabled={disabled}
nativeInputProps={{
value: packaging.other ?? "",
onChange: event =>
setPackaging({ ...packaging, other: event.target.value })
}}
state={errors?.other && touched?.other ? "error" : "default"}
stateRelatedMessage={errors?.other}
nativeInputProps={inputProps.other}
/>
</div>
</div>
Expand All @@ -157,23 +134,8 @@ function PackagingForm({
label="N° de contenant (optionnel)"
tags={packaging.identificationNumbers ?? []}
disabled={disabled}
onAddTag={tag =>
setPackaging({
...packaging,
identificationNumbers: [
...(packaging.identificationNumbers ?? []),
tag
]
})
}
onDeleteTag={idx =>
setPackaging({
...packaging,
identificationNumbers: packaging.identificationNumbers?.filter(
(_, index) => index !== idx
)
})
}
onAddTag={tag => inputProps.identificationNumbers.push(tag)}
onDeleteTag={idx => inputProps.identificationNumbers.remove(idx)}
/>

<p className="fr-info-text">
Expand Down
123 changes: 123 additions & 0 deletions front/src/Apps/Forms/Components/PackagingList/PackagingList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { useMemo } from "react";
import { PackagingInfoInput, Packagings } from "@td/codegen-ui";
import { FieldArray, FormikErrors, useField, useFormikContext } from "formik";
import PackagingForm from "./PackagingForm";

type PackagingListProps = {
fieldName: string;
disabled?: boolean;
};

const packagingTypeOptions = [
{ value: Packagings.Benne, label: "Benne" },
{ value: Packagings.Citerne, label: "Citerne" },
{ value: Packagings.Fut, label: "Fût" },
{ value: Packagings.Grv, label: "Grand Récipient Vrac (GRV)" },
{ value: Packagings.Autre, label: "Autre" }
];

function PackagingList({ fieldName, disabled = false }: PackagingListProps) {
const [field, { error }] = useField<PackagingInfoInput[]>(fieldName);

const { getFieldProps, touched } = useFormikContext();

// Le type des erreurs et de touched n'est pas correctement inféré par Formik ici
// Peut-être en lien avec https://github.com/jaredpalmer/formik/issues/2347
const typedErrors = error as any as
| FormikErrors<PackagingInfoInput[]>
| undefined;

const packagings = field.value;

const options =
packagings.length > 1
? packagingTypeOptions.filter(
o => o.value !== Packagings.Citerne && o.value !== Packagings.Benne
)
: packagingTypeOptions;

const showAddButton = packagings.every(
p =>
p.type !== Packagings.Citerne &&
p.type !== Packagings.Benne &&
p.type !== Packagings.Pipeline
);

const packagingsTouched = useMemo(() => {
return fieldName.split(".").reduce((acc, path) => {
return acc?.[path];
}, touched);
}, [touched, fieldName]);

return (
<FieldArray
name={fieldName}
render={({ push: pushPackaging, remove: removePackaging }) => (
<>
{packagings
.filter(p => p.type !== Packagings.Pipeline)
.map((p, idx) => (
<FieldArray
name={`${fieldName}.${idx}.identificationNumbers`}
render={({
push: pushIdentificationNumber,
remove: removeIdentificationNumber
}) => (
<div key={idx}>
<PackagingForm
packaging={p}
inputProps={{
type: getFieldProps(`${fieldName}.${idx}.type`),
volume: getFieldProps(`${fieldName}.${idx}.volume`),
quantity: getFieldProps(`${fieldName}.${idx}.quantity`),
other: getFieldProps(`${fieldName}.${idx}.other`),
identificationNumbers: {
push: pushIdentificationNumber,
remove: removeIdentificationNumber
}
}}
packagingTypeOptions={options}
disabled={disabled}
errors={typedErrors?.[idx]}
touched={packagingsTouched?.[idx]}
/>
{packagings.length > 1 && (
<>
<button
type="button"
disabled={disabled}
className="fr-btn fr-btn--tertiary fr-mb-2w"
onClick={() => {
removePackaging(idx);
}}
>
Supprimer
</button>
<hr />
</>
)}
</div>
)}
/>
))}
{showAddButton && (
<div className="fr-grid-row fr-grid-row--right fr-mb-4w">
<button
type="button"
disabled={disabled}
className="fr-btn fr-btn--secondary"
onClick={() => {
pushPackaging({ type: "", quantity: "" });
}}
>
Ajouter un conditionnement
</button>
</div>
)}
</>
)}
/>
);
}

export default PackagingList;
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import NumberInput from "../../../../../form/common/components/custom-inputs/Num
import { IconPaperWrite } from "../../../../../Apps/common/Components/Icons/Icons";
import { getFormWasteDetailsADRMention } from "@td/constants";
import { isDefined } from "../../../../../common/helper";
import PackagingList from "../../../../../form/bsdd/components/packagings/PackagingList";
import PackagingList from "../../../../../Apps/Forms/Components/PackagingList/PackagingList";
import { emptyPackaging } from "../../../../../Apps/Forms/Components/PackagingList/helpers";

interface FormWasteEmissionSummaryProps {
form: Form;
Expand Down Expand Up @@ -64,7 +65,7 @@ const EDITABLE_FIELDS: Record<FormKeys, () => JSX.Element> = {
export function FormWasteEmissionSummary({
form
}: FormWasteEmissionSummaryProps) {
const { values } = useFormikContext<FormValues>();
const { values, setFieldValue } = useFormikContext<FormValues>();

const [fields, setFields] = React.useState<FormKeys[]>([]);
const addField = (name: FormKeys) =>
Expand Down Expand Up @@ -116,7 +117,10 @@ export function FormWasteEmissionSummary({

<button
type="button"
onClick={() => addField("packagingInfos")}
onClick={() => {
addField("packagingInfos");
setFieldValue("packagingInfos", [emptyPackaging]);
}}
className="tw-ml-2"
>
<IconPaperWrite color="blue" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import { IconPaperWrite } from "../../../../../Apps/common/Components/Icons/Icon
import { useEffect } from "react";
import { getTransportModeLabel } from "../../../../constants";
import { getFormWasteDetailsADRMention } from "@td/constants";
import PackagingList from "../../../../../form/bsdd/components/packagings/PackagingList";
import { emptyPackaging } from "../../../../../form/bsdd/components/packagings/helpers";
import PackagingList from "../../../../../Apps/Forms/Components/PackagingList/PackagingList";
import { emptyPackaging } from "../../../../../Apps/Forms/Components/PackagingList/helpers";

interface FormWasteTransportSummaryProps {
form: Form;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { TransporterForm } from "../../../../../Apps/Forms/Components/Transporte
import { useParams } from "react-router-dom";
import * as yup from "yup";
import { companySchema } from "../../../../../common/validation/schema";
import PackagingList from "../../../../../form/bsdd/components/packagings/PackagingList";
import PackagingList from "../../../../../Apps/Forms/Components/PackagingList/PackagingList";

const MARK_RESEALED = gql`
mutation MarkAsResealed($id: ID!, $resealedInfos: ResealedFormInput!) {
Expand Down
Loading

0 comments on commit 78af58f

Please sign in to comment.