edit prefill fix for dropdowns
This commit is contained in:
parent
10745a299d
commit
0e66f2323f
1 changed files with 129 additions and 38 deletions
|
|
@ -55,8 +55,22 @@ function shortId(s?: string) {
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Props (Create vs Edit) + type guard
|
||||
/** Normalizers for edit-mode prefill (IDs + enums) */
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
function idToString(v: any): string {
|
||||
if (v == null || v === "") return "";
|
||||
if (typeof v === "object") {
|
||||
if ((v as any).id != null) return String((v as any).id);
|
||||
if ((v as any).submission_id != null) return String((v as any).submission_id);
|
||||
}
|
||||
return String(v);
|
||||
}
|
||||
|
||||
function normalizeEnums(value: any, allowed: string[], fallback: string) {
|
||||
const v = value == null ? "" : String(value).toLowerCase();
|
||||
return allowed.includes(v) ? v : fallback;
|
||||
}
|
||||
|
||||
type BaseProps = { initialTarget?: Target };
|
||||
|
||||
type EditInitialValues = {
|
||||
|
|
@ -84,6 +98,50 @@ type EditInitialValues = {
|
|||
raster_settings?: any[] | null;
|
||||
};
|
||||
|
||||
function normalizeForReset(iv: EditInitialValues) {
|
||||
return {
|
||||
...iv,
|
||||
// Single-selects → string IDs
|
||||
mat: idToString(iv.mat),
|
||||
mat_coat: idToString(iv.mat_coat),
|
||||
mat_color: idToString(iv.mat_color),
|
||||
mat_opacity: idToString(iv.mat_opacity),
|
||||
source: idToString(iv.source),
|
||||
lens: idToString(iv.lens),
|
||||
laser_soft: idToString(iv.laser_soft),
|
||||
|
||||
// Arrays: coerce dropdown-ish fields to internal enum keys
|
||||
fill_settings: (iv.fill_settings ?? []).map((r: any) => ({
|
||||
...r,
|
||||
type: normalizeEnums(r?.type, ["uni", "bi", "offset"], "uni"),
|
||||
})),
|
||||
raster_settings: (iv.raster_settings ?? []).map((r: any) => ({
|
||||
...r,
|
||||
type: normalizeEnums(r?.type, ["uni", "bi", "offset"], "uni"),
|
||||
dither: normalizeEnums(
|
||||
r?.dither,
|
||||
[
|
||||
"threshold",
|
||||
"ordered",
|
||||
"atkinson",
|
||||
"dither",
|
||||
"stucki",
|
||||
"jarvis",
|
||||
"newsprint",
|
||||
"halftone",
|
||||
"sketch",
|
||||
"grayscale",
|
||||
],
|
||||
"threshold"
|
||||
),
|
||||
})),
|
||||
line_settings: (iv.line_settings ?? []).map((r: any) => ({ ...r })),
|
||||
};
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Props (Create vs Edit) + type guard
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
type CreateProps = BaseProps & {
|
||||
mode?: "create";
|
||||
submissionId?: never;
|
||||
|
|
@ -103,11 +161,13 @@ function isEditProps(p: CreateProps | EditProps): p is EditProps {
|
|||
// ─────────────────────────────────────────────────────────────
|
||||
// Options loader (materials, lenses, etc.)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
function useOptions(path: string) {
|
||||
const [opts, setOpts] = useState<Opt[]>([]);
|
||||
function useOptions(path: string, forceIncludeId?: string, opts?: { disableNmFilter?: boolean }) {
|
||||
const [optsState, setOptsState] = useState<Opt[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [q, setQ] = useState("");
|
||||
|
||||
const disableNmFilter = opts?.disableNmFilter === true;
|
||||
|
||||
// helpers for the "laser_source" nm filtering
|
||||
const parseNum = (v: any): number | null => {
|
||||
if (v == null) return null;
|
||||
|
|
@ -153,7 +213,7 @@ function useOptions(path: string) {
|
|||
url = `${API}/items/laser_software?fields=id,name&limit=1000&sort=name`;
|
||||
} else if (rawPath === "laser_source") {
|
||||
url = `${API}/items/laser_source?fields=submission_id,make,model,nm&limit=2000&sort=make,model`;
|
||||
const range = nmRangeFor(target);
|
||||
const range = disableNmFilter ? null : nmRangeFor(target);
|
||||
normalize = (rows) => {
|
||||
const filtered = range
|
||||
? rows.filter((r: any) => {
|
||||
|
|
@ -190,7 +250,7 @@ function useOptions(path: string) {
|
|||
}
|
||||
} else {
|
||||
// unknown path → empty
|
||||
setOpts([]);
|
||||
setOptsState([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -201,21 +261,27 @@ function useOptions(path: string) {
|
|||
const rows = json?.data ?? [];
|
||||
const mapped = normalize(rows);
|
||||
|
||||
// Ensure the currently selected value is present even if filters/pagination miss it
|
||||
let ensured = mapped;
|
||||
if (forceIncludeId && !mapped.some((o: any) => String(o.id) === String(forceIncludeId))) {
|
||||
ensured = [{ id: String(forceIncludeId), label: "(current selection)" }, ...mapped];
|
||||
}
|
||||
|
||||
// client-side text filter
|
||||
const needle = (q || "").trim().toLowerCase();
|
||||
const filtered = needle ? mapped.filter((o) => o.label.toLowerCase().includes(needle)) : mapped;
|
||||
const filtered = needle ? ensured.filter((o) => o.label.toLowerCase().includes(needle)) : ensured;
|
||||
|
||||
if (alive) setOpts(filtered);
|
||||
if (alive) setOptsState(filtered);
|
||||
})()
|
||||
.catch(() => alive && setOpts([]))
|
||||
.catch(() => alive && setOptsState([]))
|
||||
.finally(() => alive && setLoading(false));
|
||||
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, [path, q]);
|
||||
}, [path, q, forceIncludeId, disableNmFilter]);
|
||||
|
||||
return { opts, loading, setQ };
|
||||
return { opts: optsState, loading, setQ };
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
|
@ -332,9 +398,19 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
const isEdit = isEditProps(props);
|
||||
const edit = isEdit ? props : null; // strongly-typed local when edit
|
||||
|
||||
const initialFromQuery = (sp.get("target") as Target) || props.initialTarget || "settings_fiber";
|
||||
// Initialize as CO2-galvo in edit mode (unless explicitly overridden)
|
||||
const initialFromQuery =
|
||||
(sp.get("target") as Target) ||
|
||||
props.initialTarget ||
|
||||
(isEdit ? "settings_co2gal" : "settings_fiber");
|
||||
const [target, setTarget] = useState<Target>(initialFromQuery);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEdit && props.initialTarget && props.initialTarget !== target) {
|
||||
setTarget(props.initialTarget);
|
||||
}
|
||||
}, [isEdit, props.initialTarget, target]);
|
||||
|
||||
// Map collection -> slug used by options selectors
|
||||
const typeForOptions = useMemo(() => {
|
||||
switch (target) {
|
||||
|
|
@ -385,16 +461,24 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
|
||||
const meLabel = me?.username ?? "";
|
||||
|
||||
// For edit-mode, compute normalized current values once to seed option lists
|
||||
const current = useMemo(
|
||||
() => (isEdit && edit?.initialValues ? normalizeForReset(edit.initialValues) : null),
|
||||
[isEdit, edit?.initialValues]
|
||||
);
|
||||
|
||||
// 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 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
|
||||
|
||||
// these two need ?target=
|
||||
const srcs = useOptions(`laser_source?target=${typeForOptions}`);
|
||||
const lens = useOptions(`lens?target=${typeForOptions}`);
|
||||
const srcs = useOptions(`laser_source?target=${typeForOptions}`, current?.source || undefined, {
|
||||
disableNmFilter: isEdit, // show the exact current source even if nm is out-of-range
|
||||
});
|
||||
const lens = useOptions(`lens?target=${typeForOptions}`, current?.lens || undefined);
|
||||
|
||||
// Repeater choice options (LOCAL now, no network)
|
||||
const fillType = { opts: toOpts(FILL_TYPE_OPTIONS), loading: false, setQ: (_: string) => {} };
|
||||
|
|
@ -434,24 +518,25 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
// Prefill the form in edit mode
|
||||
useEffect(() => {
|
||||
if (isEdit && edit?.initialValues) {
|
||||
const iv = normalizeForReset(edit.initialValues);
|
||||
reset({
|
||||
setting_title: edit.initialValues.setting_title ?? "",
|
||||
setting_notes: edit.initialValues.setting_notes ?? "",
|
||||
photo: edit.initialValues.photo ?? null,
|
||||
screen: edit.initialValues.screen ?? null,
|
||||
mat: edit.initialValues.mat ?? "",
|
||||
mat_coat: edit.initialValues.mat_coat ?? "",
|
||||
mat_color: edit.initialValues.mat_color ?? "",
|
||||
mat_opacity: edit.initialValues.mat_opacity ?? "",
|
||||
mat_thickness: edit.initialValues.mat_thickness ?? "",
|
||||
source: edit.initialValues.source ?? "",
|
||||
lens: edit.initialValues.lens ?? "",
|
||||
focus: edit.initialValues.focus ?? "",
|
||||
laser_soft: 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 ?? [],
|
||||
setting_title: iv.setting_title ?? "",
|
||||
setting_notes: iv.setting_notes ?? "",
|
||||
photo: iv.photo ?? null,
|
||||
screen: iv.screen ?? null,
|
||||
mat: iv.mat ?? "",
|
||||
mat_coat: iv.mat_coat ?? "",
|
||||
mat_color: iv.mat_color ?? "",
|
||||
mat_opacity: iv.mat_opacity ?? "",
|
||||
mat_thickness: iv.mat_thickness ?? "",
|
||||
source: iv.source ?? "",
|
||||
lens: iv.lens ?? "",
|
||||
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 ?? [],
|
||||
});
|
||||
}
|
||||
}, [isEdit, edit?.initialValues, reset]);
|
||||
|
|
@ -687,7 +772,9 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{photoFile ? (
|
||||
<>Selected: <span className="font-mono">{photoFile.name}</span></>
|
||||
<>
|
||||
Selected: <span className="font-mono">{photoFile.name}</span>
|
||||
</>
|
||||
) : (
|
||||
"Max 25 MB. JPG/PNG/WebP recommended."
|
||||
)}
|
||||
|
|
@ -711,7 +798,9 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{screenFile ? (
|
||||
<>Selected: <span className="font-mono">{screenFile.name}</span></>
|
||||
<>
|
||||
Selected: <span className="font-mono">{screenFile.name}</span>
|
||||
</>
|
||||
) : (
|
||||
"Max 25 MB. JPG/PNG/WebP recommended."
|
||||
)}
|
||||
|
|
@ -789,7 +878,9 @@ export default function SettingsSubmit(props: CreateProps | EditProps) {
|
|||
<LabeledInput label="Material Thickness (mm)" name="mat_thickness" type="number" step="0.01" register={register} />
|
||||
<LabeledInput label="Focus (mm)" name="focus" type="number" min={-10} max={10} step="1" register={register} required />
|
||||
<LabeledInput label="Repeat All" name="repeat_all" type="number" step="1" register={register} required />
|
||||
<p className="text-xs text-muted-foreground md:col-span-3">0 = in focus. Negative = focus closer. Positive = focus further.</p>
|
||||
<p className="text-xs text-muted-foreground md:col-span-3">
|
||||
0 = in focus. Negative = focus closer. Positive = focus further.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* FILL */}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue