"use client"; import { ChoiceGroup } from "@/components/form/choice-group"; import { NumberStepper } from "@/components/form/number-stepper"; import { LEVEL_TOLERANCE } from "@/lib/hive/types"; import type { CatalogPartType, ChoiceField, LevelField, StateField } from "@/lib/hive/types"; const numberFormat = new Intl.NumberFormat("de-DE", { maximumFractionDigits: 3 }); /** Digits after the decimal point of a step: 0.5 -> 1, 10 -> 0. */ export function decimalsOf(step: number): number { const text = String(step); const dot = text.indexOf("."); return dot < 0 ? 0 : text.length - dot - 1; } /** Draft text per state field key ("2.5" for levels, the option for choices). */ export type FieldDraft = Record; export function draftOf(type: CatalogPartType, state: Record): FieldDraft { const draft: FieldDraft = {}; for (const field of type.stateSchema) { const value = state[field.key]; draft[field.key] = value === undefined ? defaultText(field) : String(value); } return draft; } function defaultText(field: StateField): string { return field.kind === "level" ? "0" : (field.options[0] ?? ""); } /** Empty, out of 0..max or off the step grid: the value must not be saved. */ export function isLevelValid(raw: string, field: LevelField): boolean { return levelProblem(raw, field) === null && !isOffGrid(raw, field); } function isOffGrid(raw: string, field: LevelField): boolean { const position = Number(raw) / field.step; return Math.abs(position - Math.round(position)) > LEVEL_TOLERANCE; } /** Message for an empty or out-of-range level; the stepper words the grid error itself. */ export function levelProblem(raw: string, field: LevelField): string | null { const value = raw === "" ? Number.NaN : Number(raw); if (Number.isNaN(value)) return "Bitte einen Wert angeben."; if (value < 0 || value > field.max) return `Nur Werte von 0 bis ${numberFormat.format(field.max)}.`; return null; } export function areDraftFieldsValid(type: CatalogPartType, draft: FieldDraft): boolean { return type.stateSchema.every((field) => field.kind === "choice" || isLevelValid(draft[field.key] ?? "", field)); } /** The value as the API takes it: a number for levels, the option for choices. */ export function draftValue(field: StateField, raw: string): number | string { return field.kind === "level" ? Number(raw) : raw; } export function isFieldChanged(field: StateField, raw: string, current: number | string | undefined): boolean { if (field.kind === "choice") return raw !== String(current ?? defaultText(field)); return Math.abs(Number(raw) - Number(current ?? 0)) > LEVEL_TOLERANCE; } interface StateFieldControlProps { field: StateField; idPrefix: string; value: string; onChange: (value: string) => void; } function LevelControl({ field, idPrefix, value, onChange }: StateFieldControlProps & { field: LevelField }) { const id = `${idPrefix}-${field.key}`; const problem = levelProblem(value, field); return (
{problem ? (

{problem}

) : null}
); } function ChoiceControl({ field, idPrefix, value, onChange }: StateFieldControlProps & { field: ChoiceField }) { const id = `${idPrefix}-${field.key}`; const options = field.options.map((option, index) => ({ value: option, label: field.optionLabels[index] ?? option, })); return (
{field.label}
); } /** One control for one `stateSchema` field: a stepper for levels, a choice group for choices. */ export function StateFieldControl(props: StateFieldControlProps) { const { field } = props; return field.kind === "level" ? ( ) : ( ); }