source and lens routing bugs fixed

This commit is contained in:
Alexander Sellite 2025-09-22 13:47:00 -04:00
parent 793f848bfe
commit d8f613b161
3 changed files with 139 additions and 82 deletions

View file

@ -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<string, MapEntry> = {
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 });
}
}

View file

@ -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 });
}
}

View file

@ -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 });
}
}