Skip to content

Commit

Permalink
add errors and warnings to CandidateVotesForm
Browse files Browse the repository at this point in the history
  • Loading branch information
lkleuver committed Jul 12, 2024
1 parent 41c9621 commit d125713
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import {
CandidateVotes,
PoliticalGroup,
PoliticalGroupVotes,
useErrorsAndWarnings,
usePoliticalGroup,
} from "@kiesraad/api";
import { BottomBar, Button, Feedback, InputGrid } from "@kiesraad/ui";
import { BottomBar, Button, Feedback, InputGrid, InputGridRow } from "@kiesraad/ui";
import { usePositiveNumberInputMask, usePreventFormEnterSubmit } from "@kiesraad/util";

interface FormElements extends HTMLFormControlsCollection {
listtotal: HTMLInputElement;
total: HTMLInputElement;
"candidatevotes[]": HTMLInputElement[];
}

Expand All @@ -23,8 +24,10 @@ export interface CandidatesVotesFormProps {
group: PoliticalGroup;
}

//political_group_votes[1].candidate_votes[1].votes

export function CandidatesVotesForm({ group }: CandidatesVotesFormProps) {
const { register, format, deformat } = usePositiveNumberInputMask();
const { register, format, deformat, warnings: inputMaskWarnings } = usePositiveNumberInputMask();
const formRef = React.useRef<CandidatesVotesFormElement>(null);
const {
sectionValues,
Expand All @@ -37,6 +40,8 @@ export function CandidatesVotesForm({ group }: CandidatesVotesFormProps) {
setTemporaryCache,
} = usePoliticalGroup(group.number);

console.log(errors);

usePreventFormEnterSubmit(formRef);

const getValues = React.useCallback(
Expand All @@ -50,13 +55,16 @@ export function CandidatesVotesForm({ group }: CandidatesVotesFormProps) {
}
return {
number: group.number,
total: deformat(elements.listtotal.value),
total: deformat(elements.total.value),
candidate_votes: candidate_votes,
};
},
[deformat, group],
);

const errorsAndWarnings = useErrorsAndWarnings(errors, warnings, inputMaskWarnings);

console.log(errorsAndWarnings);
//const blocker = useBlocker() use const blocker to render confirmation UI.
useBlocker(() => {
if (formRef.current && !isCalled) {
Expand Down Expand Up @@ -127,42 +135,32 @@ export function CandidatesVotesForm({ group }: CandidatesVotesFormProps) {
const addSeparator = (index + 1) % 25 == 0;
const defaultValue = sectionValues?.candidate_votes[index]?.votes || "";
return (
<InputGrid.Row
isFocused={index === 0}
addSeparator={addSeparator}
<InputGridRow
key={`list${group.number}-candidate${index + 1}`}
>
<td>{index + 1}</td>
<td>
<input
id={`candidate-votes-${candidate.number}`}
name="candidatevotes[]"
maxLength={11}
{...register()}
/* eslint-disable-next-line jsx-a11y/no-autofocus */
autoFocus={index === 0}
defaultValue={format(defaultValue)}
/>
</td>
<td>
{candidate.last_name}, {candidate.initials} ({candidate.first_name})
</td>
</InputGrid.Row>
field={`${index + 1}`}
name="candidatevotes[]"
id={`candidate_votes-${candidate.number}.votes`}
title={`${candidate.last_name}, ${candidate.initials} (${candidate.first_name})`}
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
format={format}
addSeparator={addSeparator}
defaultValue={defaultValue}
/>
);
})}
<InputGrid.Total key={`list${group.number}-total`}>
<td></td>
<td>
<input
id={`list-total`}
name="listtotal"
maxLength={11}
{...register()}
defaultValue={format(sectionValues?.total || "")}
/>
</td>
<td>Totaal lijst {group.number}</td>
</InputGrid.Total>
<InputGridRow
key={`list${group.number}-total`}
field={``}
name="total"
id={`total`}
title={`Totaal lijst ${group.number}`}
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
format={format}
defaultValue={format(sectionValues?.total || "")}
isTotal
/>
</InputGrid.Body>
</InputGrid>
<BottomBar type="inputgrid">
Expand Down
103 changes: 48 additions & 55 deletions frontend/app/component/form/voters_and_votes/VotersAndVotesForm.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import * as React from "react";

import {
ValidationResult,
ErrorsAndWarnings,
useVotersAndVotes,
VotersAndVotesValues,
} from "@kiesraad/api";
import { useVotersAndVotes, VotersAndVotesValues, useErrorsAndWarnings } from "@kiesraad/api";
import { Button, InputGrid, Feedback, BottomBar, InputGridRow, useTooltip } from "@kiesraad/ui";
import {
usePositiveNumberInputMask,
usePreventFormEnterSubmit,
fieldNameFromPath,
} from "@kiesraad/util";
import { usePositiveNumberInputMask, usePreventFormEnterSubmit } from "@kiesraad/util";
import { useBlocker } from "react-router-dom";

interface FormElements extends HTMLFormControlsCollection {
Expand Down Expand Up @@ -93,46 +84,48 @@ export function VotersAndVotesForm() {
return false;
});

const errorsAndWarnings: Map<string, ErrorsAndWarnings> = React.useMemo(() => {
const result = new Map<string, ErrorsAndWarnings>();
const errorsAndWarnings = useErrorsAndWarnings(errors, warnings, inputMaskWarnings);

const process = (target: keyof ErrorsAndWarnings, arr: ValidationResult[]) => {
arr.forEach((v) => {
v.fields.forEach((f) => {
const fieldName = fieldNameFromPath(f);
if (!result.has(fieldName)) {
result.set(fieldName, { errors: [], warnings: [] });
}
const field = result.get(fieldName);
if (field) {
field[target].push({
code: v.code,
id: fieldName,
});
}
});
});
};
// const errorsAndWarnings: Map<string, ErrorsAndWarnings> = React.useMemo(() => {
// const result = new Map<string, ErrorsAndWarnings>();

if (errors.length > 0) {
process("errors", errors);
}
if (warnings.length > 0) {
process("warnings", warnings);
}
// const process = (target: keyof ErrorsAndWarnings, arr: ValidationResult[]) => {
// arr.forEach((v) => {
// v.fields.forEach((f) => {
// const fieldName = fieldNameFromPath(f);
// if (!result.has(fieldName)) {
// result.set(fieldName, { errors: [], warnings: [] });
// }
// const field = result.get(fieldName);
// if (field) {
// field[target].push({
// code: v.code,
// id: fieldName,
// });
// }
// });
// });
// };

// if (errors.length > 0) {
// process("errors", errors);
// }
// if (warnings.length > 0) {
// process("warnings", warnings);
// }

inputMaskWarnings.forEach((warning) => {
if (!result.has(warning.id)) {
result.set(warning.id, { errors: [], warnings: [] });
}
const field = result.get(warning.id);
if (field) {
field.warnings.push(warning);
}
});
// inputMaskWarnings.forEach((warning) => {
// if (!result.has(warning.id)) {
// result.set(warning.id, { errors: [], warnings: [] });
// }
// const field = result.get(warning.id);
// if (field) {
// field.warnings.push(warning);
// }
// });

return result;
}, [errors, warnings, inputMaskWarnings]);
// return result;
// }, [errors, warnings, inputMaskWarnings]);

React.useEffect(() => {
if (isCalled) {
Expand Down Expand Up @@ -189,7 +182,7 @@ export function VotersAndVotesForm() {
<InputGridRow
key="A"
field="A"
name="poll_card_count"
id="poll_card_count"
title="Stempassen"
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
Expand All @@ -200,7 +193,7 @@ export function VotersAndVotesForm() {
<InputGridRow
key="B"
field="B"
name="proxy_certificate_count"
id="proxy_certificate_count"
title="Volmachtbewijzen"
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
Expand All @@ -210,7 +203,7 @@ export function VotersAndVotesForm() {
<InputGridRow
key="C"
field="C"
name="voter_card_count"
id="voter_card_count"
title="Kiezerspassen"
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
Expand All @@ -220,7 +213,7 @@ export function VotersAndVotesForm() {
<InputGridRow
key="D"
field="D"
name="total_admitted_voters_count"
id="total_admitted_voters_count"
title="Totaal toegelaten kiezers"
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
Expand All @@ -233,7 +226,7 @@ export function VotersAndVotesForm() {
<InputGridRow
key="E"
field="E"
name="votes_candidates_counts"
id="votes_candidates_counts"
title="Stemmen op kandidaten"
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
Expand All @@ -243,7 +236,7 @@ export function VotersAndVotesForm() {
<InputGridRow
key="F"
field="F"
name="blank_votes_count"
id="blank_votes_count"
title="Blanco stemmen"
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
Expand All @@ -253,7 +246,7 @@ export function VotersAndVotesForm() {
<InputGridRow
key="G"
field="G"
name="invalid_votes_count"
id="invalid_votes_count"
title="Ongeldige stemmen"
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
Expand All @@ -263,7 +256,7 @@ export function VotersAndVotesForm() {
<InputGridRow
key="H"
field="H"
name="total_votes_cast_count"
id="total_votes_cast_count"
title="Totaal uitgebrachte stemmen"
errorsAndWarnings={errorsAndWarnings}
inputProps={register()}
Expand Down
19 changes: 16 additions & 3 deletions frontend/app/test/unit/test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { ReactElement } from "react";
import { Providers } from "./Providers";

import { render, RenderOptions } from "@testing-library/react";
import { render, RenderOptions, RenderResult, fireEvent } from "@testing-library/react";

const customRender = (ui: ReactElement, options?: Omit<RenderOptions, "wrapper">) =>
render(ui, { wrapper: Providers, ...options });
const customRender = (ui: ReactElement, options?: Omit<RenderOptions, "wrapper">) => {
const result: RenderResult = render(ui, { wrapper: Providers, ...options });

const fillFormValues = (values: Record<string, string | number>) => {
Object.keys(values).forEach((key) => {
const input = result.getByTestId(key);
fireEvent.change(input, { target: { value: values[key] } });
});
};

return {
...result,
fillFormValues,
};
};

export * from "@testing-library/react";
export { customRender as render };
Expand Down
1 change: 1 addition & 0 deletions frontend/lib/api/form/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./pollingstation";
export * from "./useErrorsAndWarnings";
53 changes: 53 additions & 0 deletions frontend/lib/api/form/useErrorsAndWarnings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from "react";
import { ErrorsAndWarnings, FieldValidationResult } from "../api";
import { ValidationResult } from "../gen/openapi";
import { fieldNameFromPath } from "@kiesraad/util";

export function useErrorsAndWarnings(
errors: ValidationResult[],
warnings: ValidationResult[],
clientWarnings: FieldValidationResult[],
) {
const errorsAndWarnings: Map<string, ErrorsAndWarnings> = React.useMemo(() => {
const result = new Map<string, ErrorsAndWarnings>();

const process = (target: keyof ErrorsAndWarnings, arr: ValidationResult[]) => {
arr.forEach((v) => {
v.fields.forEach((f) => {
const fieldName = fieldNameFromPath(f);
if (!result.has(fieldName)) {
result.set(fieldName, { errors: [], warnings: [] });
}
const field = result.get(fieldName);
if (field) {
field[target].push({
code: v.code,
id: fieldName,
});
}
});
});
};

if (errors.length > 0) {
process("errors", errors);
}
if (warnings.length > 0) {
process("warnings", warnings);
}

clientWarnings.forEach((warning) => {
if (!result.has(warning.id)) {
result.set(warning.id, { errors: [], warnings: [] });
}
const field = result.get(warning.id);
if (field) {
field.warnings.push(warning);
}
});

return result;
}, [errors, warnings, clientWarnings]);

return errorsAndWarnings;
}
Loading

0 comments on commit d125713

Please sign in to comment.