From f0831a6c42973b36d8ef62670c75fa9654fa0796 Mon Sep 17 00:00:00 2001 From: makearmy Date: Fri, 3 Oct 2025 21:45:57 -0400 Subject: [PATCH] prefilled settings edits --- components/forms/SettingsSubmit.tsx | 102 ++++++++++++++++------------ 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/components/forms/SettingsSubmit.tsx b/components/forms/SettingsSubmit.tsx index c1e53690..f9ea6edb 100644 --- a/components/forms/SettingsSubmit.tsx +++ b/components/forms/SettingsSubmit.tsx @@ -1,3 +1,4 @@ +// components/forms/SettingsSubmit.tsx "use client"; import { useEffect, useMemo, useState } from "react"; @@ -62,8 +63,6 @@ type EditInitialValues = { setting_title?: string; setting_notes?: string; - // In edit mode these may be an existing Directus file id string. - // (We treat them as string here; if you later pass File objects it still compiles.) photo?: string | File | { id?: string } | null; screen?: string | File | { id?: string } | null; @@ -153,7 +152,6 @@ function useOptions(path: string) { } else if (rawPath === "laser_software") { url = `${API}/items/laser_software?fields=id,name&limit=1000&sort=name`; } else if (rawPath === "laser_source") { - // fetch all and client-filter by nm until/if a numeric mirror field exists url = `${API}/items/laser_source?fields=submission_id,make,model,nm&limit=2000&sort=make,model`; const range = nmRangeFor(target); normalize = (rows) => { @@ -169,12 +167,10 @@ function useOptions(path: string) { })); }; } 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) => { @@ -191,7 +187,6 @@ function useOptions(path: string) { }; } } else { - // unknown path → empty setOpts([]); setLoading(false); return; @@ -203,7 +198,6 @@ function useOptions(path: string) { const rows = json?.data ?? []; const mapped = normalize(rows); - // client-side text filter const needle = (q || "").trim().toLowerCase(); const filtered = needle ? mapped.filter((o) => o.label.toLowerCase().includes(needle)) : mapped; @@ -295,12 +289,11 @@ 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; const initialFromQuery = (sp.get("target") as Target) || props.initialTarget || "settings_fiber"; const [target, setTarget] = useState(initialFromQuery); - // Map collection -> slug used by options selectors const typeForOptions = useMemo(() => { switch (target) { case "settings_fiber": @@ -332,7 +325,6 @@ 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) => { @@ -349,24 +341,19 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { }, []); const meLabel = - me?.display_name?.trim() || - [me?.first_name, me?.last_name].filter(Boolean).join(" ").trim() || - me?.username?.trim() || - me?.email?.trim() || - (me?.id ? `User ${me.id.slice(0, 8)}…${me.id.slice(-4)}` : "Unknown user"); + me?.username() || // Options const mats = useOptions("material"); const coats = useOptions("material_coating"); const colors = useOptions("material_color"); const opacs = useOptions("material_opacity"); - const soft = useOptions("laser_software"); // required for ALL targets + const soft = useOptions("laser_software"); - // these two need ?target= const srcs = useOptions(`laser_source?target=${typeForOptions}`); const lens = useOptions(`lens?target=${typeForOptions}`); - // Repeater choice options (LOCAL now, no network) + // Repeater choice options (LOCAL) 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) => {} }; @@ -390,7 +377,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { lens: "", focus: "", laser_soft: "", - repeat_all: "", // on all targets + repeat_all: "", fill_settings: [], line_settings: [], raster_settings: [], @@ -401,9 +388,31 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { const lines = useFieldArray({ control, name: "line_settings" }); const rasters = useFieldArray({ control, name: "raster_settings" }); - // If you later want to prefill the form in edit mode, call reset(...) here - // with string IDs for selects. (The record page currently passes raw objects, - // so we’re not auto-resetting yet to avoid mismatched shapes.) + // ✅ Prefill when editing + useEffect(() => { + if (!isEdit || !edit?.initialValues) return; + + const S = (v: any) => (v == null ? "" : String(v)); + + reset({ + setting_title: edit.initialValues.setting_title ?? "", + setting_notes: edit.initialValues.setting_notes ?? "", + mat: S(edit.initialValues.mat), + mat_coat: S(edit.initialValues.mat_coat), + mat_color: S(edit.initialValues.mat_color), + mat_opacity: S(edit.initialValues.mat_opacity), + mat_thickness: edit.initialValues.mat_thickness ?? "", + source: S(edit.initialValues.source), + lens: S(edit.initialValues.lens), + focus: edit.initialValues.focus ?? "", + laser_soft: S(edit.initialValues.laser_soft), + repeat_all: edit.initialValues.repeat_all ?? "", + fill_settings: edit.initialValues.fill_settings ?? [], + line_settings: edit.initialValues.line_settings ?? [], + raster_settings: edit.initialValues.raster_settings ?? [], + }); + // we keep previews empty; “Current: ” is shown below file inputs + }, [isEdit, edit?.initialValues, reset]); function num(v: any) { return v === "" || v == null ? null : Number(v); @@ -413,9 +422,9 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { async function onSubmit(values: any) { setSubmitErr(null); - // In edit mode, allow keeping the existing photo (no new file) if one exists. const hasExistingPhotoId = - !!(edit && typeof edit.initialValues?.photo === "string" && edit.initialValues.photo); + !!(isEdit && typeof edit?.initialValues?.photo === "string" && edit?.initialValues?.photo); + if (!photoFile && !hasExistingPhotoId) { (document.querySelector('input[type="file"][data-role="photo"]') as HTMLInputElement | null)?.focus(); return; @@ -433,8 +442,8 @@ 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), fill_settings: (values.fill_settings || []).map((r: any) => ({ name: r.name || "", power: num(r.power), @@ -485,11 +494,15 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { })), }; + // ✅ Tell the API we're editing and which record to patch + if (isEdit) { + payload.mode = "edit"; + payload.submission_id = edit!.submissionId; + } + try { let res: Response; - // In edit mode you may later switch this to a dedicated update endpoint. - // For now we keep the existing create endpoint behavior. if (photoFile || screenFile) { const form = new FormData(); form.set("payload", JSON.stringify(payload)); @@ -511,14 +524,20 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { throw new Error(data?.error || "Submission failed"); } - reset(); - setPhotoFile(null); - setScreenFile(null); - setPhotoPreview(""); - setScreenPreview(""); + // Reset only on create; for edit, keep values visible + if (!isEdit) { + reset(); + setPhotoFile(null); + setScreenFile(null); + setPhotoPreview(""); + setScreenPreview(""); + } - const id = data?.id ? String(data.id) : ""; - router.push(`/submit/settings/success?target=${encodeURIComponent(target)}&id=${encodeURIComponent(id)}`); + const id = data?.id ? String(data.id) : String(edit?.submissionId ?? ""); + const next = isEdit + ? `/submit/settings/success?target=${encodeURIComponent(target)}&id=${encodeURIComponent(id)}` + : `/submit/settings/success?target=${encodeURIComponent(target)}&id=${encodeURIComponent(id)}`; + router.push(next); } catch (e: any) { setSubmitErr(e?.message || "Submission failed"); } @@ -535,15 +554,14 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { reader.readAsDataURL(file); } - // Convenience strings for “Current:” (edit mode) const currentPhotoId = - edit && typeof edit.initialValues?.photo === "string" ? edit.initialValues.photo : null; + isEdit && typeof edit?.initialValues?.photo === "string" ? edit!.initialValues!.photo : null; const currentScreenId = - edit && typeof edit.initialValues?.screen === "string" ? edit.initialValues.screen : null; + isEdit && typeof edit?.initialValues?.screen === "string" ? edit!.initialValues!.screen : null; return (
- {/* Target + Software (Software required for ALL targets) */} + {/* Target + Software */}
@@ -615,7 +633,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { type="file" accept="image/*" data-role="photo" - required={!currentPhotoId} // in edit mode, only required if there isn't an existing id + required={!currentPhotoId} onChange={(e) => onPick(e.target.files?.[0] ?? null, setPhotoFile, setPhotoPreview)} />

@@ -758,7 +776,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) { @@ -837,7 +855,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {