Files
app/src/admin/ui/Field.tsx
BOHA 519edce373 fix: 2026-06-09 full-codebase audit hardening
Validation: shared NaN-guarded Zod coercion helpers in schemas/common.ts replace
the raw number|string transform idiom across every schema (the root-cause NaN bug
class); emailOrEmpty + lenient isoDateString/timeString.

Security: roles privilege-escalation closed; refresh-token family revocation on
reuse; TOTP uses config params; read endpoints permission-guarded; received-invoices
gross VAT on all paths; orders-pdf custom-items authz.

Concurrency: $queryRaw SELECT...FOR UPDATE locks in ascending-id order (warehouse
confirm/cancel, attendance lockUserRow); uniqueness checks moved into create
transactions (TOCTOU -> 409); deterministic id tiebreak on second-precision
timestamp ordering (plan resolveCell/resolveGrid, warehouse FIFO).

Frontend: Rules-of-Hooks fixed across ~14 pages + PlanCellModal; UTC-date persisted
fields; dashboard invalidation gaps; stale-closure confirm bugs.

Tooling/tests: ESLint flat config (react-hooks/rules-of-hooks = error) + Prettier;
tsconfig.test.json so tsc -b type-checks the tests; removed 3 dead deps; npm audit
fix (8 -> 3). Suite 195 -> 247 (happy-path auth, FIFO oldest-first, flakiness fixes),
isolated on app_test via .env.test with a hard-throw setup guard.

Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:45:26 +02:00

120 lines
3.0 KiB
TypeScript

import {
cloneElement,
isValidElement,
useId,
type ReactElement,
type ReactNode,
} from "react";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
/** Labeled field wrapper: label (+required) + the input + an error/hint line. */
export function Field({
label,
required,
error,
hint,
children,
}: {
label: string;
required?: boolean;
error?: string;
hint?: string;
children: ReactNode;
}) {
// Associate the <label> with the control. Generate a stable id, render it via
// `htmlFor`, and inject the same id onto the single input child so click-to-
// focus works and screen readers announce the label. Wire error/hint via
// aria-describedby + aria-invalid for assistive tech. We only inject onto a
// valid element that hasn't already set `id`, so explicit caller ids win.
const reactId = useId();
const generatedId = `field-${reactId}`;
// If the child already carries an id, keep it as the htmlFor target so the
// label still points at the real input; otherwise inject the generated one.
const childId = isValidElement(children)
? (children.props as { id?: string }).id
: undefined;
const inputId = childId ?? generatedId;
const describedById = error
? `${inputId}-error`
: hint
? `${inputId}-hint`
: undefined;
let control = children;
if (isValidElement(children)) {
const extra: Record<string, unknown> = {};
if (childId == null) extra.id = inputId;
if (describedById) extra["aria-describedby"] = describedById;
if (error) extra["aria-invalid"] = true;
if (Object.keys(extra).length > 0) {
control = cloneElement(
children as ReactElement<Record<string, unknown>>,
extra,
);
}
}
return (
<Box sx={{ mb: 2 }}>
<Typography
component="label"
htmlFor={inputId}
variant="body2"
sx={{
display: "block",
mb: 0.5,
fontWeight: 600,
color: "text.secondary",
}}
>
{label}
{required && (
<Box component="span" sx={{ color: "error.main", ml: 0.25 }}>
*
</Box>
)}
</Typography>
{control}
{error ? (
<Typography id={describedById} variant="caption" color="error">
{error}
</Typography>
) : hint ? (
<Typography id={describedById} variant="caption" color="text.secondary">
{hint}
</Typography>
) : null}
</Box>
);
}
/** Boolean switch with a label. */
export function SwitchField({
label,
checked,
onChange,
disabled,
}: {
label: string;
checked: boolean;
onChange: (v: boolean) => void;
disabled?: boolean;
}) {
return (
<FormControlLabel
control={
<Switch
checked={checked}
disabled={disabled}
onChange={(e) => onChange(e.target.checked)}
/>
}
label={label}
disabled={disabled}
/>
);
}