refactor for envelope updates on submissions
This commit is contained in:
parent
f6a275cbc4
commit
8654653589
1 changed files with 84 additions and 64 deletions
|
|
@ -93,6 +93,11 @@ type EditInitialValues = {
|
|||
laser_soft?: any;
|
||||
repeat_all?: number | null;
|
||||
|
||||
// may be present in existing data
|
||||
lens_conf?: number | null;
|
||||
lens_apt?: number | null;
|
||||
lens_exp?: number | null;
|
||||
|
||||
fill_settings?: any[] | null;
|
||||
line_settings?: any[] | null;
|
||||
raster_settings?: any[] | null;
|
||||
|
|
@ -161,6 +166,11 @@ const DIRECTUS_FIELDS: Record<Target, readonly string[]> = {
|
|||
"fill_settings",
|
||||
"line_settings",
|
||||
"raster_settings",
|
||||
// extras
|
||||
"uploader",
|
||||
"lens_conf",
|
||||
"lens_apt",
|
||||
"lens_exp",
|
||||
],
|
||||
settings_co2gan: [
|
||||
"setting_title",
|
||||
|
|
@ -180,6 +190,8 @@ const DIRECTUS_FIELDS: Record<Target, readonly string[]> = {
|
|||
"fill_settings",
|
||||
"line_settings",
|
||||
"raster_settings",
|
||||
"uploader",
|
||||
"lens_conf",
|
||||
],
|
||||
settings_fiber: [
|
||||
"setting_title",
|
||||
|
|
@ -199,6 +211,7 @@ const DIRECTUS_FIELDS: Record<Target, readonly string[]> = {
|
|||
"fill_settings",
|
||||
"line_settings",
|
||||
"raster_settings",
|
||||
"uploader",
|
||||
],
|
||||
settings_uv: [
|
||||
"setting_title",
|
||||
|
|
@ -218,6 +231,7 @@ const DIRECTUS_FIELDS: Record<Target, readonly string[]> = {
|
|||
"fill_settings",
|
||||
"line_settings",
|
||||
"raster_settings",
|
||||
"uploader",
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
|
@ -423,10 +437,7 @@ function FilterableSelect({
|
|||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
<select className="w-full border rounded px-2 py-1" {...register(name, { required })}>
|
||||
<option value="">
|
||||
{placeholder}
|
||||
{loading ? " (loading…)" : ""}
|
||||
</option>
|
||||
<option value="">{placeholder}{loading ? " (loading…)" : ""}</option>
|
||||
{filtered.map((o) => (
|
||||
<option key={o.id} value={o.id}>
|
||||
{o.label}
|
||||
|
|
@ -530,7 +541,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
// UX error for auth/submit
|
||||
const [submitErr, setSubmitErr] = useState<string | null>(null);
|
||||
|
||||
// Current signed-in user (banner only)
|
||||
// Current signed-in user (banner + uploader)
|
||||
const [me, setMe] = useState<Me | null>(null);
|
||||
const [meErr, setMeErr] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -599,6 +610,11 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
focus: "",
|
||||
laser_soft: "",
|
||||
repeat_all: "", // on all targets
|
||||
// extras
|
||||
uploader: "",
|
||||
lens_conf: "",
|
||||
lens_apt: "",
|
||||
lens_exp: "",
|
||||
fill_settings: [],
|
||||
line_settings: [],
|
||||
raster_settings: [],
|
||||
|
|
@ -628,12 +644,17 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
focus: iv.focus ?? "",
|
||||
laser_soft: iv.laser_soft ?? "",
|
||||
repeat_all: iv.repeat_all ?? "",
|
||||
fill_settings: iv.fill_settings ?? [],
|
||||
line_settings: iv.line_settings ?? [],
|
||||
raster_settings: iv.raster_settings ?? [],
|
||||
uploader: (me?.username ?? me?.email ?? "") || "",
|
||||
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 ?? [],
|
||||
});
|
||||
}
|
||||
}, [isEdit, edit?.initialValues, reset]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isEdit, edit?.initialValues, reset, me?.username, me?.email]);
|
||||
|
||||
// After reset, force RHF values once (covers early case)
|
||||
useEffect(() => {
|
||||
|
|
@ -692,7 +713,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
const bool = (v: any) => !!v;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// SUBMIT: Build clean Directus payload + include route metadata
|
||||
// SUBMIT: Match prod envelope → ALWAYS multipart with "payload"
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
async function onSubmit(values: any) {
|
||||
setSubmitErr(null);
|
||||
|
|
@ -705,13 +726,9 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
return;
|
||||
}
|
||||
|
||||
// map UI target -> backend slug as helper
|
||||
const target_slug = typeForOptions;
|
||||
|
||||
// full UI payload (same shape the form uses)
|
||||
const fullPayload: any = {
|
||||
target,
|
||||
target_slug,
|
||||
target, // kept top-level for route parity
|
||||
setting_title: values.setting_title,
|
||||
setting_notes: values.setting_notes || "",
|
||||
mat: values.mat || null,
|
||||
|
|
@ -724,6 +741,13 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
focus: num(values.focus),
|
||||
laser_soft: values.laser_soft || null, // all targets
|
||||
repeat_all: num(values.repeat_all), // all targets
|
||||
|
||||
// new/extra fields
|
||||
uploader: (me?.username ?? me?.email ?? "") || "",
|
||||
lens_conf: num(values.lens_conf),
|
||||
lens_apt: num(values.lens_apt),
|
||||
lens_exp: num(values.lens_exp),
|
||||
|
||||
fill_settings: (values.fill_settings || []).map((r: any) => ({
|
||||
name: r.name || "",
|
||||
power: num(r.power),
|
||||
|
|
@ -745,8 +769,8 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
power: num(r.power),
|
||||
speed: num(r.speed),
|
||||
perf: bool(r.perf),
|
||||
cut: r.cut || "",
|
||||
skip: r.skip || "",
|
||||
cut: bool(r.cut),
|
||||
skip: bool(r.skip),
|
||||
pass: num(r.pass),
|
||||
air: bool(r.air),
|
||||
frequency: num(r.frequency),
|
||||
|
|
@ -774,7 +798,7 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
})),
|
||||
};
|
||||
|
||||
// ✅ Exact Directus data object (whitelisted fields only, empty strings removed)
|
||||
// Whitelist to match collection fields and drop empty strings
|
||||
const directusData = toDirectusData(target, fullPayload);
|
||||
|
||||
// Early guard for the common required field
|
||||
|
|
@ -783,52 +807,29 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Edit meta travels outside `data`
|
||||
const meta: any = {};
|
||||
if (isEdit && edit?.submissionId != null) {
|
||||
meta.mode = "edit";
|
||||
meta.submission_id = edit.submissionId;
|
||||
}
|
||||
// Build prod-compatible flat payload and ALWAYS send multipart
|
||||
const base = isEdit && edit?.submissionId
|
||||
? { target, mode: "edit" as const, submission_id: edit.submissionId }
|
||||
: { target };
|
||||
|
||||
const flatPayload = {
|
||||
...base,
|
||||
...directusData,
|
||||
// Keep top-level setting_title for route validators
|
||||
setting_title: directusData.setting_title,
|
||||
};
|
||||
|
||||
try {
|
||||
let res: Response;
|
||||
const form = new FormData();
|
||||
form.set("payload", JSON.stringify(flatPayload)); // << prod-compatible key
|
||||
if (photoFile) form.set("photo", photoFile, photoFile.name || "photo");
|
||||
if (screenFile) form.set("screen", screenFile, screenFile.name || "screen");
|
||||
|
||||
if (photoFile || screenFile) {
|
||||
// multipart: pack everything your route needs under one JSON field
|
||||
const form = new FormData();
|
||||
form.set(
|
||||
"data",
|
||||
JSON.stringify({
|
||||
target,
|
||||
target_slug,
|
||||
...meta,
|
||||
data: directusData,
|
||||
// 🔑 Compat for API route validators that expect top-level title
|
||||
setting_title: directusData.setting_title,
|
||||
})
|
||||
);
|
||||
// 🔑 Also add a flat field for extreme route handlers that read form fields directly
|
||||
form.set("setting_title", String(directusData.setting_title || ""));
|
||||
if (photoFile) form.set("photo", photoFile, photoFile.name || "photo");
|
||||
if (screenFile) form.set("screen", screenFile, screenFile.name || "screen");
|
||||
|
||||
res = await fetch("/api/submit/settings", { method: "POST", body: form, credentials: "include" });
|
||||
} else {
|
||||
// JSON parity with multipart
|
||||
res = await fetch("/api/submit/settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
target,
|
||||
target_slug,
|
||||
...meta,
|
||||
data: directusData,
|
||||
// 🔑 Compat for API route validators
|
||||
setting_title: directusData.setting_title,
|
||||
}),
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
const res = await fetch("/api/submit/settings", {
|
||||
method: "POST",
|
||||
body: form,
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
|
|
@ -921,6 +922,9 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
<div className="border border-red-500 text-red-600 bg-red-50 rounded p-2 text-sm">{submitErr}</div>
|
||||
) : null}
|
||||
|
||||
{/* hidden uploader so it's always sent */}
|
||||
<input type="hidden" {...register("uploader", { required: true })} value={me?.username ?? me?.email ?? ""} />
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
{/* Title */}
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
|
|
@ -1065,6 +1069,22 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{/* Lens Configuration (target-specific requireds) */}
|
||||
{(target === "settings_co2gan" || target === "settings_co2gal") && (
|
||||
<fieldset className="border rounded p-3 space-y-2">
|
||||
<legend className="font-semibold">Lens Configuration</legend>
|
||||
<div className="grid md:grid-cols-3 gap-3">
|
||||
<LabeledInput label="Lens Conf" name="lens_conf" type="number" step="1" register={register} required />
|
||||
{target === "settings_co2gal" && (
|
||||
<>
|
||||
<LabeledInput label="Lens Aperture" name="lens_apt" type="number" step="1" register={register} required />
|
||||
<LabeledInput label="Lens Expansion" name="lens_exp" type="number" step="1" register={register} required />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</fieldset>
|
||||
)}
|
||||
|
||||
{/* FILL */}
|
||||
<fieldset className="border rounded p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -1124,9 +1144,9 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
<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="Speed (mm/s)" name={`line_settings.${i}.speed`} type="number" step="0.1" register={register} />
|
||||
<LabeledInput label="Perf" name={`line_settings.${i}.perf`} register={register} />
|
||||
<LabeledInput label="Cut" name={`line_settings.${i}.cut`} register={register} />
|
||||
<LabeledInput label="Skip" name={`line_settings.${i}.skip`} register={register} />
|
||||
<BoolBox label="Perf" name={`line_settings.${i}.perf`} register={register} />
|
||||
<BoolBox label="Cut" name={`line_settings.${i}.cut`} 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="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} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue