From d8f613b1610c129dc69e8d39faae003cc9463eec Mon Sep 17 00:00:00 2001 From: voyagerxyx Date: Mon, 22 Sep 2025 13:47:00 -0400 Subject: [PATCH] source and lens routing bugs fixed --- app/api/options/[collection]/route.ts | 79 ++++++++++++--------------- app/api/options/laser_source/route.ts | 65 ++++++++++++++++++++++ app/api/options/lens/route.ts | 77 +++++++++++++------------- 3 files changed, 139 insertions(+), 82 deletions(-) create mode 100644 app/api/options/laser_source/route.ts diff --git a/app/api/options/[collection]/route.ts b/app/api/options/[collection]/route.ts index a153c1a2..71d71c06 100644 --- a/app/api/options/[collection]/route.ts +++ b/app/api/options/[collection]/route.ts @@ -1,56 +1,45 @@ -// app/api/options/[collection]/route.ts +// app/app/api/options/[collection]/route.ts import { NextResponse } from "next/server"; import { directusFetch } from "@/lib/directus"; -/** - * Generic option lists resolved by collection name. - * NOTE: laser_source and lens have their own dedicated routes. - */ -const MAP: Record< -string, -{ path: string; fields: string; label: (x: any) => string } -> = { - material: { path: "/items/material", fields: "id,name", label: (x) => x.name }, - material_coating: { path: "/items/material_coating", fields: "id,name", label: (x) => x.name }, - material_color: { path: "/items/material_color", fields: "id,name", label: (x) => x.name }, - material_opacity: { path: "/items/material_opacity", fields: "id,opacity", label: (x) => String(x.opacity) }, - laser_software: { path: "/items/laser_software", fields: "id,name", label: (x) => x.name }, +type MapEntry = { path: string; fields: string; label: (x: any) => string }; + +const MAP: Record = { + material: { path: "/items/material", fields: "id,name", label: (x) => String(x.name ?? x.id) }, + material_coating: { path: "/items/material_coating", fields: "id,name", label: (x) => String(x.name ?? x.id) }, + material_color: { path: "/items/material_color", fields: "id,name", label: (x) => String(x.name ?? x.id) }, + material_opacity: { path: "/items/material_opacity", fields: "id,opacity", label: (x) => String(x.opacity ?? x.id) }, + laser_software: { path: "/items/laser_software", fields: "id,name", label: (x) => String(x.name ?? x.id) }, + // laser_source and lens are handled by dedicated routes }; -export async function GET(req: Request, ctx: any) { - const { searchParams } = new URL(req.url); - const q = searchParams.get("q")?.trim() || ""; - const limit = Number(searchParams.get("limit") || "400"); - - // Use a loose type for the 2nd arg to avoid Next 15's strict route signature check - const key = String(ctx?.params?.collection ?? ""); - - const cfg = MAP[key]; - if (!cfg) { - // Unknown collection → return an empty list (avoids noisy 4xx) - return NextResponse.json({ data: [] }); - } - - // Build Directus URL (dummy base to use URL.searchParams ergonomically) - const u = new URL("http://x" + cfg.path); - u.searchParams.set("fields", cfg.fields); - u.searchParams.set("limit", String(limit)); - if (q) u.searchParams.set("search", q); - +export async function GET(req: Request, ctx: { params: { collection: string } }) { try { - const { data } = await directusFetch<{ data: any[] }>( - u.pathname + "?" + u.searchParams.toString() - ); + const { searchParams } = new URL(req.url); + const key = ctx.params.collection; + const q = (searchParams.get("q") || "").trim().toLowerCase(); + const limit = Number(searchParams.get("limit") || "500"); - const out = (data || []) - .map((it) => ({ id: String(it.id), label: cfg.label(it) ?? String(it.id) })) - .sort((a, b) => a.label.localeCompare(b.label)); + const cfg = MAP[key]; + if (!cfg) { + return NextResponse.json({ error: "unsupported collection" }, { status: 400 }); + } - return NextResponse.json({ data: out }); + const url = `${cfg.path}?fields=${encodeURIComponent(cfg.fields)}&limit=${limit}`; + const { data } = await directusFetch<{ data: any[] }>(url); + const list = Array.isArray(data) ? data : []; + + const mapped = list.map((x) => ({ + id: String(x.id), + label: cfg.label(x), + _s: Object.values(x).join(" ").toLowerCase(), + })); + + const filtered = q ? mapped.filter((m) => m._s.includes(q)) : mapped; + filtered.sort((a, b) => a.label.localeCompare(b.label)); + + return NextResponse.json({ data: filtered.map(({ id, label }) => ({ id, label })) }); } catch (e: any) { - return NextResponse.json( - { error: e?.message || "Directus fetch failed" }, - { status: 500 } - ); + return NextResponse.json({ error: e?.message || "Failed to load options" }, { status: 500 }); } } diff --git a/app/api/options/laser_source/route.ts b/app/api/options/laser_source/route.ts new file mode 100644 index 00000000..cc95be80 --- /dev/null +++ b/app/api/options/laser_source/route.ts @@ -0,0 +1,65 @@ +// app/app/api/options/laser_source/route.ts +import { NextResponse } from "next/server"; +import { directusFetch } from "@/lib/directus"; + +// Parse "nm" that may be stored as a string (e.g., "1064", "1064nm", "1,064") +function parseNm(v: any): number | null { + const s = String(v ?? "").replace(/[^0-9.]/g, ""); + if (!s) return null; + const n = Number(s); + return Number.isFinite(n) ? n : null; +} + +// target → wavelength range (nm) +function nmRangeForTarget(t?: string): [number, number] | null { + switch (t) { + case "settings_fiber": return [1000, 1100]; + case "settings_uv": return [300, 400]; + case "settings_co2gan": + case "settings_co2gal": return [10000, 11000]; + default: return null; + } +} + +export async function GET(req: Request) { + try { + const { searchParams } = new URL(req.url); + const target = searchParams.get("target") || undefined; + const q = (searchParams.get("q") || "").trim().toLowerCase(); + const limit = Number(searchParams.get("limit") || "500"); + + const range = nmRangeForTarget(target); + if (!range) { + return NextResponse.json({ error: "missing/invalid target" }, { status: 400 }); + } + const [lo, hi] = range; + + // Only request fields we can read. laser_source uses submission_id as PK. + const url = `/items/laser_source?fields=submission_id,make,model,nm&limit=${limit}`; + const { data } = await directusFetch<{ data: any[] }>(url); + const list = Array.isArray(data) ? data : []; + + // Filter by nm and optional text query + const filtered = list.filter((x) => { + const nm = parseNm(x.nm); + if (nm === null || nm < lo || nm > hi) return false; + if (!q) return true; + const label = [x.make, x.model].filter(Boolean).join(" ").toLowerCase(); + return label.includes(q); + }); + + // Build labels and sort by make, then model + const out = filtered + .map((x) => ({ + id: String(x.submission_id), // critical: use submission_id, not id + label: [x.make, x.model].filter(Boolean).join(" ") || String(x.submission_id), + sortKey: [x.make ?? "", x.model ?? ""].join(" ").toLowerCase(), + })) + .sort((a, b) => a.sortKey.localeCompare(b.sortKey)) + .map(({ id, label }) => ({ id, label })); + + return NextResponse.json({ data: out }); + } catch (e: any) { + return NextResponse.json({ error: e?.message || "Failed to load laser_source" }, { status: 500 }); + } +} diff --git a/app/api/options/lens/route.ts b/app/api/options/lens/route.ts index 2ae0a217..6dd2cf4e 100644 --- a/app/api/options/lens/route.ts +++ b/app/api/options/lens/route.ts @@ -1,54 +1,57 @@ -// app/api/options/lens/route.ts +// app/app/api/options/lens/route.ts import { NextResponse } from "next/server"; import { directusFetch } from "@/lib/directus"; -/** - * For fiber, co2-galvo, uv → f-theta scan lenses - * For co2-gantry → focusing lenses - */ -function collectionForTarget(target?: string): { coll: string } | null { - switch (target) { - case "settings_fiber": - case "settings_co2gal": - case "settings_uv": - return { coll: "laser_scan_lens" }; - case "settings_co2gan": - return { coll: "laser_focusing_lens" }; - default: - return null; - } -} - -function bestLensLabel(it: any): string { - // Try common fields, then derive something readable - if (it.name) return String(it.name); - if (it.model) return String(it.model); - if (it.focal_length_mm) return `F${it.focal_length_mm} mm`; - return String(it.submission_id ?? it.id ?? "lens"); +// Decide which collection to read based on target +function lensCollectionForTarget(target?: string) { + if (target === "settings_co2gan") return "laser_focus_lens"; // gantry + // fiber, uv, co2gal → scan lenses + return "laser_scan_lens"; } export async function GET(req: Request) { try { const { searchParams } = new URL(req.url); - const q = searchParams.get("q")?.trim() || ""; - const limit = Number(searchParams.get("limit") || "500"); const target = searchParams.get("target") || undefined; + const q = (searchParams.get("q") || "").trim().toLowerCase(); + const limit = Number(searchParams.get("limit") || "500"); - const cfg = collectionForTarget(target); - if (!cfg) return NextResponse.json({ error: "missing/invalid target" }, { status: 400 }); + const coll = lensCollectionForTarget(target); - const url = `/items/${cfg.coll}?limit=${limit}${q ? `&search=${encodeURIComponent(q)}` : ""}`; + // Request both possible PK fields, plus name & focal_length for labels + const url = `/items/${coll}?fields=submission_id,id,name,focal_length&limit=${limit}`; const { data } = await directusFetch<{ data: any[] }>(url); + const list = Array.isArray(data) ? data : []; - const out = (data || []) - .map((it) => ({ - id: String(it.submission_id ?? it.id), - label: bestLensLabel(it), - })) - .sort((a, b) => a.label.localeCompare(b.label)); + const mapped = list.map((x) => { + const id = String(x.submission_id ?? x.id); + const label = + (x.name && String(x.name)) || + (x.focal_length != null ? `F${x.focal_length} mm` : id); + const key = + (x.focal_length != null ? String(x.focal_length).padStart(6, "0") : "") + + " " + + label.toLowerCase(); + return { id, label, key, focal: Number(x.focal_length ?? NaN) }; + }); - return NextResponse.json({ data: out }); + // Optional text filter + const filtered = q + ? mapped.filter((m) => m.label.toLowerCase().includes(q)) + : mapped; + + // Prefer numeric focal_length ordering, fallback to label + filtered.sort((a, b) => { + const aNum = Number.isFinite(a.focal); + const bNum = Number.isFinite(b.focal); + if (aNum && bNum) return a.focal - b.focal; + if (aNum) return -1; + if (bNum) return 1; + return a.key.localeCompare(b.key); + }); + + return NextResponse.json({ data: filtered.map(({ id, label }) => ({ id, label })) }); } catch (e: any) { - return NextResponse.json({ error: e?.message || "lens error" }, { status: 500 }); + return NextResponse.json({ error: e?.message || "Failed to load lens options" }, { status: 500 }); } }