diff --git a/app/settings/co2-galvo/page.tsx b/app/settings/co2-galvo/page.tsx index 1cc344f8..a9009834 100644 --- a/app/settings/co2-galvo/page.tsx +++ b/app/settings/co2-galvo/page.tsx @@ -5,6 +5,13 @@ import { useSearchParams } from "next/navigation"; import Link from "next/link"; import Image from "next/image"; +type Owner = { + id?: string | number; + first_name?: string | null; + last_name?: string | null; + email?: string | null; +}; + export default function CO2GalvoSettingsPage() { const searchParams = useSearchParams(); const initialQuery = searchParams.get("query") || ""; @@ -23,15 +30,15 @@ export default function CO2GalvoSettingsPage() { useEffect(() => { const url = - `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_co2gal?fields=` + + `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_co2gal` + + `?fields=` + [ "submission_id", "setting_title", "uploader", - "owner.display_name", + "owner.id", "owner.first_name", "owner.last_name", - "owner.username", "owner.email", "photo.id", "photo.title", @@ -39,32 +46,28 @@ export default function CO2GalvoSettingsPage() { "mat_coat.name", "source.model", "lens.field_size", - "lens.name", ].join(",") + - "&limit=-1"; + `&limit=-1`; fetch(url, { cache: "no-store" }) - .then((res) => res.json()) - .then((data) => { - setSettings(data?.data || []); - setLoading(false); + .then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); }) - .catch(() => setLoading(false)); + .then((data) => setSettings(data?.data || [])) + .catch((e) => { + console.error("CO2 Galvo settings fetch failed:", e); + setSettings([]); + }) + .finally(() => setLoading(false)); }, []); - const ownerName = (owner?: any) => { - if (!owner) return "—"; - return ( - owner.display_name || - [owner.first_name, owner.last_name].filter(Boolean).join(" ").trim() || - owner.username || - owner.email || - "—" - ); + const ownerLabel = (o?: Owner) => { + if (!o) return "—"; + const name = [o.first_name, o.last_name].filter(Boolean).join(" ").trim(); + return name || o.email || "—"; }; - const lensLabel = (row: any) => row?.lens?.field_size ?? row?.lens?.name ?? "—"; - const highlight = (text?: string) => { if (!debouncedQuery) return text || ""; const regex = new RegExp(`(${debouncedQuery})`, "gi"); @@ -76,12 +79,12 @@ export default function CO2GalvoSettingsPage() { return settings.filter((entry) => { const fieldsToSearch = [ entry.setting_title, - ownerName(entry.owner), + ownerLabel(entry.owner), entry.uploader, entry.mat?.name, entry.mat_coat?.name, entry.source?.model, - lensLabel(entry), + entry.lens?.field_size, ]; return fieldsToSearch .filter(Boolean) @@ -89,31 +92,30 @@ export default function CO2GalvoSettingsPage() { }); }, [settings, debouncedQuery]); - // Stats - const totalSettings = settings.length; + const total = settings.length; const uniqueMaterials = new Set(settings.map((s) => s.mat?.name).filter(Boolean)).size; - const commonLens = settings.reduce((acc: Record, cur) => { - const l = lensLabel(cur); - if (!l || l === "—") return acc; - acc[l] = (acc[l] || 0) + 1; + const lensCounts = settings.reduce((acc: Record, cur) => { + const v = cur.lens?.field_size; + if (!v) return acc; + acc[v] = (acc[v] || 0) + 1; return acc; }, {}); const mostCommonLens = - Object.entries(commonLens).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || + Object.entries(lensCounts).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—"; -const sourceModels = settings.reduce((acc: Record, cur) => { - const model = cur.source?.model; - if (!model) return acc; - acc[model] = (acc[model] || 0) + 1; +const srcCounts = settings.reduce((acc: Record, cur) => { + const v = cur.source?.model; + if (!v) return acc; + acc[v] = (acc[v] || 0) + 1; return acc; }, {}); const mostCommonSource = -Object.entries(sourceModels).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || +Object.entries(srcCounts).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—"; -const recentSettings = [...settings] +const recent = [...settings] .sort((a, b) => Number(b.submission_id) - Number(a.submission_id)) .slice(0, 5); @@ -128,7 +130,7 @@ return ( } `} - {/* Header + Search */} + {/* Header / Search */}

CO₂ Galvo Settings

@@ -136,7 +138,7 @@ return ( type="search" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search by material, owner, uploader, model, etc…" + placeholder="Search by material, owner, uploader, model, lens…" className="w-full mb-4 dark:bg-background border border-border rounded-md p-2" />

@@ -144,70 +146,42 @@ return (

+ {/* How to use */}

How to Use

- Browse community CO₂ galvo settings. Use the search to narrow results. Click any title - to view the full configuration, notes, and photos. + Browse community CO₂ galvo settings. Use search to narrow results. Click a row to view full configuration, + notes, and photos.

+ {/* Stats */}

Stats Summary

    -
  • Total Settings: {totalSettings}
  • +
  • Total Settings: {total}
  • Unique Materials: {uniqueMaterials}
  • Most Common Lens: {mostCommonLens}
  • Most Used Source: {mostCommonSource}
-
+ {/* Recently Added */} +

Recently Added

    - {recentSettings.map((s) => ( + {recent.map((s) => (
  • {s.setting_title || "Untitled"} {" "} - by {ownerName(s.owner) !== "—" ? ownerName(s.owner) : s.uploader || "—"} + by {ownerLabel(s.owner)}{s.uploader ? ` (uploader: ${s.uploader})` : ""}
  • ))}
- -
{/* Table */} @@ -231,59 +205,54 @@ return ( - {filtered.map((setting) => { - const ownerText = ownerName(setting.owner); - return ( - - - {setting.photo?.id ? ( - {setting.photo.title - ) : ( - "—" - )} - - - ( + + + {s.photo?.id ? ( + {s.photo.title - - - - - - - - - ); - })} + ) : ( + "—" + )} + + + + + + + + + + + + ))}
diff --git a/app/settings/co2-gantry/page.tsx b/app/settings/co2-gantry/page.tsx index 49c61b12..b3517587 100644 --- a/app/settings/co2-gantry/page.tsx +++ b/app/settings/co2-gantry/page.tsx @@ -5,6 +5,13 @@ import { useSearchParams } from "next/navigation"; import Link from "next/link"; import Image from "next/image"; +type Owner = { + id?: string | number; + first_name?: string | null; + last_name?: string | null; + email?: string | null; +}; + export default function CO2GantrySettingsPage() { const searchParams = useSearchParams(); const initialQuery = searchParams.get("query") || ""; @@ -23,48 +30,45 @@ export default function CO2GantrySettingsPage() { useEffect(() => { const url = - `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_co2gan?fields=` + + `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_co2gan` + + `?fields=` + [ "submission_id", "setting_title", "uploader", - "owner.display_name", + "owner.id", "owner.first_name", "owner.last_name", - "owner.username", "owner.email", "photo.id", "photo.title", "mat.name", "mat_coat.name", "source.model", - "lens.field_size", + // NOTE: gantry uses lens.name (not field_size) "lens.name", ].join(",") + - "&limit=-1"; + `&limit=-1`; fetch(url, { cache: "no-store" }) - .then((res) => res.json()) - .then((data) => { - setSettings(data?.data || []); - setLoading(false); + .then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); }) - .catch(() => setLoading(false)); + .then((data) => setSettings(data?.data || [])) + .catch((e) => { + console.error("CO2 Gantry settings fetch failed:", e); + setSettings([]); + }) + .finally(() => setLoading(false)); }, []); - const ownerName = (owner?: any) => { - if (!owner) return "—"; - return ( - owner.display_name || - [owner.first_name, owner.last_name].filter(Boolean).join(" ").trim() || - owner.username || - owner.email || - "—" - ); + const ownerLabel = (o?: Owner) => { + if (!o) return "—"; + const name = [o.first_name, o.last_name].filter(Boolean).join(" ").trim(); + return name || o.email || "—"; }; - const lensLabel = (row: any) => row?.lens?.field_size ?? row?.lens?.name ?? "—"; - const highlight = (text?: string) => { if (!debouncedQuery) return text || ""; const regex = new RegExp(`(${debouncedQuery})`, "gi"); @@ -76,12 +80,12 @@ export default function CO2GantrySettingsPage() { return settings.filter((entry) => { const fieldsToSearch = [ entry.setting_title, - ownerName(entry.owner), - entry.uploader, + ownerLabel(entry.owner), + entry.uploader, // keep legacy column visible for now entry.mat?.name, entry.mat_coat?.name, entry.source?.model, - lensLabel(entry), + entry.lens?.name, ]; return fieldsToSearch .filter(Boolean) @@ -89,31 +93,30 @@ export default function CO2GantrySettingsPage() { }); }, [settings, debouncedQuery]); - // Stats - const totalSettings = settings.length; + const total = settings.length; const uniqueMaterials = new Set(settings.map((s) => s.mat?.name).filter(Boolean)).size; - const commonLens = settings.reduce((acc: Record, cur) => { - const l = lensLabel(cur); - if (!l || l === "—") return acc; - acc[l] = (acc[l] || 0) + 1; + const lensCounts = settings.reduce((acc: Record, cur) => { + const v = cur.lens?.name; + if (!v) return acc; + acc[v] = (acc[v] || 0) + 1; return acc; }, {}); const mostCommonLens = - Object.entries(commonLens).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || + Object.entries(lensCounts).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—"; -const sourceModels = settings.reduce((acc: Record, cur) => { - const model = cur.source?.model; - if (!model) return acc; - acc[model] = (acc[model] || 0) + 1; +const srcCounts = settings.reduce((acc: Record, cur) => { + const v = cur.source?.model; + if (!v) return acc; + acc[v] = (acc[v] || 0) + 1; return acc; }, {}); const mostCommonSource = -Object.entries(sourceModels).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || +Object.entries(srcCounts).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—"; -const recentSettings = [...settings] +const recent = [...settings] .sort((a, b) => Number(b.submission_id) - Number(a.submission_id)) .slice(0, 5); @@ -128,7 +131,7 @@ return ( } `} - {/* Header + Search */} + {/* Header / Search */}

CO₂ Gantry Settings

@@ -136,7 +139,7 @@ return ( type="search" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search by material, owner, uploader, model, etc…" + placeholder="Search by material, owner, uploader, model, lens…" className="w-full mb-4 dark:bg-background border border-border rounded-md p-2" />

@@ -144,40 +147,35 @@ return (

-
-

How to Use

-

- Browse real-world CO₂ gantry settings. Use the search to narrow results. Click any title - to view the full configuration, notes, and photos. -

-
- + {/* Stats */}

Stats Summary

    -
  • Total Settings: {totalSettings}
  • +
  • Total Settings: {total}
  • Unique Materials: {uniqueMaterials}
  • Most Common Lens: {mostCommonLens}
  • Most Used Source: {mostCommonSource}
+ {/* Recently Added */}

Recently Added

    - {recentSettings.map((s) => ( + {recent.map((s) => (
  • {s.setting_title || "Untitled"} {" "} - by {ownerName(s.owner) !== "—" ? ownerName(s.owner) : s.uploader || "—"} + by {ownerLabel(s.owner)}{s.uploader ? ` (uploader: ${s.uploader})` : ""}
  • ))}
+ {/* Resources */}

Resources

    @@ -197,12 +195,7 @@ return (
  • - + JPT Datasheets
  • @@ -214,7 +207,7 @@ return ( {loading ? (

    Loading settings...

    ) : filtered.length === 0 ? ( -

    No CO₂ gantry settings found.

    +

    No gantry settings found.

    ) : (
    @@ -231,13 +224,13 @@ return ( - {filtered.map((setting) => ( - + {filtered.map((s) => ( + ))} diff --git a/app/settings/uv/page.tsx b/app/settings/uv/page.tsx index c0a541ef..fe3d7831 100644 --- a/app/settings/uv/page.tsx +++ b/app/settings/uv/page.tsx @@ -5,6 +5,13 @@ import { useSearchParams } from "next/navigation"; import Link from "next/link"; import Image from "next/image"; +type Owner = { + id?: string | number; + first_name?: string | null; + last_name?: string | null; + email?: string | null; +}; + export default function UVSettingsPage() { const searchParams = useSearchParams(); const initialQuery = searchParams.get("query") || ""; @@ -23,15 +30,16 @@ export default function UVSettingsPage() { useEffect(() => { const url = - `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_uv?fields=` + + `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_uv` + + `?fields=` + [ "submission_id", "setting_title", "uploader", - "owner.display_name", + // owner (M2O) – minimal, safe fields + "owner.id", "owner.first_name", "owner.last_name", - "owner.username", "owner.email", "photo.id", "photo.title", @@ -39,32 +47,28 @@ export default function UVSettingsPage() { "mat_coat.name", "source.model", "lens.field_size", - "lens.name", ].join(",") + - "&limit=-1"; + `&limit=-1`; fetch(url, { cache: "no-store" }) - .then((res) => res.json()) - .then((data) => { - setSettings(data?.data || []); - setLoading(false); + .then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); }) - .catch(() => setLoading(false)); + .then((data) => setSettings(data?.data || [])) + .catch((e) => { + console.error("UV settings fetch failed:", e); + setSettings([]); + }) + .finally(() => setLoading(false)); }, []); - const ownerName = (owner?: any) => { - if (!owner) return "—"; - return ( - owner.display_name || - [owner.first_name, owner.last_name].filter(Boolean).join(" ").trim() || - owner.username || - owner.email || - "—" - ); + const ownerLabel = (o?: Owner) => { + if (!o) return "—"; + const name = [o.first_name, o.last_name].filter(Boolean).join(" ").trim(); + return name || o.email || "—"; }; - const lensLabel = (row: any) => row?.lens?.field_size ?? row?.lens?.name ?? "—"; - const highlight = (text?: string) => { if (!debouncedQuery) return text || ""; const regex = new RegExp(`(${debouncedQuery})`, "gi"); @@ -76,12 +80,12 @@ export default function UVSettingsPage() { return settings.filter((entry) => { const fieldsToSearch = [ entry.setting_title, - ownerName(entry.owner), + ownerLabel(entry.owner), entry.uploader, entry.mat?.name, entry.mat_coat?.name, entry.source?.model, - lensLabel(entry), + entry.lens?.field_size, ]; return fieldsToSearch .filter(Boolean) @@ -89,31 +93,30 @@ export default function UVSettingsPage() { }); }, [settings, debouncedQuery]); - // Stats - const totalSettings = settings.length; + const total = settings.length; const uniqueMaterials = new Set(settings.map((s) => s.mat?.name).filter(Boolean)).size; - const commonLens = settings.reduce((acc: Record, cur) => { - const l = lensLabel(cur); - if (!l || l === "—") return acc; - acc[l] = (acc[l] || 0) + 1; + const lensCounts = settings.reduce((acc: Record, cur) => { + const v = cur.lens?.field_size; + if (!v) return acc; + acc[v] = (acc[v] || 0) + 1; return acc; }, {}); const mostCommonLens = - Object.entries(commonLens).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || + Object.entries(lensCounts).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—"; -const sourceModels = settings.reduce((acc: Record, cur) => { - const model = cur.source?.model; - if (!model) return acc; - acc[model] = (acc[model] || 0) + 1; +const srcCounts = settings.reduce((acc: Record, cur) => { + const v = cur.source?.model; + if (!v) return acc; + acc[v] = (acc[v] || 0) + 1; return acc; }, {}); const mostCommonSource = -Object.entries(sourceModels).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || +Object.entries(srcCounts).sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—"; -const recentSettings = [...settings] +const recent = [...settings] .sort((a, b) => Number(b.submission_id) - Number(a.submission_id)) .slice(0, 5); @@ -128,7 +131,7 @@ return ( } `} - {/* Header + Search */} + {/* Header / Search */}

    UV Laser Settings

    @@ -136,78 +139,50 @@ return ( type="search" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search by material, owner, uploader, model, etc…" + placeholder="Search by material, owner, uploader, model, lens…" className="w-full mb-4 dark:bg-background border border-border rounded-md p-2" />

    - View and explore detailed UV settings with context. + View and explore detailed UV laser settings with context.

    + {/* How to use */}

    How to Use

    - Browse community UV laser settings. Use search to narrow results. Click any - title to view full configuration, notes, and photos. + Browse community UV settings. Use search to narrow results. Click a row to view full configuration, + notes, and photos.

    + {/* Stats */}

    Stats Summary

      -
    • Total Settings: {totalSettings}
    • +
    • Total Settings: {total}
    • Unique Materials: {uniqueMaterials}
    • Most Common Lens: {mostCommonLens}
    • Most Used Source: {mostCommonSource}
    -
    + {/* Recently Added */} +

    Recently Added

      - {recentSettings.map((s) => ( + {recent.map((s) => (
    • {s.setting_title || "Untitled"} {" "} - by {ownerName(s.owner) !== "—" ? ownerName(s.owner) : s.uploader || "—"} + by {ownerLabel(s.owner)}{s.uploader ? ` (uploader: ${s.uploader})` : ""}
    • ))}
    - -
    {/* Table */} @@ -231,59 +206,54 @@ return (
    - {filtered.map((setting) => { - const ownerText = ownerName(setting.owner); - return ( - - - + - - ); - })} + ) : ( + "—" + )} + + + + ))}
    - {setting.photo?.id ? ( + {s.photo?.id ? ( {setting.photo.title
    - {setting.photo?.id ? ( - {setting.photo.title - ) : ( - "—" - )} - - ( +
    + {s.photo?.id ? ( + {s.photo.title - - - - - - -
    + + + + + + + +