diff --git a/app/api/options/lens/route.ts b/app/api/options/lens/route.ts index b5fcc97e..1c75a44e 100644 --- a/app/api/options/lens/route.ts +++ b/app/api/options/lens/route.ts @@ -2,22 +2,42 @@ import { NextResponse } from "next/server"; import { directusFetch } from "@/lib/directus"; -/** Map target to the correct lens collection */ -function collectionForTarget( - target?: string -): "laser_scan_lens" | "laser_focus_lens" | null { - switch (target) { +type Target = +| "settings_fiber" +| "settings_uv" +| "settings_co2gal" +| "settings_co2gan"; + +/** Map target -> Directus collection */ +function collectionForTarget(t?: string) { + switch ((t ?? "") as Target) { case "settings_fiber": case "settings_uv": case "settings_co2gal": - return "laser_scan_lens"; + return "laser_scan_lens" as const; // has field_size + focal_length case "settings_co2gan": - return "laser_focus_lens"; + return "laser_focus_lens" as const; // has name (no focal_length) default: return null; } } +/** Parse "110x110", "110×110", "110 x 110", or single "110" => {w,h} */ +function parseFieldSize(raw: unknown): { w: number; h: number } | null { + if (raw == null) return null; + const s = String(raw).trim(); + const m = s.match(/(\d+(?:\.\d+)?)(?:\s*[x×]\s*(\d+(?:\.\d+)?))?/i); + if (!m) return null; + const w = Number(m[1]); + const h = m[2] ? Number(m[2]) : w; + if (!Number.isFinite(w) || !Number.isFinite(h)) return null; + return { w, h }; +} + +const fmtNum = (n: number) => +Number.isInteger(n) ? String(n) : String(n).replace(/\.0+$/, ""); +const dimsText = (d: { w: number; h: number }) => `${fmtNum(d.w)}x${fmtNum(d.h)}`; + export async function GET(req: Request) { try { const { searchParams } = new URL(req.url); @@ -28,65 +48,70 @@ export async function GET(req: Request) { const coll = collectionForTarget(target); if (!coll) return NextResponse.json({ data: [] }); - // IMPORTANT: only request fields that exist on each collection. - const fields = - coll === "laser_scan_lens" - ? "id,field_size,focal_length" - : "id,focal_length"; + if (coll === "laser_scan_lens") { + // fiber / uv / co2gal → scan lenses have field_size + focal_length + const { data } = await directusFetch<{ data: any[] }>( + `/items/${coll}?fields=id,field_size,focal_length&limit=${encodeURIComponent( + String(limit) + )}` + ); - const url = `/items/${coll}?fields=${encodeURIComponent( - fields - )}&limit=${limit}`; + const rows = (data ?? []).map((r) => { + const dim = parseFieldSize(r.field_size); + const fnumRaw = r.focal_length; + const label = + dim && fnumRaw != null && fnumRaw !== "" + ? `${dimsText(dim)} (F${fmtNum(Number(fnumRaw))})` + : dim + ? dimsText(dim) + : String(r.field_size ?? r.id); - const { data } = await directusFetch<{ data: any[] }>(url); - const list = Array.isArray(data) ? data : []; + const area = + dim && Number.isFinite(dim.w) && Number.isFinite(dim.h) + ? dim.w * dim.h + : Number.POSITIVE_INFINITY; - const items = list.map((x) => { - const id = String(x?.id); + return { + id: String(r.id), + label, + _sort: { area, w: dim?.w ?? Number.POSITIVE_INFINITY, h: dim?.h ?? Number.POSITIVE_INFINITY }, + _search: `${r.field_size ?? ""} ${r.focal_length ?? ""} ${label}`.toLowerCase(), + }; + }); - const fieldSize = - x?.field_size !== null && x?.field_size !== undefined - ? String(x.field_size).trim() - : ""; + const filtered = q ? rows.filter((r) => r._search.includes(q)) : rows; - const fnum = Number(x?.focal_length); - const focalTxt = Number.isFinite(fnum) ? `F${fnum} mm` : ""; + filtered.sort((a, b) => { + if (a._sort.area !== b._sort.area) return a._sort.area - b._sort.area; + if (a._sort.w !== b._sort.w) return a._sort.w - b._sort.w; + if (a._sort.h !== b._sort.h) return a._sort.h - b._sort.h; + return a.label.localeCompare(b.label); + }); - // Requested order: field_size first, then focal length - let label = - coll === "laser_scan_lens" - ? [fieldSize, focalTxt].filter(Boolean).join(" — ") - : focalTxt || id; + return NextResponse.json({ + data: filtered.map(({ id, label }) => ({ id, label })), + }); + } - if (!label) label = id; + // CO2 Gantry → focus lenses only have "name" + const { data } = await directusFetch<{ data: any[] }>( + `/items/${coll}?fields=id,name&limit=${encodeURIComponent(String(limit))}` + ); - const sortKey: number | string = Number.isFinite(fnum) - ? fnum - : label.toLowerCase(); + const rows = (data ?? []).map((r) => ({ + id: String(r.id), + label: String(r.name ?? r.id), + _search: String(r.name ?? r.id).toLowerCase(), + })); - return { - id, - label, - sortKey, - _search: `${id} ${label} ${fieldSize} ${x?.focal_length ?? ""}`.toLowerCase(), - }; - }); - - const filtered = q ? items.filter((m) => m._search.includes(q)) : items; - - filtered.sort((a, b) => { - if (typeof a.sortKey === "number" && typeof b.sortKey === "number") { - return a.sortKey - b.sortKey; - } - return String(a.sortKey).localeCompare(String(b.sortKey)); - }); + const filtered = q ? rows.filter((r) => r._search.includes(q)) : rows; + filtered.sort((a, b) => a.label.localeCompare(b.label)); return NextResponse.json({ data: filtered.map(({ id, label }) => ({ id, label })), }); - } catch (e: any) { - console.error("[options/lens] error:", e?.message || e); - // Fail-soft so the UI doesn’t hang - return NextResponse.json({ data: [] }); + } catch (err: any) { + console.error("[options/lens] error:", err?.message || err); + return NextResponse.json({ data: [] }, { status: 200 }); } }