From f38ffef984b676c0e60faaf53aca552ab02100f3 Mon Sep 17 00:00:00 2001 From: makearmy Date: Mon, 22 Sep 2025 15:15:06 -0400 Subject: [PATCH] lens focal distance bug fixes --- app/api/options/lens/route.ts | 172 +++++++++++++++------------------- 1 file changed, 78 insertions(+), 94 deletions(-) diff --git a/app/api/options/lens/route.ts b/app/api/options/lens/route.ts index 1c75a44e..6a3c339e 100644 --- a/app/api/options/lens/route.ts +++ b/app/api/options/lens/route.ts @@ -1,117 +1,101 @@ // app/api/options/lens/route.ts -import { NextResponse } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; import { directusFetch } from "@/lib/directus"; -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" as const; // has field_size + focal_length - case "settings_co2gan": - return "laser_focus_lens" as const; // has name (no focal_length) - default: - return null; +/** + * Parse "110x110", "110×110", "110 X 110", etc. -> [110, 110] + * Returns null if we can't find two numbers. + */ +function parseFieldSize(s: unknown): [number, number] | null { + if (!s) return null; + const nums = String(s).match(/\d+(\.\d+)?/g)?.map((n) => Number(n)) ?? []; + if (nums.length >= 2 && Number.isFinite(nums[0]) && Number.isFinite(nums[1])) { + return [nums[0], nums[1]]; } + 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); +/** Pull a clean integer from a value like "F160", "160", "160mm" */ +function parseFocalLength(v: unknown): number | null { + if (v == null) return null; + const m = String(v).match(/\d+(\.\d+)?/); 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 n = Number(m[0]); + return Number.isFinite(n) ? Math.round(n) : null; } -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)}`; +/** Natural-ish compare for scan lens sizes: sort by width then height */ +function sizeCompare(a: string, b: string): number { + const as = parseFieldSize(a); + const bs = parseFieldSize(b); + if (!as && !bs) return String(a).localeCompare(String(b)); + if (!as) return 1; + if (!bs) return -1; + // width, then height + if (as[0] !== bs[0]) return as[0] - bs[0]; + if (as[1] !== bs[1]) return as[1] - bs[1]; + return 0; +} -export async function GET(req: Request) { +export async function GET(req: NextRequest) { try { const { searchParams } = new URL(req.url); - const target = searchParams.get("target") || undefined; - const q = (searchParams.get("q") || "").toLowerCase().trim(); + const target = searchParams.get("target") || ""; + const q = (searchParams.get("q") || "").trim().toLowerCase(); const limit = Number(searchParams.get("limit") || "500"); - const coll = collectionForTarget(target); - if (!coll) return NextResponse.json({ data: [] }); + const isGantry = target === "settings_co2gan"; - 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) - )}` - ); + if (isGantry) { + // CO2 Gantry -> FOCUS lenses + const url = `/items/laser_focus_lens?fields=id,name&limit=${limit}`; + const { data } = await directusFetch<{ data: Array<{ id: string | number; name?: string }> }>(url); + let rows = (data ?? []).map((r) => ({ + id: String(r.id), + label: r.name?.trim() || String(r.id), + _key: r.name?.toLowerCase() ?? "", + })); - 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); + if (q) rows = rows.filter((r) => r.label.toLowerCase().includes(q)); + rows.sort((a, b) => a.label.localeCompare(b.label)); - const area = - dim && Number.isFinite(dim.w) && Number.isFinite(dim.h) - ? dim.w * dim.h - : Number.POSITIVE_INFINITY; - - 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 filtered = q ? rows.filter((r) => r._search.includes(q)) : rows; - - 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); - }); - - return NextResponse.json({ - data: filtered.map(({ id, label }) => ({ id, label })), - }); + return NextResponse.json({ data: rows.map(({ id, label }) => ({ id, label })) }); } - // CO2 Gantry → focus lenses only have "name" - const { data } = await directusFetch<{ data: any[] }>( - `/items/${coll}?fields=id,name&limit=${encodeURIComponent(String(limit))}` - ); + // Galvo/UV/Fiber -> SCAN lenses with field_size + focal_length + const url = `/items/laser_scan_lens?fields=id,field_size,focal_length&limit=${limit}`; + const { data } = await directusFetch<{ + data: Array<{ id: string | number; field_size?: string; focal_length?: string | number }>; + }>(url); - const rows = (data ?? []).map((r) => ({ - id: String(r.id), - label: String(r.name ?? r.id), - _search: String(r.name ?? r.id).toLowerCase(), - })); - - 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 })), + let rows = (data ?? []).map((r) => { + const sizeTxt = (r.field_size ?? "").toString().trim(); // e.g. "110x110" + const fmm = parseFocalLength(r.focal_length); // e.g. 160 + // Build label: "110x110mm (F160)" or "110x110mm" if focal length missing + const label = + sizeTxt + ? `${sizeTxt}mm${fmm != null ? ` (F${fmm})` : ""}` + : `${r.id}`; + return { + id: String(r.id), + label, + _size: sizeTxt, + }; }); - } catch (err: any) { - console.error("[options/lens] error:", err?.message || err); - return NextResponse.json({ data: [] }, { status: 200 }); + + if (q) { + const qq = q.toLowerCase(); + rows = rows.filter((r) => r.label.toLowerCase().includes(qq)); + } + + rows.sort((a, b) => { + const c = sizeCompare(a._size, b._size); + return c !== 0 ? c : a.label.localeCompare(b.label); + }); + + return NextResponse.json({ data: rows.map(({ id, label }) => ({ id, label })) }); + } catch (e: any) { + console.error("[options/lens] error:", e?.message || e); + return NextResponse.json({ error: e?.message || "Internal error" }, { status: 500 }); } }