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 */}
Target
@@ -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 ?
: 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 ? : 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" />
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
+
-
fills.remove(i)}>
Remove
@@ -1140,20 +1049,19 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
{lines.fields.map((f, i) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
lines.remove(i)}>
Remove
@@ -1165,50 +1073,29 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
Raster Settings
- rasters.append({ type: "uni", dither: "threshold" })}
- >
+ rasters.append({ type: "uni", dither: "threshold" })}>
+ Add
{rasters.fields.map((f, i) => (
-
-
-
-
{}}
- placeholder="Select type"
- />
- {}}
- placeholder="Select dither"
- />
-
-
-
-
-
-
-
-
+
+
+
+ {}} placeholder="Select type" />
+ {}} placeholder="Select dither" />
+
+
+
+
+
+
+
+
-
+
-
rasters.remove(i)}>
Remove