From fd97d67080d5684f9620370607795fb3cbb41c2d Mon Sep 17 00:00:00 2001 From: makearmy Date: Sun, 5 Oct 2025 08:50:01 -0400 Subject: [PATCH] form fixes --- components/forms/SettingsSubmit.tsx | 395 ++++++++++------------------ 1 file changed, 141 insertions(+), 254 deletions(-) diff --git a/components/forms/SettingsSubmit.tsx b/components/forms/SettingsSubmit.tsx index 0867a368..d611d547 100644 --- a/components/forms/SettingsSubmit.tsx +++ b/components/forms/SettingsSubmit.tsx @@ -93,10 +93,10 @@ type EditInitialValues = { laser_soft?: any; repeat_all?: number | null; - // may exist on CO2 targets - lens_conf?: number | null; - lens_apt?: number | null; - lens_exp?: number | null; + // CO2 extras (stored as relations -> ids) + lens_conf?: any; + lens_apt?: any; + lens_exp?: any; fill_settings?: any[] | null; line_settings?: any[] | null; @@ -114,6 +114,9 @@ function normalizeForReset(iv: EditInitialValues) { source: idToString(iv.source), lens: idToString(iv.lens), laser_soft: idToString(iv.laser_soft), + lens_conf: idToString(iv.lens_conf), + lens_apt: idToString(iv.lens_apt), + lens_exp: idToString(iv.lens_exp), // Arrays: coerce dropdown-ish fields to internal enum keys fill_settings: (iv.fill_settings ?? []).map((r: any) => ({ @@ -166,7 +169,6 @@ const DIRECTUS_FIELDS: Record = { "fill_settings", "line_settings", "raster_settings", - // extras "uploader", "lens_conf", "lens_apt", @@ -304,7 +306,7 @@ function useOptions(path: string, forceIncludeId?: string, opts?: { disableNmFil let url = ""; let normalize: (rows: any[]) => Opt[] = (rows) => rows.map((r) => ({ - id: String(r.id), + id: String(r.id ?? r.submission_id ?? r.value), label: String(r.name ?? r.label ?? r.title ?? r.value ?? r.id), })); @@ -335,12 +337,10 @@ function useOptions(path: string, forceIncludeId?: string, opts?: { disableNmFil })); }; } else if (rawPath === "lens") { - // CO2 gantry uses focus lenses; all others use scan lenses if (target === "co2-gantry") { url = `${API}/items/laser_focus_lens?fields=id,name&limit=1000`; normalize = (rows) => rows.map((r) => ({ id: String(r.id), label: String(r.name ?? r.id) })); } else { - // SCAN LENSES (fiber, uv, co2-galvo): sort numerically by focal_length url = `${API}/items/laser_scan_lens?fields=id,field_size,focal_length&limit=1000`; normalize = (rows) => { const toNum = (v: any) => { @@ -356,8 +356,15 @@ function useOptions(path: string, forceIncludeId?: string, opts?: { disableNmFil }); }; } + } + // NEW: fixed lists for config / aperture / expander + else if (rawPath === "laser_scan_lens_config") { + url = `${API}/items/laser_scan_lens_config?fields=id,name&limit=1000&sort=name`; + } else if (rawPath === "laser_scan_lens_apt") { + url = `${API}/items/laser_scan_lens_apt?fields=id,name&limit=1000&sort=name`; + } else if (rawPath === "laser_scan_lens_exp") { + url = `${API}/items/laser_scan_lens_exp?fields=id,name&limit=1000&sort=name`; } else { - // unknown path → empty setOptsState([]); setLoading(false); return; @@ -375,7 +382,6 @@ function useOptions(path: string, forceIncludeId?: string, opts?: { disableNmFil ensured = [{ id: String(forceIncludeId), label: "(current selection)" }, ...mapped]; } - // client-side text filter const needle = (q || "").trim().toLowerCase(); const filtered = needle ? ensured.filter((o) => o.label.toLowerCase().includes(needle)) : ensured; @@ -501,9 +507,8 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { const sp = useSearchParams(); const isEdit = isEditProps(props); - const edit = isEdit ? props : null; // strongly-typed local when edit + const edit = isEdit ? props : null; - // Initialize as CO2-galvo in edit mode (unless explicitly overridden) const initialFromQuery = (sp.get("target") as Target) || props.initialTarget || @@ -516,19 +521,13 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { } }, [isEdit, props.initialTarget, target]); - // Map collection -> slug used by options selectors const typeForOptions = useMemo(() => { switch (target) { - case "settings_fiber": - return "fiber"; - case "settings_uv": - return "uv"; - case "settings_co2gan": - return "co2-gantry"; - case "settings_co2gal": - return "co2-galvo"; - default: - return "fiber"; + case "settings_fiber": return "fiber"; + case "settings_uv": return "uv"; + case "settings_co2gan": return "co2-gantry"; + case "settings_co2gal": return "co2-galvo"; + default: return "fiber"; } }, [target]); @@ -547,46 +546,39 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { useEffect(() => { let alive = true; - - // use our bearer-only API fetch(`/api/me`, { cache: "no-store", credentials: "include" }) .then((r) => (r.ok ? r.json() : Promise.reject(r))) - .then((j) => { - if (!alive) return; - setMe(j || null); - }) - .catch(() => { - if (alive) setMeErr("not-signed-in"); - }); - - return () => { - alive = false; - }; + .then((j) => { if (!alive) return; setMe(j || null); }) + .catch(() => { if (alive) setMeErr("not-signed-in"); }); + return () => { alive = false; }; }, []); const meLabel = me?.username ?? ""; - // For edit-mode, compute normalized current values once to seed option lists + // For edit-mode, compute normalized current values once const current = useMemo( () => (isEdit && edit?.initialValues ? normalizeForReset(edit.initialValues) : null), [isEdit, edit?.initialValues] ); // Options - const mats = useOptions("material", current?.mat || undefined); + const mats = useOptions("material", current?.mat || undefined); const coats = useOptions("material_coating", current?.mat_coat || undefined); const colors = useOptions("material_color", current?.mat_color || undefined); const opacs = useOptions("material_opacity", current?.mat_opacity || undefined); - const soft = useOptions("laser_software", current?.laser_soft || undefined); // required for ALL targets - const srcs = useOptions(`laser_source?target=${typeForOptions}`, current?.source || undefined, { - disableNmFilter: isEdit, - }); - const lens = useOptions(`lens?target=${typeForOptions}`, current?.lens || undefined); + const soft = useOptions("laser_software", current?.laser_soft || undefined); // required for ALL targets + const srcs = useOptions(`laser_source?target=${typeForOptions}`, current?.source || undefined, { disableNmFilter: isEdit }); + const lens = useOptions(`lens?target=${typeForOptions}`, current?.lens || undefined); + + // NEW: fixed-value dropdowns for galvo configs + const lensConf = useOptions("laser_scan_lens_config", current?.lens_conf || undefined); + const lensApt = useOptions("laser_scan_lens_apt", current?.lens_apt || undefined); + const lensExp = useOptions("laser_scan_lens_exp", current?.lens_exp || undefined); // Repeater choice options (LOCAL now, no network) - const fillType = { opts: toOpts(FILL_TYPE_OPTIONS), loading: false, setQ: (_: string) => {} }; - const rasterType = { opts: toOpts(RASTER_TYPE_OPTIONS), loading: false, setQ: (_: string) => {} }; - const rasterDither = { opts: toOpts(RASTER_DITHER_OPTIONS), loading: false, setQ: (_: string) => {} }; + const fillType = { opts: toOpts(FILL_TYPE_OPTIONS), loading: false, setQ: (_: string) => {} }; + const rasterType = { opts: toOpts(RASTER_TYPE_OPTIONS), loading: false, setQ: (_: string) => {} }; + const rasterDither= { opts: toOpts(RASTER_DITHER_OPTIONS), loading: false, setQ: (_: string) => {} }; const { register, @@ -609,8 +601,8 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { lens: "", focus: "", laser_soft: "", - repeat_all: "", // on all targets - // lens config (may be required on CO2 targets) + repeat_all: "", + // dropdown ids lens_conf: "", lens_apt: "", lens_exp: "", @@ -620,8 +612,8 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { }, }); - const fills = useFieldArray({ control, name: "fill_settings" }); - const lines = useFieldArray({ control, name: "line_settings" }); + const fills = useFieldArray({ control, name: "fill_settings" }); + const lines = useFieldArray({ control, name: "line_settings" }); const rasters = useFieldArray({ control, name: "raster_settings" }); // Prefill the form in edit mode @@ -643,31 +635,23 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { focus: iv.focus ?? "", laser_soft: iv.laser_soft ?? "", repeat_all: iv.repeat_all ?? "", - lens_conf: (iv as any).lens_conf ?? "", - lens_apt: (iv as any).lens_apt ?? "", - lens_exp: (iv as any).lens_exp ?? "", - fill_settings: iv.fill_settings ?? [], - line_settings: iv.line_settings ?? [], - raster_settings: iv.raster_settings ?? [], + lens_conf: iv.lens_conf ?? "", + lens_apt: iv.lens_apt ?? "", + lens_exp: iv.lens_exp ?? "", + fill_settings: iv.fill_settings ?? [], + line_settings: iv.line_settings ?? [], + raster_settings: iv.raster_settings ?? [], }); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isEdit, edit?.initialValues, reset]); // After reset, force RHF values once (covers early case) useEffect(() => { if (!isEdit || !current) return; - const fieldNames = [ - "laser_soft", - "mat", - "mat_coat", - "mat_color", - "mat_opacity", - "source", - "lens", + "laser_soft", "mat", "mat_coat", "mat_color", "mat_opacity", "source", "lens", + "lens_conf", "lens_apt", "lens_exp", ] as const; - const values = getValues(); fieldNames.forEach((name) => { const cur = (current as any)[name]; @@ -685,29 +669,11 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { const cur = (current as any)[name]; if (cur) setValue(name as any, cur, { shouldDirty: false, shouldValidate: false }); }; - apply("mat"); - apply("mat_coat"); - apply("mat_color"); - apply("mat_opacity"); - apply("laser_soft"); - apply("source"); - apply("lens"); - }, [ - isEdit, - current, - setValue, - mats.opts, - coats.opts, - colors.opts, - opacs.opts, - soft.opts, - srcs.opts, - lens.opts, - ]); + ["mat","mat_coat","mat_color","mat_opacity","laser_soft","source","lens","lens_conf","lens_apt","lens_exp"] + .forEach((n) => apply(n as any)); + }, [isEdit, current, setValue, mats.opts, coats.opts, colors.opts, opacs.opts, soft.opts, srcs.opts, lens.opts, lensConf.opts, lensApt.opts, lensExp.opts]); - function num(v: any) { - return v === "" || v == null ? null : Number(v); - } + function num(v: any) { return v === "" || v == null ? null : Number(v); } const bool = (v: any) => !!v; // ───────────────────────────────────────────────────────────── @@ -727,9 +693,8 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { return; } - // full UI payload (same shape the form uses) const fullPayload: any = { - target, // kept top-level for route parity + target, setting_title: values.setting_title, setting_notes: values.setting_notes || "", mat: values.mat || null, @@ -740,16 +705,16 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { source: values.source || null, lens: values.lens || null, focus: num(values.focus), - laser_soft: values.laser_soft || null, // all targets - repeat_all: num(values.repeat_all), // all targets + laser_soft: values.laser_soft || null, + repeat_all: num(values.repeat_all), - // uploader: set automatically from owner; include only if present on client + // uploader: include if we have it; server will also set from owner ...(me?.username || me?.email ? { uploader: me?.username ?? me?.email! } : {}), - // lens config (required for CO2 targets per your list) - lens_conf: num(values.lens_conf), - lens_apt: num(values.lens_apt), - lens_exp: num(values.lens_exp), + // CO2 dropdown ids (relations) + lens_conf: values.lens_conf || null, + lens_apt: values.lens_apt || null, + lens_exp: values.lens_exp || null, fill_settings: (values.fill_settings || []).map((r: any) => ({ name: r.name || "", @@ -801,16 +766,12 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { })), }; - // Whitelist to match collection fields and drop empty strings const directusData = toDirectusData(target, fullPayload); - - // Early guard for the common required field if (!directusData.setting_title) { setSubmitErr("Title is required."); return; } - // Build prod-compatible flat payload and ALWAYS send multipart const base = isEdit && edit?.submissionId ? { target, mode: "edit" as const, submission_id: edit.submissionId } : { target }; @@ -818,13 +779,12 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { const flatPayload = { ...base, ...directusData, - // Keep top-level setting_title for route validators setting_title: directusData.setting_title, }; try { const form = new FormData(); - form.set("payload", JSON.stringify(flatPayload)); // prod-compatible key + form.set("payload", JSON.stringify(flatPayload)); if (photoFile) form.set("photo", photoFile, photoFile.name || "photo"); if (screenFile) form.set("screen", screenFile, screenFile.name || "screen"); @@ -840,7 +800,6 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { throw new Error((data as any)?.error || "Submission failed"); } - // Success if (!isEdit) { reset(); setPhotoFile(null); @@ -864,16 +823,12 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { function onPick(file: File | null, setFile: (f: File | null) => void, setPreview: (s: string) => void) { setFile(file); - if (!file) { - setPreview(""); - return; - } + if (!file) { setPreview(""); return; } const reader = new FileReader(); reader.onload = () => setPreview(String(reader.result || "")); reader.readAsDataURL(file); } - // Convenience strings for “Current:” (edit mode) const currentPhotoId = isEdit && typeof edit?.initialValues?.photo === "string" ? (edit!.initialValues.photo as string) : null; const currentScreenId = @@ -881,7 +836,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { return (
- {/* Target + Software (Software required for ALL targets) */} + {/* Target + Software */}
@@ -905,7 +860,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { options={soft.opts} loading={soft.loading} onQuery={soft.setQ} - required={true} + required />
@@ -953,18 +908,11 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { type="file" accept="image/*" data-role="photo" - // Required when creating OR when editing without an existing photo id (until a new file is chosen) required={!currentPhotoId && !photoFile} onChange={(e) => onPick(e.target.files?.[0] ?? null, setPhotoFile, setPhotoPreview)} />

- {photoFile ? ( - <> - Selected: {photoFile.name} - - ) : ( - "Max 25 MB. JPG/PNG/WebP recommended." - )} + {photoFile ? <>Selected: {photoFile.name} : "Max 25 MB. JPG/PNG/WebP recommended."}

{photoPreview ? Result preview : null}
@@ -984,13 +932,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { onChange={(e) => onPick(e.target.files?.[0] ?? null, setScreenFile, setScreenPreview)} />

- {screenFile ? ( - <> - Selected: {screenFile.name} - - ) : ( - "Max 25 MB. JPG/PNG/WebP recommended." - )} + {screenFile ? <>Selected: {screenFile.name} : "Max 25 MB. JPG/PNG/WebP recommended."}

{screenPreview ? Settings preview : null} @@ -1004,60 +946,12 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { {/* Material / Source / Lens */}
- - - - - - + + + + + +
{/* Focus, thickness, repeat_all */} @@ -1070,16 +964,40 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {

- {/* Lens Configuration (required on CO2 targets per your list) */} + {/* Lens Configuration (dropdowns with fixed options) */} {(target === "settings_co2gan" || target === "settings_co2gal") && (
- Lens Configuration + Lens Options
- + {target === "settings_co2gal" && ( <> - - + + )}
@@ -1097,32 +1015,23 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { {fills.fields.map((f, i) => (
- {}} - placeholder="Select type" - /> + {}} placeholder="Select type" /> - - - - - - - + + + + + + +
- +
- +
- @@ -1140,20 +1049,19 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
{lines.fields.map((f, i) => (
- - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -1165,50 +1073,29 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
Raster Settings -
{rasters.fields.map((f, i) => (
- - - - {}} - placeholder="Select type" - /> - {}} - placeholder="Select dither" - /> - - - - - - - - + + + + {}} placeholder="Select type" /> + {}} placeholder="Select dither" /> + + + + + + + +
- +
-