"use client"; import { useState } from "react"; import type { FieldConfig, FieldHistoryEntry, SelectOption } from "@/types/form"; const currencyFmt = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 2, }); const numberFmt = new Intl.NumberFormat("en-US"); export function FormattedValue({ value, field, options, }: { value: unknown; field: FieldConfig; options: Record; }) { if (value === null || value === undefined || value === "") return <>โ€”; const opts: SelectOption[] | undefined = field.optionGroupId ? options[field.optionGroupId] : field.options; switch (field.type) { case "currency": { const n = typeof value === "number" ? value : Number(value); return <>{Number.isFinite(n) ? currencyFmt.format(n) : String(value)}; } case "percent": { const n = typeof value === "number" ? value : Number(value); return <>{Number.isFinite(n) ? `${n}%` : String(value)}; } case "number": { const n = typeof value === "number" ? value : Number(value); return <>{Number.isFinite(n) ? numberFmt.format(n) : String(value)}; } case "date": return <>{formatLongDate(String(value))}; case "boolean": return <>{value ? "Yes" : "No"}; case "select": case "readonly": { const v = String(value); const found = opts?.find((o) => o.value === v); return <>{found?.label ?? v}; } case "multiselect": { let parts: string[]; if (Array.isArray(value)) { parts = value.map(String); } else { parts = String(value).split(/[|,]/).map((s) => s.trim()).filter(Boolean); } const labels = parts.map((p) => opts?.find((o) => o.value === p)?.label ?? p); return <>{labels.join(", ")}; } case "file": { // Prefill / history wraps file values into { id, file_name } so the // UI can show a human-readable name. Fall back to whatever scalar // came through if the shape is different. if (typeof value === "object" && value !== null) { const o = value as Record; const fname = o.file_name ?? o.name ?? o.label ?? o.filename; if (typeof fname === "string" && fname.length > 0) return <>{fname}; if (typeof o.id !== "undefined") return <>Attachment #{String(o.id)}; } return <>{String(value)}; } case "textarea": case "text": case "email": case "phone": default: return <>{String(value)}; } } export function formatShortDate(iso: string): string { if (!iso) return "โ€”"; const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso; return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); } export function formatLongDate(iso: string): string { if (!iso) return "โ€”"; const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso); const d = m ? new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])) : new Date(iso); if (Number.isNaN(d.getTime())) return iso; return d.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" }); } export function computeDateRange(dates: string[]): { from: string; to: string } | null { if (dates.length === 0) return null; const times = dates .map((d) => new Date(d).getTime()) .filter((t) => Number.isFinite(t)); if (times.length === 0) return null; const min = new Date(Math.min(...times)).toISOString(); const max = new Date(Math.max(...times)).toISOString(); return { from: min, to: max }; } export function Chevron({ open }: { open: boolean }) { return ( ); } export function FieldHistoryRow({ field, entries, options, }: { field: FieldConfig; entries: FieldHistoryEntry[]; options: Record; }) { const [expanded, setExpanded] = useState(false); const latest = entries[0]; const priorEntries = entries.slice(1); return (

{field.label}

{field.help && (

{field.help}

)}

as of {formatShortDate(latest.date)} {priorEntries.length > 0 && ( <> {" ยท "} )}

{expanded && priorEntries.length > 0 && (
    {priorEntries.map((e) => (
  1. {formatShortDate(e.date)}
  2. ))}
)}
); } export function FieldHistoryGroup({ label, fields, history, options, }: { label?: string; fields: FieldConfig[]; history: Record; options: Record; }) { return (
{label && (

{label}

)}
{fields.map((f) => ( ))}
); } export { currencyFmt, numberFmt };