form fixes

This commit is contained in:
makearmy 2025-10-05 08:50:01 -04:00
parent d04613ffdc
commit fd97d67080

View file

@ -93,10 +93,10 @@ type EditInitialValues = {
laser_soft?: any; laser_soft?: any;
repeat_all?: number | null; repeat_all?: number | null;
// may exist on CO2 targets // CO2 extras (stored as relations -> ids)
lens_conf?: number | null; lens_conf?: any;
lens_apt?: number | null; lens_apt?: any;
lens_exp?: number | null; lens_exp?: any;
fill_settings?: any[] | null; fill_settings?: any[] | null;
line_settings?: any[] | null; line_settings?: any[] | null;
@ -114,6 +114,9 @@ function normalizeForReset(iv: EditInitialValues) {
source: idToString(iv.source), source: idToString(iv.source),
lens: idToString(iv.lens), lens: idToString(iv.lens),
laser_soft: idToString(iv.laser_soft), 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 // Arrays: coerce dropdown-ish fields to internal enum keys
fill_settings: (iv.fill_settings ?? []).map((r: any) => ({ fill_settings: (iv.fill_settings ?? []).map((r: any) => ({
@ -166,7 +169,6 @@ const DIRECTUS_FIELDS: Record<Target, readonly string[]> = {
"fill_settings", "fill_settings",
"line_settings", "line_settings",
"raster_settings", "raster_settings",
// extras
"uploader", "uploader",
"lens_conf", "lens_conf",
"lens_apt", "lens_apt",
@ -304,7 +306,7 @@ function useOptions(path: string, forceIncludeId?: string, opts?: { disableNmFil
let url = ""; let url = "";
let normalize: (rows: any[]) => Opt[] = (rows) => let normalize: (rows: any[]) => Opt[] = (rows) =>
rows.map((r) => ({ 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), 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") { } else if (rawPath === "lens") {
// CO2 gantry uses focus lenses; all others use scan lenses
if (target === "co2-gantry") { if (target === "co2-gantry") {
url = `${API}/items/laser_focus_lens?fields=id,name&limit=1000`; 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) })); normalize = (rows) => rows.map((r) => ({ id: String(r.id), label: String(r.name ?? r.id) }));
} else { } 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`; url = `${API}/items/laser_scan_lens?fields=id,field_size,focal_length&limit=1000`;
normalize = (rows) => { normalize = (rows) => {
const toNum = (v: any) => { 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 { } else {
// unknown path → empty
setOptsState([]); setOptsState([]);
setLoading(false); setLoading(false);
return; return;
@ -375,7 +382,6 @@ function useOptions(path: string, forceIncludeId?: string, opts?: { disableNmFil
ensured = [{ id: String(forceIncludeId), label: "(current selection)" }, ...mapped]; ensured = [{ id: String(forceIncludeId), label: "(current selection)" }, ...mapped];
} }
// client-side text filter
const needle = (q || "").trim().toLowerCase(); const needle = (q || "").trim().toLowerCase();
const filtered = needle ? ensured.filter((o) => o.label.toLowerCase().includes(needle)) : ensured; 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 sp = useSearchParams();
const isEdit = isEditProps(props); 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 = const initialFromQuery =
(sp.get("target") as Target) || (sp.get("target") as Target) ||
props.initialTarget || props.initialTarget ||
@ -516,19 +521,13 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
} }
}, [isEdit, props.initialTarget, target]); }, [isEdit, props.initialTarget, target]);
// Map collection -> slug used by options selectors
const typeForOptions = useMemo(() => { const typeForOptions = useMemo(() => {
switch (target) { switch (target) {
case "settings_fiber": case "settings_fiber": return "fiber";
return "fiber"; case "settings_uv": return "uv";
case "settings_uv": case "settings_co2gan": return "co2-gantry";
return "uv"; case "settings_co2gal": return "co2-galvo";
case "settings_co2gan": default: return "fiber";
return "co2-gantry";
case "settings_co2gal":
return "co2-galvo";
default:
return "fiber";
} }
}, [target]); }, [target]);
@ -547,46 +546,39 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
useEffect(() => { useEffect(() => {
let alive = true; let alive = true;
// use our bearer-only API
fetch(`/api/me`, { cache: "no-store", credentials: "include" }) fetch(`/api/me`, { cache: "no-store", credentials: "include" })
.then((r) => (r.ok ? r.json() : Promise.reject(r))) .then((r) => (r.ok ? r.json() : Promise.reject(r)))
.then((j) => { .then((j) => { if (!alive) return; setMe(j || null); })
if (!alive) return; .catch(() => { if (alive) setMeErr("not-signed-in"); });
setMe(j || null); return () => { alive = false; };
})
.catch(() => {
if (alive) setMeErr("not-signed-in");
});
return () => {
alive = false;
};
}, []); }, []);
const meLabel = me?.username ?? ""; 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( const current = useMemo(
() => (isEdit && edit?.initialValues ? normalizeForReset(edit.initialValues) : null), () => (isEdit && edit?.initialValues ? normalizeForReset(edit.initialValues) : null),
[isEdit, edit?.initialValues] [isEdit, edit?.initialValues]
); );
// Options // 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 coats = useOptions("material_coating", current?.mat_coat || undefined);
const colors = useOptions("material_color", current?.mat_color || undefined); const colors = useOptions("material_color", current?.mat_color || undefined);
const opacs = useOptions("material_opacity", current?.mat_opacity || undefined); const opacs = useOptions("material_opacity", current?.mat_opacity || undefined);
const soft = useOptions("laser_software", current?.laser_soft || undefined); // required for ALL targets const soft = useOptions("laser_software", current?.laser_soft || undefined); // required for ALL targets
const srcs = useOptions(`laser_source?target=${typeForOptions}`, current?.source || undefined, { const srcs = useOptions(`laser_source?target=${typeForOptions}`, current?.source || undefined, { disableNmFilter: isEdit });
disableNmFilter: isEdit, const lens = useOptions(`lens?target=${typeForOptions}`, current?.lens || undefined);
});
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) // Repeater choice options (LOCAL now, no network)
const fillType = { opts: toOpts(FILL_TYPE_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 rasterType = { opts: toOpts(RASTER_TYPE_OPTIONS), loading: false, setQ: (_: string) => {} };
const rasterDither = { opts: toOpts(RASTER_DITHER_OPTIONS), loading: false, setQ: (_: string) => {} }; const rasterDither= { opts: toOpts(RASTER_DITHER_OPTIONS), loading: false, setQ: (_: string) => {} };
const { const {
register, register,
@ -609,8 +601,8 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
lens: "", lens: "",
focus: "", focus: "",
laser_soft: "", laser_soft: "",
repeat_all: "", // on all targets repeat_all: "",
// lens config (may be required on CO2 targets) // dropdown ids
lens_conf: "", lens_conf: "",
lens_apt: "", lens_apt: "",
lens_exp: "", lens_exp: "",
@ -620,8 +612,8 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
}, },
}); });
const fills = useFieldArray({ control, name: "fill_settings" }); const fills = useFieldArray({ control, name: "fill_settings" });
const lines = useFieldArray({ control, name: "line_settings" }); const lines = useFieldArray({ control, name: "line_settings" });
const rasters = useFieldArray({ control, name: "raster_settings" }); const rasters = useFieldArray({ control, name: "raster_settings" });
// Prefill the form in edit mode // Prefill the form in edit mode
@ -643,31 +635,23 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
focus: iv.focus ?? "", focus: iv.focus ?? "",
laser_soft: iv.laser_soft ?? "", laser_soft: iv.laser_soft ?? "",
repeat_all: iv.repeat_all ?? "", repeat_all: iv.repeat_all ?? "",
lens_conf: (iv as any).lens_conf ?? "", lens_conf: iv.lens_conf ?? "",
lens_apt: (iv as any).lens_apt ?? "", lens_apt: iv.lens_apt ?? "",
lens_exp: (iv as any).lens_exp ?? "", lens_exp: iv.lens_exp ?? "",
fill_settings: iv.fill_settings ?? [], fill_settings: iv.fill_settings ?? [],
line_settings: iv.line_settings ?? [], line_settings: iv.line_settings ?? [],
raster_settings: iv.raster_settings ?? [], raster_settings: iv.raster_settings ?? [],
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isEdit, edit?.initialValues, reset]); }, [isEdit, edit?.initialValues, reset]);
// After reset, force RHF values once (covers early case) // After reset, force RHF values once (covers early case)
useEffect(() => { useEffect(() => {
if (!isEdit || !current) return; if (!isEdit || !current) return;
const fieldNames = [ const fieldNames = [
"laser_soft", "laser_soft", "mat", "mat_coat", "mat_color", "mat_opacity", "source", "lens",
"mat", "lens_conf", "lens_apt", "lens_exp",
"mat_coat",
"mat_color",
"mat_opacity",
"source",
"lens",
] as const; ] as const;
const values = getValues(); const values = getValues();
fieldNames.forEach((name) => { fieldNames.forEach((name) => {
const cur = (current as any)[name]; const cur = (current as any)[name];
@ -685,29 +669,11 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
const cur = (current as any)[name]; const cur = (current as any)[name];
if (cur) setValue(name as any, cur, { shouldDirty: false, shouldValidate: false }); if (cur) setValue(name as any, cur, { shouldDirty: false, shouldValidate: false });
}; };
apply("mat"); ["mat","mat_coat","mat_color","mat_opacity","laser_soft","source","lens","lens_conf","lens_apt","lens_exp"]
apply("mat_coat"); .forEach((n) => apply(n as any));
apply("mat_color"); }, [isEdit, current, setValue, mats.opts, coats.opts, colors.opts, opacs.opts, soft.opts, srcs.opts, lens.opts, lensConf.opts, lensApt.opts, lensExp.opts]);
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,
]);
function num(v: any) { function num(v: any) { return v === "" || v == null ? null : Number(v); }
return v === "" || v == null ? null : Number(v);
}
const bool = (v: any) => !!v; const bool = (v: any) => !!v;
// ───────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────
@ -727,9 +693,8 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
return; return;
} }
// full UI payload (same shape the form uses)
const fullPayload: any = { const fullPayload: any = {
target, // kept top-level for route parity target,
setting_title: values.setting_title, setting_title: values.setting_title,
setting_notes: values.setting_notes || "", setting_notes: values.setting_notes || "",
mat: values.mat || null, mat: values.mat || null,
@ -740,16 +705,16 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
source: values.source || null, source: values.source || null,
lens: values.lens || null, lens: values.lens || null,
focus: num(values.focus), focus: num(values.focus),
laser_soft: values.laser_soft || null, // all targets laser_soft: values.laser_soft || null,
repeat_all: num(values.repeat_all), // all targets 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! } : {}), ...(me?.username || me?.email ? { uploader: me?.username ?? me?.email! } : {}),
// lens config (required for CO2 targets per your list) // CO2 dropdown ids (relations)
lens_conf: num(values.lens_conf), lens_conf: values.lens_conf || null,
lens_apt: num(values.lens_apt), lens_apt: values.lens_apt || null,
lens_exp: num(values.lens_exp), lens_exp: values.lens_exp || null,
fill_settings: (values.fill_settings || []).map((r: any) => ({ fill_settings: (values.fill_settings || []).map((r: any) => ({
name: r.name || "", 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); const directusData = toDirectusData(target, fullPayload);
// Early guard for the common required field
if (!directusData.setting_title) { if (!directusData.setting_title) {
setSubmitErr("Title is required."); setSubmitErr("Title is required.");
return; return;
} }
// Build prod-compatible flat payload and ALWAYS send multipart
const base = isEdit && edit?.submissionId const base = isEdit && edit?.submissionId
? { target, mode: "edit" as const, submission_id: edit.submissionId } ? { target, mode: "edit" as const, submission_id: edit.submissionId }
: { target }; : { target };
@ -818,13 +779,12 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
const flatPayload = { const flatPayload = {
...base, ...base,
...directusData, ...directusData,
// Keep top-level setting_title for route validators
setting_title: directusData.setting_title, setting_title: directusData.setting_title,
}; };
try { try {
const form = new FormData(); 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 (photoFile) form.set("photo", photoFile, photoFile.name || "photo");
if (screenFile) form.set("screen", screenFile, screenFile.name || "screen"); 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"); throw new Error((data as any)?.error || "Submission failed");
} }
// Success
if (!isEdit) { if (!isEdit) {
reset(); reset();
setPhotoFile(null); 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) { function onPick(file: File | null, setFile: (f: File | null) => void, setPreview: (s: string) => void) {
setFile(file); setFile(file);
if (!file) { if (!file) { setPreview(""); return; }
setPreview("");
return;
}
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => setPreview(String(reader.result || "")); reader.onload = () => setPreview(String(reader.result || ""));
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
// Convenience strings for “Current:” (edit mode)
const currentPhotoId = const currentPhotoId =
isEdit && typeof edit?.initialValues?.photo === "string" ? (edit!.initialValues.photo as string) : null; isEdit && typeof edit?.initialValues?.photo === "string" ? (edit!.initialValues.photo as string) : null;
const currentScreenId = const currentScreenId =
@ -881,7 +836,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
return ( return (
<div className="max-w-3xl mx-auto space-y-4"> <div className="max-w-3xl mx-auto space-y-4">
{/* Target + Software (Software required for ALL targets) */} {/* Target + Software */}
<div className="flex flex-wrap gap-3 items-end"> <div className="flex flex-wrap gap-3 items-end">
<div> <div>
<label className="block text-sm mb-1">Target</label> <label className="block text-sm mb-1">Target</label>
@ -905,7 +860,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
options={soft.opts} options={soft.opts}
loading={soft.loading} loading={soft.loading}
onQuery={soft.setQ} onQuery={soft.setQ}
required={true} required
/> />
</div> </div>
</div> </div>
@ -953,18 +908,11 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
type="file" type="file"
accept="image/*" accept="image/*"
data-role="photo" data-role="photo"
// Required when creating OR when editing without an existing photo id (until a new file is chosen)
required={!currentPhotoId && !photoFile} required={!currentPhotoId && !photoFile}
onChange={(e) => onPick(e.target.files?.[0] ?? null, setPhotoFile, setPhotoPreview)} onChange={(e) => onPick(e.target.files?.[0] ?? null, setPhotoFile, setPhotoPreview)}
/> />
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
{photoFile ? ( {photoFile ? <>Selected: <span className="font-mono">{photoFile.name}</span></> : "Max 25 MB. JPG/PNG/WebP recommended."}
<>
Selected: <span className="font-mono">{photoFile.name}</span>
</>
) : (
"Max 25 MB. JPG/PNG/WebP recommended."
)}
</p> </p>
{photoPreview ? <img src={photoPreview} alt="Result preview" className="mt-2 rounded border" /> : null} {photoPreview ? <img src={photoPreview} alt="Result preview" className="mt-2 rounded border" /> : null}
</div> </div>
@ -984,13 +932,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
onChange={(e) => onPick(e.target.files?.[0] ?? null, setScreenFile, setScreenPreview)} onChange={(e) => onPick(e.target.files?.[0] ?? null, setScreenFile, setScreenPreview)}
/> />
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
{screenFile ? ( {screenFile ? <>Selected: <span className="font-mono">{screenFile.name}</span></> : "Max 25 MB. JPG/PNG/WebP recommended."}
<>
Selected: <span className="font-mono">{screenFile.name}</span>
</>
) : (
"Max 25 MB. JPG/PNG/WebP recommended."
)}
</p> </p>
{screenPreview ? <img src={screenPreview} alt="Settings preview" className="mt-2 rounded border" /> : null} {screenPreview ? <img src={screenPreview} alt="Settings preview" className="mt-2 rounded border" /> : null}
</div> </div>
@ -1004,60 +946,12 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
{/* Material / Source / Lens */} {/* Material / Source / Lens */}
<div className="grid md:grid-cols-2 gap-3"> <div className="grid md:grid-cols-2 gap-3">
<FilterableSelect <FilterableSelect label="Material" name="mat" register={register} options={mats.opts} loading={mats.loading} onQuery={mats.setQ} required />
label="Material" <FilterableSelect label="Coating" name="mat_coat" register={register} options={coats.opts} loading={coats.loading} onQuery={coats.setQ} required />
name="mat" <FilterableSelect label="Color" name="mat_color" register={register} options={colors.opts} loading={colors.loading} onQuery={colors.setQ} required />
register={register} <FilterableSelect label="Opacity" name="mat_opacity" register={register} options={opacs.opts} loading={opacs.loading} onQuery={opacs.setQ} required />
options={mats.opts} <FilterableSelect label="Laser Source" name="source" register={register} options={srcs.opts} loading={srcs.loading} onQuery={srcs.setQ} required />
loading={mats.loading} <FilterableSelect label="Lens" name="lens" register={register} options={lens.opts} loading={lens.loading} onQuery={lens.setQ} required />
onQuery={mats.setQ}
required
/>
<FilterableSelect
label="Coating"
name="mat_coat"
register={register}
options={coats.opts}
loading={coats.loading}
onQuery={coats.setQ}
required
/>
<FilterableSelect
label="Color"
name="mat_color"
register={register}
options={colors.opts}
loading={colors.loading}
onQuery={colors.setQ}
required
/>
<FilterableSelect
label="Opacity"
name="mat_opacity"
register={register}
options={opacs.opts}
loading={opacs.loading}
onQuery={opacs.setQ}
required
/>
<FilterableSelect
label="Laser Source"
name="source"
register={register}
options={srcs.opts}
loading={srcs.loading}
onQuery={srcs.setQ}
required
/>
<FilterableSelect
label="Lens"
name="lens"
register={register}
options={lens.opts}
loading={lens.loading}
onQuery={lens.setQ}
required
/>
</div> </div>
{/* Focus, thickness, repeat_all */} {/* Focus, thickness, repeat_all */}
@ -1070,16 +964,40 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
</p> </p>
</div> </div>
{/* Lens Configuration (required on CO2 targets per your list) */} {/* Lens Configuration (dropdowns with fixed options) */}
{(target === "settings_co2gan" || target === "settings_co2gal") && ( {(target === "settings_co2gan" || target === "settings_co2gal") && (
<fieldset className="border rounded p-3 space-y-2"> <fieldset className="border rounded p-3 space-y-2">
<legend className="font-semibold">Lens Configuration</legend> <legend className="font-semibold">Lens Options</legend>
<div className="grid md:grid-cols-3 gap-3"> <div className="grid md:grid-cols-3 gap-3">
<LabeledInput label="Lens Conf" name="lens_conf" type="number" step="1" register={register} required /> <FilterableSelect
label="Lens Configuration"
name="lens_conf"
register={register}
options={lensConf.opts}
loading={lensConf.loading}
onQuery={lensConf.setQ}
required
/>
{target === "settings_co2gal" && ( {target === "settings_co2gal" && (
<> <>
<LabeledInput label="Lens Aperture" name="lens_apt" type="number" step="1" register={register} required /> <FilterableSelect
<LabeledInput label="Lens Expansion" name="lens_exp" type="number" step="1" register={register} required /> label="Scan Head Aperture"
name="lens_apt"
register={register}
options={lensApt.opts}
loading={lensApt.loading}
onQuery={lensApt.setQ}
required
/>
<FilterableSelect
label="Beam Expander"
name="lens_exp"
register={register}
options={lensExp.opts}
loading={lensExp.loading}
onQuery={lensExp.setQ}
required
/>
</> </>
)} )}
</div> </div>
@ -1097,32 +1015,23 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
{fills.fields.map((f, i) => ( {fills.fields.map((f, i) => (
<div key={f.id} className="grid md:grid-cols-4 gap-2"> <div key={f.id} className="grid md:grid-cols-4 gap-2">
<LabeledInput label="Name" name={`fill_settings.${i}.name`} register={register} /> <LabeledInput label="Name" name={`fill_settings.${i}.name`} register={register} />
<FilterableSelect <FilterableSelect label="Type" name={`fill_settings.${i}.type`} register={register} options={toOpts(FILL_TYPE_OPTIONS)} loading={false} onQuery={() => {}} placeholder="Select type" />
label="Type"
name={`fill_settings.${i}.type`}
register={register}
options={fillType.opts}
loading={false}
onQuery={() => {}}
placeholder="Select type"
/>
<LabeledInput label="Frequency (kHz)" name={`fill_settings.${i}.frequency`} type="number" step="0.1" register={register} /> <LabeledInput label="Frequency (kHz)" name={`fill_settings.${i}.frequency`} type="number" step="0.1" register={register} />
<LabeledInput label="Pulse (ns)" name={`fill_settings.${i}.pulse`} type="number" step="0.1" register={register} /> <LabeledInput label="Pulse (ns)" name={`fill_settings.${i}.pulse`} type="number" step="0.1" register={register} />
<LabeledInput label="Power (%)" name={`fill_settings.${i}.power`} type="number" step="0.1" register={register} /> <LabeledInput label="Power (%)" name={`fill_settings.${i}.power`} type="number" step="0.1" register={register} />
<LabeledInput label="Speed (mm/s)" name={`fill_settings.${i}.speed`} type="number" step="0.1" register={register} /> <LabeledInput label="Speed (mm/s)" name={`fill_settings.${i}.speed`} type="number" step="0.1" register={register} />
<LabeledInput label="Interval (mm)" name={`fill_settings.${i}.interval`} type="number" step="0.001" register={register} /> <LabeledInput label="Interval (mm)" name={`fill_settings.${i}.interval`} type="number" step="0.001" register={register} />
<LabeledInput label="Pass" name={`fill_settings.${i}.pass`} type="number" step="1" register={register} /> <LabeledInput label="Pass" name={`fill_settings.${i}.pass`} type="number" step="1" register={register} />
<LabeledInput label="Angle (°)" name={`fill_settings.${i}.angle`} type="number" step="1" register={register} /> <LabeledInput label="Angle (°)" name={`fill_settings.${i}.angle`} type="number" step="1" register={register} />
<LabeledInput label="Increment" name={`fill_settings.${i}.increment`} type="number" step="0.001" register={register} /> <LabeledInput label="Increment" name={`fill_settings.${i}.increment`} type="number" step="0.001" register={register} />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<BoolBox label="Auto" name={`fill_settings.${i}.auto`} register={register} /> <BoolBox label="Auto" name={`fill_settings.${i}.auto`} register={register} />
<BoolBox label="Cross" name={`fill_settings.${i}.cross`} register={register} /> <BoolBox label="Cross" name={`fill_settings.${i}.cross`} register={register} />
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<BoolBox label="Flood" name={`fill_settings.${i}.flood`} register={register} /> <BoolBox label="Flood" name={`fill_settings.${i}.flood`} register={register} />
<BoolBox label="Air" name={`fill_settings.${i}.air`} register={register} /> <BoolBox label="Air" name={`fill_settings.${i}.air`} register={register} />
</div> </div>
<button type="button" className="px-2 py-1 border rounded md:col-span-4" onClick={() => fills.remove(i)}> <button type="button" className="px-2 py-1 border rounded md:col-span-4" onClick={() => fills.remove(i)}>
Remove Remove
</button> </button>
@ -1140,20 +1049,19 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
</div> </div>
{lines.fields.map((f, i) => ( {lines.fields.map((f, i) => (
<div key={f.id} className="grid md:grid-cols-4 gap-2"> <div key={f.id} className="grid md:grid-cols-4 gap-2">
<LabeledInput label="Name" name={`line_settings.${i}.name`} register={register} /> <LabeledInput label="Name" name={`line_settings.${i}.name`} register={register} />
<LabeledInput label="Frequency (kHz)" name={`line_settings.${i}.frequency`} type="number" step="0.1" register={register} /> <LabeledInput label="Frequency (kHz)" name={`line_settings.${i}.frequency`} type="number" step="0.1" register={register} />
<LabeledInput label="Pulse (ns)" name={`line_settings.${i}.pulse`} type="number" step="0.1" register={register} /> <LabeledInput label="Pulse (ns)" name={`line_settings.${i}.pulse`} type="number" step="0.1" register={register} />
<LabeledInput label="Power (%)" name={`line_settings.${i}.power`} type="number" step="0.1" register={register} /> <LabeledInput label="Power (%)" name={`line_settings.${i}.power`} type="number" step="0.1" register={register} />
<LabeledInput label="Speed (mm/s)" name={`line_settings.${i}.speed`} type="number" step="0.1" register={register} /> <LabeledInput label="Speed (mm/s)" name={`line_settings.${i}.speed`} type="number" step="0.1" register={register} />
<BoolBox label="Perf" name={`line_settings.${i}.perf`} register={register} /> <BoolBox label="Perf" name={`line_settings.${i}.perf`} register={register} />
<BoolBox label="Cut" name={`line_settings.${i}.cut`} register={register} /> <BoolBox label="Cut" name={`line_settings.${i}.cut`} register={register} />
<BoolBox label="Skip" name={`line_settings.${i}.skip`} register={register} /> <BoolBox label="Skip" name={`line_settings.${i}.skip`} register={register} />
<LabeledInput label="Pass" name={`line_settings.${i}.pass`} type="number" step="1" register={register} /> <LabeledInput label="Pass" name={`line_settings.${i}.pass`} type="number" step="1" register={register} />
<LabeledInput label="Step" name={`line_settings.${i}.step`} type="number" step="0.001" register={register} /> <LabeledInput label="Step" name={`line_settings.${i}.step`} type="number" step="0.001" register={register} />
<LabeledInput label="Size" name={`line_settings.${i}.size`} type="number" step="0.001" register={register} /> <LabeledInput label="Size" name={`line_settings.${i}.size`} type="number" step="0.001" register={register} />
<BoolBox label="Wobble" name={`line_settings.${i}.wobble`} register={register} /> <BoolBox label="Wobble" name={`line_settings.${i}.wobble`} register={register} />
<BoolBox label="Air" name={`line_settings.${i}.air`} register={register} /> <BoolBox label="Air" name={`line_settings.${i}.air`} register={register} />
<button type="button" className="px-2 py-1 border rounded md:col-span-4" onClick={() => lines.remove(i)}> <button type="button" className="px-2 py-1 border rounded md:col-span-4" onClick={() => lines.remove(i)}>
Remove Remove
</button> </button>
@ -1165,50 +1073,29 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
<fieldset className="border rounded p-3 space-y-2"> <fieldset className="border rounded p-3 space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<legend className="font-semibold">Raster Settings</legend> <legend className="font-semibold">Raster Settings</legend>
<button <button type="button" className="px-2 py-1 border rounded" onClick={() => rasters.append({ type: "uni", dither: "threshold" })}>
type="button"
className="px-2 py-1 border rounded"
onClick={() => rasters.append({ type: "uni", dither: "threshold" })}
>
+ Add + Add
</button> </button>
</div> </div>
{rasters.fields.map((f, i) => ( {rasters.fields.map((f, i) => (
<div key={f.id} className="grid md:grid-cols-4 gap-2"> <div key={f.id} className="grid md:grid-cols-4 gap-2">
<LabeledInput label="Name" name={`raster_settings.${i}.name`} register={register} /> <LabeledInput label="Name" name={`raster_settings.${i}.name`} register={register} />
<LabeledInput label="Frequency (kHz)" name={`raster_settings.${i}.frequency`} type="number" step="0.1" register={register} /> <LabeledInput label="Frequency (kHz)" name={`raster_settings.${i}.frequency`} type="number" step="0.1" register={register} />
<LabeledInput label="Pulse (ns)" name={`raster_settings.${i}.pulse`} type="number" step="0.1" register={register} /> <LabeledInput label="Pulse (ns)" name={`raster_settings.${i}.pulse`} type="number" step="0.1" register={register} />
<FilterableSelect <FilterableSelect label="Type" name={`raster_settings.${i}.type`} register={register} options={toOpts(RASTER_TYPE_OPTIONS)} loading={false} onQuery={() => {}} placeholder="Select type" />
label="Type" <FilterableSelect label="Dither" name={`raster_settings.${i}.dither`} register={register} options={toOpts(RASTER_DITHER_OPTIONS)} loading={false} onQuery={() => {}} placeholder="Select dither" />
name={`raster_settings.${i}.type`} <LabeledInput label="Power (%)" name={`raster_settings.${i}.power`} type="number" step="0.1" register={register} />
register={register} <LabeledInput label="Speed (mm/s)" name={`raster_settings.${i}.speed`} type="number" step="0.1" register={register} />
options={rasterType.opts} <LabeledInput label="Halftone Cell" name={`raster_settings.${i}.halftone_cell`} type="number" step="1" register={register} />
loading={false} <LabeledInput label="Halftone Angle" name={`raster_settings.${i}.halftone_angle`} type="number" step="1" register={register} />
onQuery={() => {}} <LabeledInput label="Interval (mm)" name={`raster_settings.${i}.interval`} type="number" step="0.001" register={register} />
placeholder="Select type" <LabeledInput label="Dot" name={`raster_settings.${i}.dot`} type="number" step="0.1" register={register} />
/> <LabeledInput label="Pass" name={`raster_settings.${i}.pass`} type="number" step="1" register={register} />
<FilterableSelect <BoolBox label="Cross" name={`raster_settings.${i}.cross`} register={register} />
label="Dither"
name={`raster_settings.${i}.dither`}
register={register}
options={rasterDither.opts}
loading={false}
onQuery={() => {}}
placeholder="Select dither"
/>
<LabeledInput label="Power (%)" name={`raster_settings.${i}.power`} type="number" step="0.1" register={register} />
<LabeledInput label="Speed (mm/s)" name={`raster_settings.${i}.speed`} type="number" step="0.1" register={register} />
<LabeledInput label="Halftone Cell" name={`raster_settings.${i}.halftone_cell`} type="number" step="1" register={register} />
<LabeledInput label="Halftone Angle" name={`raster_settings.${i}.halftone_angle`} type="number" step="1" register={register} />
<LabeledInput label="Interval (mm)" name={`raster_settings.${i}.interval`} type="number" step="0.001" register={register} />
<LabeledInput label="Dot" name={`raster_settings.${i}.dot`} type="number" step="0.1" register={register} />
<LabeledInput label="Pass" name={`raster_settings.${i}.pass`} type="number" step="1" register={register} />
<BoolBox label="Cross" name={`raster_settings.${i}.cross`} register={register} />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<BoolBox label="Inversion" name={`raster_settings.${i}.inversion`} register={register} /> <BoolBox label="Inversion" name={`raster_settings.${i}.inversion`} register={register} />
<BoolBox label="Air" name={`raster_settings.${i}.air`} register={register} /> <BoolBox label="Air" name={`raster_settings.${i}.air`} register={register} />
</div> </div>
<button type="button" className="px-2 py-1 border rounded md:col-span-4" onClick={() => rasters.remove(i)}> <button type="button" className="px-2 py-1 border rounded md:col-span-4" onClick={() => rasters.remove(i)}>
Remove Remove
</button> </button>