diff --git a/app/settings/fiber/[id]/fiber.tsx b/app/settings/fiber/[id]/fiber.tsx index a826bd02..783adfb8 100644 --- a/app/settings/fiber/[id]/fiber.tsx +++ b/app/settings/fiber/[id]/fiber.tsx @@ -7,49 +7,106 @@ import Markdown from "react-markdown"; export default function FiberSettingDetailPage() { const { id } = useParams(); - const [setting, setSetting] = useState(null); + const [setting, setSetting] = useState(null); const [loading, setLoading] = useState(true); + // claim UI state + const [claimBusy, setClaimBusy] = useState(false); + const [claimMsg, setClaimMsg] = useState(null); + const [claimErr, setClaimErr] = useState(null); + useEffect(() => { - fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_fiber/${id}?fields=submission_id,setting_title,uploader,setting_notes,photo.filename_disk,screen.filename_disk,mat.name,mat_coat.name,mat_color.name,mat_opacity.opacity,mat_thickness,source.make,source.model,lens.field_size,lens.focal_length,focus,laser_soft.name,repeat_all,fill_settings,line_settings,raster_settings` - ) - .then((res) => { - if (!res.ok) throw new Error("Failed to load"); - return res.json(); - }) - .then((data) => setSetting(data.data)) - .catch(() => setSetting(null)) - .finally(() => setLoading(false)); + // include owner + screen in fields + const url = + `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_fiber/${id}` + + `?fields=` + + [ + "submission_id", + "setting_title", + "uploader", + "owner.id", + "owner.first_name", + "owner.last_name", + "owner.email", + "setting_notes", + "photo.filename_disk", + "photo.title", + "screen.filename_disk", + "screen.title", + "mat.name", + "mat_coat.name", + "mat_color.name", + "mat_opacity.opacity", + "mat_thickness", + "source.make", + "source.model", + "lens.field_size", + "lens.focal_length", + "focus", + "laser_soft.name", + "repeat_all", + "fill_settings", + "line_settings", + "raster_settings", + ].join(","); + + fetch(url, { cache: "no-store" }) + .then((res) => { + if (!res.ok) throw new Error("Failed to load"); + return res.json(); + }) + .then((data) => setSetting(data.data)) + .catch(() => setSetting(null)) + .finally(() => setLoading(false)); }, [id]); if (loading) return

Loading setting...

; if (!setting) return

Setting not found.

; - const formatBoolean = (val) => val ? "Enabled" : val === false ? "Disabled" : "—"; + const ownerName = (row: any) => { + const o = row?.owner; + if (!o) return null; + const name = [o.first_name, o.last_name].filter(Boolean).join(" ").trim(); + return name || o.email || null; + }; - const renderRepeaterCard = (title, fields, items) => { - const filtered = (items || []).filter(item => Object.values(item).some(v => v !== null && v !== "")); + const formatBoolean = (val: any) => + val ? "Enabled" : val === false ? "Disabled" : "—"; + + const renderRepeaterCard = (title: string, fields: any[], items: any[]) => { + const filtered = (items || []).filter((item) => + Object.values(item).some((v) => v !== null && v !== "") + ); if (filtered.length === 0) return null; return (
-

{title}

-
- {filtered.map((item, i) => ( -
- {fields.map(({ key, label, condition }) => { - const value = item[key]; - if (condition && !condition(item)) return null; - return

{label}: {typeof value === "boolean" ? formatBoolean(value) : value || "—"}

; - })} -
- ))} +

{title}

+
+ {filtered.map((item, i) => ( +
+ {fields.map(({ key, label, condition }: any) => { + const value = item[key]; + if (condition && !condition(item)) return null; + return ( +

+ {label}:{" "} + {typeof value === "boolean" + ? formatBoolean(value) + : value || "—"} +

+ ); + })}
+ ))} +
); }; - const openSearchInNewTab = (value) => { + const openSearchInNewTab = (value: string) => { const url = new URL("/fiber-settings", window.location.origin); url.searchParams.set("query", value); const anchor = document.createElement("a"); @@ -59,121 +116,282 @@ export default function FiberSettingDetailPage() { anchor.click(); }; + const onClaim = async () => { + setClaimBusy(true); + setClaimErr(null); + setClaimMsg(null); + try { + const r = await fetch("/api/claims", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + target_collection: "settings_fiber", + target_id: id, + }), + cache: "no-store", + }); + const data = await r.json().catch(() => ({})); + if (!r.ok) { + throw new Error( + data?.error || data?.errors?.[0]?.message || "Failed to submit claim" + ); + } + setClaimMsg("Claim request submitted for review."); + } catch (e: any) { + setClaimErr(e?.message || "Failed to submit claim"); + } finally { + setClaimBusy(false); + } + }; + + const owner = ownerName(setting); + return (
-
+
+ {/* Title / Meta / Ownership */}
-
-

{setting.setting_title}

-

Uploaded by: {setting.uploader || "—"}

-
- - ← Back to Fiber Settings - +
+

+ {setting.setting_title} +

+ +
+

+ Owner:{" "} + {owner ? {owner} : } +

+

+ Uploader: {setting.uploader || "—"} +

-
-
- {setting.photo?.filename_disk && ( - - Laser preview - - )} -
-
-

Material

-

Material: openSearchInNewTab(setting.mat?.name)}>{setting.mat?.name || "—"}

-

Coating: openSearchInNewTab(setting.mat_coat?.name)}>{setting.mat_coat?.name || "—"}

-

Color: {setting.mat_color?.name || "—"}

-

Opacity: {setting.mat_opacity?.opacity || "—"}

-

Thickness: {setting.mat_thickness ? `${setting.mat_thickness} mm` : "Not Applicable"}

-
-
-
-

Setup

-

Software: {setting.laser_soft?.name || "—"}

-

Repeat All (global): {setting.repeat_all ?? "—"}

-

Focus: {setting.focus ?? "—"} mm

- -Values Focus Closer | +Values Focus Further -
- -
-

Laser

-

Source Make: {setting.source?.make || "—"}

-

Source Model: openSearchInNewTab(setting.source?.model)}>{setting.source?.model || "—"}

-

Lens: openSearchInNewTab(setting.lens?.field_size)}>{setting.lens?.field_size || "—"} mm | {setting.lens?.focal_length || "—"}

-
-
- - {setting.setting_notes && ( -
-

Notes

- {setting.setting_notes} -
+ {!owner && ( +
+ + {claimMsg && ( + {claimMsg} )} + {claimErr && ( + {claimErr} + )} +
+ )} +
-
+ {/* keep your existing back link for now (external detail view) */} + + ← Back to Fiber Settings + +
- {renderRepeaterCard("Fill Settings", [ + {/* Photo + Material */} +
+
+ {/* Primary photo */} + {setting.photo?.filename_disk && ( + + {setting.photo?.title + + )} + + {/* Screen / Screenshot (NEW visible block) */} + {setting.screen?.filename_disk && ( + + {setting.screen?.title + + )} +
+ +
+

Material

+

+ Material:{" "} + openSearchInNewTab(setting.mat?.name)} + > + {setting.mat?.name || "—"} + +

+

+ Coating:{" "} + openSearchInNewTab(setting.mat_coat?.name)} + > + {setting.mat_coat?.name || "—"} + +

+

+ Color: {setting.mat_color?.name || "—"} +

+

+ Opacity: {setting.mat_opacity?.opacity || "—"} +

+

+ Thickness:{" "} + {setting.mat_thickness ? `${setting.mat_thickness} mm` : "Not Applicable"} +

+
+
+ + {/* Setup */} +
+

Setup

+

+ Software: {setting.laser_soft?.name || "—"} +

+

+ Repeat All (global): {setting.repeat_all ?? "—"} +

+

+ Focus: {setting.focus ?? "—"} mm +

+ -Values Focus Closer | +Values Focus Further +
+ + {/* Laser */} +
+

Laser

+

+ Source Make: {setting.source?.make || "—"} +

+

+ Source Model:{" "} + openSearchInNewTab(setting.source?.model)} + > + {setting.source?.model || "—"} + +

+

+ Lens:{" "} + openSearchInNewTab(setting.lens?.field_size)} + > + {setting.lens?.field_size || "—"} + {" "} + mm | {setting.lens?.focal_length || "—"} +

+
+
+ + {/* Notes */} + {setting.setting_notes && ( +
+

Notes

+ {setting.setting_notes} +
+ )} + +
+ + {/* Fill / Line / Raster */} + {renderRepeaterCard( + "Fill Settings", + [ { key: "fill_name", label: "Fill Name" }, { key: "power", label: "Power (%)" }, - { key: "speed", label: "Speed (mm/s)" }, - { key: "frequency", label: "Frequency (kHz)" }, - { key: "pulse", label: "Pulse Width (ns)" }, - { key: "interval", label: "Interval (mm)" }, - { key: "pass", label: "Passes" }, - { key: "type", label: "Type" }, - { key: "angle", label: "Angle (°)" }, - { key: "auto", label: "Auto-Rotate" }, - { key: "increment", label: "Increment (°)", condition: (e) => e.auto }, - { key: "cross", label: "Crosshatch" }, - { key: "flood", label: "Flood Fill" }, - { key: "air", label: "Air Assist" }, - ], setting.fill_settings)} + { key: "speed", label: "Speed (mm/s)" }, + { key: "frequency", label: "Frequency (kHz)" }, + { key: "pulse", label: "Pulse Width (ns)" }, + { key: "interval", label: "Interval (mm)" }, + { key: "pass", label: "Passes" }, + { key: "type", label: "Type" }, + { key: "angle", label: "Angle (°)" }, + { key: "auto", label: "Auto-Rotate" }, + { key: "increment", label: "Increment (°)", condition: (e: any) => e.auto }, + { key: "cross", label: "Crosshatch" }, + { key: "flood", label: "Flood Fill" }, + { key: "air", label: "Air Assist" }, + ], + setting.fill_settings + )} - {renderRepeaterCard("Line Settings", [ + {renderRepeaterCard( + "Line Settings", + [ { key: "name", label: "Line Name" }, { key: "power", label: "Power (%)" }, - { key: "speed", label: "Speed (mm/s)" }, - { key: "frequency", label: "Frequency (kHz)" }, - { key: "pulse", label: "Pulse Width (ns)" }, - { key: "perf", label: "Perforation Mode" }, - { key: "cut", label: "Cut (mm)", condition: (e) => e.perf }, - { key: "skip", label: "Skip (mm)", condition: (e) => e.perf }, - { key: "wobble", label: "Wobble Mode" }, - { key: "step", label: "Step (mm)", condition: (e) => e.wobble }, - { key: "size", label: "Size (mm)", condition: (e) => e.wobble }, - { key: "pass", label: "Passes" }, - { key: "air", label: "Air Assist" }, - ], setting.line_settings)} + { key: "speed", label: "Speed (mm/s)" }, + { key: "frequency", label: "Frequency (kHz)" }, + { key: "pulse", label: "Pulse Width (ns)" }, + { key: "perf", label: "Perforation Mode" }, + { key: "cut", label: "Cut (mm)", condition: (e: any) => e.perf }, + { key: "skip", label: "Skip (mm)", condition: (e: any) => e.perf }, + { key: "wobble", label: "Wobble Mode" }, + { key: "step", label: "Step (mm)", condition: (e: any) => e.wobble }, + { key: "size", label: "Size (mm)", condition: (e: any) => e.wobble }, + { key: "pass", label: "Passes" }, + { key: "air", label: "Air Assist" }, + ], + setting.line_settings + )} - {renderRepeaterCard("Raster Settings", [ + {renderRepeaterCard( + "Raster Settings", + [ { key: "name", label: "Raster Name" }, { key: "power", label: "Power (%)" }, - { key: "speed", label: "Speed (mm/s)" }, - { key: "frequency", label: "Frequency (kHz)" }, - { key: "pulse", label: "Pulse Width (ns)" }, - { key: "type", label: "Type" }, - { key: "dither", label: "Dither" }, - { key: "halftone_cell", label: "Cell Size (mm)", condition: (e) => e.dither === "halftone" }, - { key: "halftone_angle", label: "Halftone Angle", condition: (e) => e.dither === "halftone" }, - { key: "inversion", label: "Image Inverted" }, - { key: "interval", label: "Interval (mm)" }, - { key: "dot", label: "Dot-width Adjustment (mm)" }, - { key: "pass", label: "Passes" }, - { key: "cross", label: "Crosshatch" }, - { key: "air", label: "Air Assist" }, - ], setting.raster_settings)} + { key: "speed", label: "Speed (mm/s)" }, + { key: "frequency", label: "Frequency (kHz)" }, + { key: "pulse", label: "Pulse Width (ns)" }, + { key: "type", label: "Type" }, + { key: "dither", label: "Dither" }, + { + key: "halftone_cell", + label: "Cell Size (mm)", + condition: (e: any) => e.dither === "halftone", + }, + { + key: "halftone_angle", + label: "Halftone Angle", + condition: (e: any) => e.dither === "halftone", + }, + { key: "inversion", label: "Image Inverted" }, + { key: "interval", label: "Interval (mm)" }, + { key: "dot", label: "Dot-width Adjustment (mm)" }, + { key: "pass", label: "Passes" }, + { key: "cross", label: "Crosshatch" }, + { key: "air", label: "Air Assist" }, + ], + setting.raster_settings + )}
); } -