// Vrai-Prix — méthode du coût : primitives de formulaire de l'atelier (champs éditoriaux, parts en %, blocs). "use client"; import type { AttributeSource } from "@/lib/cost/types"; import { AttrBadge } from "./Provenance"; export function Block({ num, title, children, id, aside }: { num: string; title: string; children: React.ReactNode; id?: string; aside?: React.ReactNode }) { return (

{num} — {title}

{aside}
{children}
); } export function Field({ label, children, hint, source, fr }: { label: string; children: React.ReactNode; hint?: string; source?: AttributeSource | null; fr?: boolean }) { return ( ); } export function NumField({ value, onChange, min, max, step, unit, placeholder, disabled }: { value: number | null | undefined; onChange: (v: number | null) => void; min?: number; max?: number; step?: number; unit?: string; placeholder?: string; disabled?: boolean }) { return (
onChange(e.target.value === "" ? null : Number(e.target.value))} className="vp-input vp-mono text-[14px]" /> {unit && {unit}}
); } export function SelectField({ value, onChange, options }: { value: T; onChange: (v: T) => void; options: { key: T; label: string }[] }) { return ( ); } export function Toggle({ checked, onChange, label }: { checked: boolean; onChange: (v: boolean) => void; label: string }) { return ( ); } /** Parts en % d'un mélange (revêtements, planchers) — la somme est normalisée par le moteur. */ export function MixField({ value, onChange, options, fr }: { value: Partial>; onChange: (v: Partial>) => void; options: { key: K; fr: string; en: string }[]; fr: boolean }) { const total = Object.values(value as Record).reduce((s, v) => s + (v || 0), 0); return (
{options.map((o) => ( ))}

0.02 && total > 0 ? "text-[var(--amber)]" : "text-ink-3"}`}> {fr ? "Total" : "Total"} {Math.round(total * 100)} % {total > 0 && Math.abs(total - 1) > 0.02 ? (fr ? "— normalisé à 100 % par le moteur" : "— normalised to 100 % by the engine") : ""}

); } export function PctField({ value, onChange, label, note, amount, fr, lang }: { value: number; onChange: (v: number) => void; label: string; note?: string; amount?: number; fr: boolean; lang: string }) { return (
{label}{note && ⓘ} onChange(Number(e.target.value) || 0)} className="vp-input vp-mono !min-h-0 !py-1 text-right text-[13px]" />% {amount != null ? new Intl.NumberFormat(lang === "fr" ? "fr-CA" : "en-CA", { style: "currency", currency: "CAD", maximumFractionDigits: 0 }).format(amount) : (fr ? "—" : "—")}
); }