diff --git a/app/lasers/[id]/lasers.tsx b/app/lasers/[id]/lasers.tsx new file mode 100644 index 00000000..e6b1d97a --- /dev/null +++ b/app/lasers/[id]/lasers.tsx @@ -0,0 +1,144 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useParams } from 'next/navigation'; +import Link from 'next/link'; + +export default function LaserSourceDetailsPage() { + const { id } = useParams(); + const [laser, setLaser] = useState(null); + const [labels, setLabels] = useState({}); + + useEffect(() => { + if (!id) return; + + fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/laser_source/${id}?fields=*`) + .then((res) => res.json()) + .then((data) => setLaser(data.data || null)); + + fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/fields/laser_source`) + .then((res) => res.json()) + .then((data) => { + const labelMap = {}; + (data.data || []).forEach((field) => { + if (field.interface === 'select-dropdown' && field.options?.choices) { + labelMap[field.field] = {}; + field.options.choices.forEach((choice) => { + labelMap[field.field][choice.value] = choice.text; + }); + } + }); + setLabels(labelMap); + }); + }, [id]); + + if (!laser) return
Loading...
; + + const resolveLabel = (field, value) => { + if (!value) return '—'; + + const hardcodedLabels = { + op: { + pm: 'MOPA', + pq: 'Q-Switch', + }, + cooling: { + aa: 'Air, Active', + ap: 'Air, Passive', + w: 'Water', + }, + }; + + if (hardcodedLabels[field] && hardcodedLabels[field][value]) { + return hardcodedLabels[field][value]; + } + + return labels[field]?.[value] || value; + }; + + const fieldGroups = [ + { + title: 'General Information', + fields: { + make: 'Make', + model: 'Model', + op: 'Pulse Operation Mode', + notes: 'Notes', + }, + }, + { + title: 'Optical Specifications', + fields: { + w: 'Laser Wattage (W)', + mj: 'milliJoule Max (mJ)', + nm: 'Wavelength (nm)', + k_hz: 'Pulse Repetition Rate (kHz)', + ns: 'Pulse Width (ns)', + d: 'Beam Diameter (mm)', + m2: 'M² - Quality', + instability: 'Instability', + polarization: 'Polarization', + band: 'Band (nm)', + anti: 'Anti-Reflection Coating', + mw: 'Red Dot Wattage (mW)', + }, + }, + { + title: 'Electrical & Timing', + fields: { + v: 'Operating Voltage (V)', + temp_op: 'Operating Temperature (°C)', + temp_store: 'Storage Temperature (°C)', + l_on: 'l_on', + l_off: 'l_off', + mj_c: 'mj_c', + ns_c: 'ns_c', + d_c: 'd_c', + on_c: 'on_c', + off_c: 'off_c', + }, + }, + { + title: 'Integration & Physical', + fields: { + cable: 'Cable Length (m)', + cooling: 'Cooling Method', + weight: 'Weight (kg)', + dimensions: 'Dimensions (cm)', + }, + }, + ]; + + return ( +
+

+ {laser.make || '—'} {laser.model || ''} +

+ +
+ {fieldGroups.map(({ title, fields }) => ( +
+

{title}

+
+ {Object.entries(fields).map(([key, label]) => ( +
+
{label}
+
+ {resolveLabel(key, laser[key])} +
+
+ ))} +
+
+ ))} +
+ +
+ + ← Back to Laser Sources + +
+
+ ); +} + diff --git a/app/lasers/[id]/page.tsx b/app/lasers/[id]/page.tsx index e6b1d97a..f8c796b6 100644 --- a/app/lasers/[id]/page.tsx +++ b/app/lasers/[id]/page.tsx @@ -1,144 +1,5 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import { useParams } from 'next/navigation'; -import Link from 'next/link'; - -export default function LaserSourceDetailsPage() { - const { id } = useParams(); - const [laser, setLaser] = useState(null); - const [labels, setLabels] = useState({}); - - useEffect(() => { - if (!id) return; - - fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/laser_source/${id}?fields=*`) - .then((res) => res.json()) - .then((data) => setLaser(data.data || null)); - - fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/fields/laser_source`) - .then((res) => res.json()) - .then((data) => { - const labelMap = {}; - (data.data || []).forEach((field) => { - if (field.interface === 'select-dropdown' && field.options?.choices) { - labelMap[field.field] = {}; - field.options.choices.forEach((choice) => { - labelMap[field.field][choice.value] = choice.text; - }); - } - }); - setLabels(labelMap); - }); - }, [id]); - - if (!laser) return
Loading...
; - - const resolveLabel = (field, value) => { - if (!value) return '—'; - - const hardcodedLabels = { - op: { - pm: 'MOPA', - pq: 'Q-Switch', - }, - cooling: { - aa: 'Air, Active', - ap: 'Air, Passive', - w: 'Water', - }, - }; - - if (hardcodedLabels[field] && hardcodedLabels[field][value]) { - return hardcodedLabels[field][value]; - } - - return labels[field]?.[value] || value; - }; - - const fieldGroups = [ - { - title: 'General Information', - fields: { - make: 'Make', - model: 'Model', - op: 'Pulse Operation Mode', - notes: 'Notes', - }, - }, - { - title: 'Optical Specifications', - fields: { - w: 'Laser Wattage (W)', - mj: 'milliJoule Max (mJ)', - nm: 'Wavelength (nm)', - k_hz: 'Pulse Repetition Rate (kHz)', - ns: 'Pulse Width (ns)', - d: 'Beam Diameter (mm)', - m2: 'M² - Quality', - instability: 'Instability', - polarization: 'Polarization', - band: 'Band (nm)', - anti: 'Anti-Reflection Coating', - mw: 'Red Dot Wattage (mW)', - }, - }, - { - title: 'Electrical & Timing', - fields: { - v: 'Operating Voltage (V)', - temp_op: 'Operating Temperature (°C)', - temp_store: 'Storage Temperature (°C)', - l_on: 'l_on', - l_off: 'l_off', - mj_c: 'mj_c', - ns_c: 'ns_c', - d_c: 'd_c', - on_c: 'on_c', - off_c: 'off_c', - }, - }, - { - title: 'Integration & Physical', - fields: { - cable: 'Cable Length (m)', - cooling: 'Cooling Method', - weight: 'Weight (kg)', - dimensions: 'Dimensions (cm)', - }, - }, - ]; - - return ( -
-

- {laser.make || '—'} {laser.model || ''} -

- -
- {fieldGroups.map(({ title, fields }) => ( -
-

{title}

-
- {Object.entries(fields).map(([key, label]) => ( -
-
{label}
-
- {resolveLabel(key, laser[key])} -
-
- ))} -
-
- ))} -
- -
- - ← Back to Laser Sources - -
-
- ); +// app/lasers/[id]/page.tsx +import { redirect } from "next/navigation"; +export default function Page({ params }: { params: { id: string } }) { + redirect(`/portal/laser-sources?id=${encodeURIComponent(params.id)}`); } - diff --git a/app/materials/materials-coatings/[id]/materials-coatings.tsx b/app/materials/materials-coatings/[id]/materials-coatings.tsx new file mode 100644 index 00000000..0ea4c23b --- /dev/null +++ b/app/materials/materials-coatings/[id]/materials-coatings.tsx @@ -0,0 +1,58 @@ +'use client'; + +import Link from 'next/link'; +import { useEffect, useState } from 'react'; +import { useParams } from 'next/navigation'; + +export default function MaterialCoatingDetailsPage() { + const { id } = useParams(); + const [material, setMaterial] = useState(null); + + useEffect(() => { + if (!id) return; + + fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/material_coating/${id}?fields=id,name,abbreviation,technical_name,composition,notes,override_reason,coating_status.name,coating_status_override,hazard_tags.hazard_tags_id.hazard_source.source,hazard_tags.hazard_tags_id.hazard_danger.danger,hazard_tags.hazard_tags_id.hazard_severity.severity` + ) + .then((res) => res.json()) + .then((data) => setMaterial(data.data || null)); + }, [id]); + + if (!material) return
Loading...
; + + return ( +
+

{material.name}

+
+

Status: {material.coating_status?.name || '—'}

+

Abbreviation: {material.abbreviation || '—'}

+

Technical Name: {material.technical_name || '—'}

+

Composition: {material.composition || '—'}

+

Notes: {material.notes || '—'}

+

Override Reason: {material.override_reason || '—'}

+ +
+ Hazard Tags +
    + {Array.isArray(material.hazard_tags) && material.hazard_tags.length > 0 ? ( + material.hazard_tags.map((tag, index) => ( +
  • + {tag.hazard_tags_id?.hazard_source?.source || '—'} |{' '} + {tag.hazard_tags_id?.hazard_danger?.danger || '—'} |{' '} + {tag.hazard_tags_id?.hazard_severity?.severity || '—'} +
  • + )) + ) : ( +
  • None
  • + )} +
+
+
+ +
+ ← Back to Coatings +
+
+ ); +} + diff --git a/app/materials/materials-coatings/[id]/page.tsx b/app/materials/materials-coatings/[id]/page.tsx index 0ea4c23b..b60c1391 100644 --- a/app/materials/materials-coatings/[id]/page.tsx +++ b/app/materials/materials-coatings/[id]/page.tsx @@ -1,58 +1,5 @@ -'use client'; - -import Link from 'next/link'; -import { useEffect, useState } from 'react'; -import { useParams } from 'next/navigation'; - -export default function MaterialCoatingDetailsPage() { - const { id } = useParams(); - const [material, setMaterial] = useState(null); - - useEffect(() => { - if (!id) return; - - fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/material_coating/${id}?fields=id,name,abbreviation,technical_name,composition,notes,override_reason,coating_status.name,coating_status_override,hazard_tags.hazard_tags_id.hazard_source.source,hazard_tags.hazard_tags_id.hazard_danger.danger,hazard_tags.hazard_tags_id.hazard_severity.severity` - ) - .then((res) => res.json()) - .then((data) => setMaterial(data.data || null)); - }, [id]); - - if (!material) return
Loading...
; - - return ( -
-

{material.name}

-
-

Status: {material.coating_status?.name || '—'}

-

Abbreviation: {material.abbreviation || '—'}

-

Technical Name: {material.technical_name || '—'}

-

Composition: {material.composition || '—'}

-

Notes: {material.notes || '—'}

-

Override Reason: {material.override_reason || '—'}

- -
- Hazard Tags -
    - {Array.isArray(material.hazard_tags) && material.hazard_tags.length > 0 ? ( - material.hazard_tags.map((tag, index) => ( -
  • - {tag.hazard_tags_id?.hazard_source?.source || '—'} |{' '} - {tag.hazard_tags_id?.hazard_danger?.danger || '—'} |{' '} - {tag.hazard_tags_id?.hazard_severity?.severity || '—'} -
  • - )) - ) : ( -
  • None
  • - )} -
-
-
- -
- ← Back to Coatings -
-
- ); +// app/materials/materials-coatings/[id]/page.tsx +import { redirect } from "next/navigation"; +export default function Page({ params }: { params: { id: string } }) { + redirect(`/portal/materials?t=materials-coatings&id=${encodeURIComponent(params.id)}`); } - diff --git a/app/materials/materials/[id]/materials.tsx b/app/materials/materials/[id]/materials.tsx new file mode 100644 index 00000000..7a99e41a --- /dev/null +++ b/app/materials/materials/[id]/materials.tsx @@ -0,0 +1,60 @@ +'use client'; + +import Link from 'next/link'; +import { useEffect, useState } from 'react'; +import { useParams } from 'next/navigation'; + +export default function MaterialDetailsPage() { + const { id } = useParams(); + const [material, setMaterial] = useState(null); + + useEffect(() => { + if (!id) return; + + fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/material/${id}?fields=id,name,abbreviation,common_names,technical_name,composition,material_cat.name,material_status.name,notes,override_reason,hazard_tags.hazard_tags_id.hazard_source.source,hazard_tags.hazard_tags_id.hazard_danger.danger,hazard_tags.hazard_tags_id.hazard_severity.severity` + ) + .then((res) => res.json()) + .then((data) => setMaterial(data.data || null)); + }, [id]); + + if (!material) return
Loading...
; + + return ( +
+

{material.name}

+
+

Category: {material.material_cat?.name || '—'}

+

Status: {material.material_status?.name || '—'}

+

Abbreviation: {material.abbreviation || '—'}

+

Common Names: {material.common_names || '—'}

+

Technical Name: {material.technical_name || '—'}

+

Composition: {material.composition || '—'}

+

Notes: {material.notes || '—'}

+

Override Reason: {material.override_reason || '—'}

+ +
+ Hazard Tags +
    + {Array.isArray(material.hazard_tags) && material.hazard_tags.length > 0 ? ( + material.hazard_tags.map((tag, index) => ( +
  • + {tag.hazard_tags_id?.hazard_source?.source || '—'} |{' '} + {tag.hazard_tags_id?.hazard_danger?.danger || '—'} |{' '} + {tag.hazard_tags_id?.hazard_severity?.severity || '—'} +
  • + )) + ) : ( +
  • None
  • + )} +
+
+
+ +
+ ← Back to Materials +
+
+ ); +} + diff --git a/app/materials/materials/[id]/page.tsx b/app/materials/materials/[id]/page.tsx index 7a99e41a..83b4d610 100644 --- a/app/materials/materials/[id]/page.tsx +++ b/app/materials/materials/[id]/page.tsx @@ -1,60 +1,5 @@ -'use client'; - -import Link from 'next/link'; -import { useEffect, useState } from 'react'; -import { useParams } from 'next/navigation'; - -export default function MaterialDetailsPage() { - const { id } = useParams(); - const [material, setMaterial] = useState(null); - - useEffect(() => { - if (!id) return; - - fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/material/${id}?fields=id,name,abbreviation,common_names,technical_name,composition,material_cat.name,material_status.name,notes,override_reason,hazard_tags.hazard_tags_id.hazard_source.source,hazard_tags.hazard_tags_id.hazard_danger.danger,hazard_tags.hazard_tags_id.hazard_severity.severity` - ) - .then((res) => res.json()) - .then((data) => setMaterial(data.data || null)); - }, [id]); - - if (!material) return
Loading...
; - - return ( -
-

{material.name}

-
-

Category: {material.material_cat?.name || '—'}

-

Status: {material.material_status?.name || '—'}

-

Abbreviation: {material.abbreviation || '—'}

-

Common Names: {material.common_names || '—'}

-

Technical Name: {material.technical_name || '—'}

-

Composition: {material.composition || '—'}

-

Notes: {material.notes || '—'}

-

Override Reason: {material.override_reason || '—'}

- -
- Hazard Tags -
    - {Array.isArray(material.hazard_tags) && material.hazard_tags.length > 0 ? ( - material.hazard_tags.map((tag, index) => ( -
  • - {tag.hazard_tags_id?.hazard_source?.source || '—'} |{' '} - {tag.hazard_tags_id?.hazard_danger?.danger || '—'} |{' '} - {tag.hazard_tags_id?.hazard_severity?.severity || '—'} -
  • - )) - ) : ( -
  • None
  • - )} -
-
-
- -
- ← Back to Materials -
-
- ); +// app/materials/materials/[id]/page.tsx +import { redirect } from "next/navigation"; +export default function Page({ params }: { params: { id: string } }) { + redirect(`/portal/materials?t=materials&id=${encodeURIComponent(params.id)}`); } - diff --git a/app/portal/laser-settings/page.tsx b/app/portal/laser-settings/page.tsx index d26541fc..2a88d8c8 100644 --- a/app/portal/laser-settings/page.tsx +++ b/app/portal/laser-settings/page.tsx @@ -1,9 +1,36 @@ // app/portal/laser-settings/page.tsx +import { redirect } from "next/navigation"; import SettingsSwitcher from "@/components/portal/SettingsSwitcher"; export const metadata = { title: "MakerDash • Laser Settings" }; -export default function LaserSettingsPortalPage() { +function pickOne(v: T | T[] | undefined): T | undefined { + if (Array.isArray(v)) return v[0]; + return v; +} + +export default function LaserSettingsPortalPage({ + searchParams, +}: { + searchParams?: Record; +}) { + // Read tab + optional id from query + const t = (pickOne(searchParams?.t) || "fiber").toLowerCase(); + const id = pickOne(searchParams?.id); + + // If an id is present, hop directly to the existing detail route + if (id) { + const slugMap: Record = { + fiber: "fiber", + uv: "uv", + "co2-galvo": "co2-galvo", + "co2-gantry": "co2-gantry", + }; + const slug = slugMap[t] ?? "fiber"; + redirect(`/settings/${slug}/${encodeURIComponent(id)}`); + } + + // Otherwise show the normal portal view return (

Laser Settings

diff --git a/app/projects/[id]/page.tsx b/app/projects/[id]/projects.tsx similarity index 100% rename from app/projects/[id]/page.tsx rename to app/projects/[id]/projects.tsx diff --git a/app/settings/co2-galvo/[id]/co2-galvo.tsx b/app/settings/co2-galvo/[id]/co2-galvo.tsx new file mode 100644 index 00000000..1d7afa69 --- /dev/null +++ b/app/settings/co2-galvo/[id]/co2-galvo.tsx @@ -0,0 +1,183 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; +import Image from "next/image"; +import Markdown from "react-markdown"; + +export default function CO2GalvoSettingDetailPage() { + const { id } = useParams(); + const [setting, setSetting] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_co2gal/${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,lens_conf.name,lens_apt.name,lens_exp.name,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)); + }, [id]); + + const formatBoolean = (val) => val ? "Enabled" : val === false ? "Disabled" : "—"; + + const renderRepeaterCard = (title, fields, items) => { + 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 || "—"}

; + })} +
+ ))} +
+
+ ); + }; + + const openSearchInNewTab = (value) => { + const url = new URL("/co2-galvo-settings", window.location.origin); + url.searchParams.set("query", value); + const anchor = document.createElement("a"); + anchor.href = url.toString(); + anchor.target = "_blank"; + anchor.rel = "noopener noreferrer"; + anchor.click(); + }; + + if (loading) return

Loading setting...

; + if (!setting) return

Setting not found.

; + + return ( +
+
+
+
+

{setting.setting_title}

+

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

+
+ + ← Back to CO₂ Galvo Settings + +
+ +
+
+ {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 || "—"} mm

+

Lens Config: {setting.lens_conf?.name || "—"}

+

Aperture Type: {setting.lens_apt?.name || "—"}

+

Expansion Type: {setting.lens_exp?.name || "—"}

+
+
+ + {setting.setting_notes && ( +
+

Notes

+ {setting.setting_notes} +
+ )} + +
+ + {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)} + + {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)} + + {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)} +
+ ); +} + diff --git a/app/settings/co2-galvo/[id]/page.tsx b/app/settings/co2-galvo/[id]/page.tsx index 1d7afa69..6e8b4ecb 100644 --- a/app/settings/co2-galvo/[id]/page.tsx +++ b/app/settings/co2-galvo/[id]/page.tsx @@ -1,183 +1,5 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useParams } from "next/navigation"; -import Image from "next/image"; -import Markdown from "react-markdown"; - -export default function CO2GalvoSettingDetailPage() { - const { id } = useParams(); - const [setting, setSetting] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_co2gal/${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,lens_conf.name,lens_apt.name,lens_exp.name,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)); - }, [id]); - - const formatBoolean = (val) => val ? "Enabled" : val === false ? "Disabled" : "—"; - - const renderRepeaterCard = (title, fields, items) => { - 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 || "—"}

; - })} -
- ))} -
-
- ); - }; - - const openSearchInNewTab = (value) => { - const url = new URL("/co2-galvo-settings", window.location.origin); - url.searchParams.set("query", value); - const anchor = document.createElement("a"); - anchor.href = url.toString(); - anchor.target = "_blank"; - anchor.rel = "noopener noreferrer"; - anchor.click(); - }; - - if (loading) return

Loading setting...

; - if (!setting) return

Setting not found.

; - - return ( -
-
-
-
-

{setting.setting_title}

-

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

-
- - ← Back to CO₂ Galvo Settings - -
- -
-
- {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 || "—"} mm

-

Lens Config: {setting.lens_conf?.name || "—"}

-

Aperture Type: {setting.lens_apt?.name || "—"}

-

Expansion Type: {setting.lens_exp?.name || "—"}

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

Notes

- {setting.setting_notes} -
- )} - -
- - {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)} - - {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)} - - {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)} -
- ); +// app/settings/co2-galvo/[id]/page.tsx +import { redirect } from "next/navigation"; +export default function Page({ params }: { params: { id: string } }) { + redirect(`/portal/laser-settings?t=co2-galvo&id=${encodeURIComponent(params.id)}`); } - diff --git a/app/settings/co2-gantry/[id]/co2-gantry.tsx b/app/settings/co2-gantry/[id]/co2-gantry.tsx new file mode 100644 index 00000000..39a26e3f --- /dev/null +++ b/app/settings/co2-gantry/[id]/co2-gantry.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; +import Image from "next/image"; +import Markdown from "react-markdown"; + +export default function CO2GantrySettingDetailPage() { + const { id } = useParams(); + const [setting, setSetting] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!id) return; + + fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_co2gan/${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,laser_soft.name,lens.name,lens_conf.name,focus,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); + setLoading(false); + }) + .catch(() => setLoading(false)); + }, [id]); + + const openSearchInNewTab = (value) => { + if (!value || typeof window === "undefined") return; + const url = new URL("/co2-gantry-settings", window.location.origin); + url.searchParams.set("query", value); + window.open(url.toString(), "_blank", "noopener,noreferrer"); + }; + + const renderRepeaterCard = (title, fields, data) => { + if (!data || !Array.isArray(data) || data.length === 0) return null; + return ( +
+

{title}

+ {data.map((item, i) => ( +
+ {fields.map((field) => + field.condition === undefined || field.condition(item) ? ( +

+ {field.label}:{" "} + {item[field.key] !== undefined && item[field.key] !== null ? item[field.key].toString() : "—"} +

+ ) : null + )} +
+ ))} +
+ ); + }; + + if (loading) return
Loading…
; + if (!setting) return
Setting not found.
; + + return ( +
+
+
+
+

{setting.setting_title}

+

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

+
+ + ← Back to CO₂ Gantry Settings + +
+ +
+
+ {setting.photo?.filename_disk && ( + Preview + )} +
+
+

+ 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` : "—"}

+
+
+
+ +
+
+

Laser

+

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

+

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

+

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

+

Lens Config: {setting.lens_conf?.name || "—"}

+
+ +
+

Focus

+

Focus: {setting.focus || "—"}

+
+
+ +
+

Notes

+
+ {setting.setting_notes || "—"} +
+
+ +
+ + {renderRepeaterCard("Fill Settings", [ + { key: "name", label: "Fill Name" }, + { key: "power", label: "Power (%)" }, + { key: "speed", label: "Speed (mm/s)" }, + { key: "interval", label: "Interval (mm)" }, + { key: "pass", label: "Passes" }, + { key: "type", label: "Type" }, + { key: "flood", label: "Flood Fill" }, + { key: "air", label: "Air Assist" }, + ], setting.fill_settings)} + + {renderRepeaterCard("Line Settings", [ + { key: "name", label: "Line Name" }, + { key: "power", label: "Power (%)" }, + { key: "speed", label: "Speed (mm/s)" }, + { key: "perf", label: "Perforation" }, + { key: "cut", label: "Cut Power Override" }, + { key: "skip", label: "Skip Pass" }, + { key: "pass", label: "Passes" }, + { key: "air", label: "Air Assist" }, + ], setting.line_settings)} + + {renderRepeaterCard("Raster Settings", [ + { key: "name", label: "Raster Name" }, + { key: "power", label: "Power (%)" }, + { key: "speed", label: "Speed (mm/s)" }, + { key: "type", label: "Type" }, + { key: "dither", label: "Dither" }, + { key: "halftone_cell", label: "Halftone Cell" }, + { key: "halftone_angle", label: "Angle" }, + { key: "inversion", label: "Inversion" }, + { key: "interval", label: "Interval" }, + { key: "dot", label: "Dot Size" }, + { key: "pass", label: "Passes" }, + { key: "air", label: "Air Assist" }, + ], setting.raster_settings)} +
+ ); +} + diff --git a/app/settings/co2-gantry/[id]/page.tsx b/app/settings/co2-gantry/[id]/page.tsx index 39a26e3f..07db80f9 100644 --- a/app/settings/co2-gantry/[id]/page.tsx +++ b/app/settings/co2-gantry/[id]/page.tsx @@ -1,180 +1,5 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useParams } from "next/navigation"; -import Image from "next/image"; -import Markdown from "react-markdown"; - -export default function CO2GantrySettingDetailPage() { - const { id } = useParams(); - const [setting, setSetting] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - if (!id) return; - - fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_co2gan/${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,laser_soft.name,lens.name,lens_conf.name,focus,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); - setLoading(false); - }) - .catch(() => setLoading(false)); - }, [id]); - - const openSearchInNewTab = (value) => { - if (!value || typeof window === "undefined") return; - const url = new URL("/co2-gantry-settings", window.location.origin); - url.searchParams.set("query", value); - window.open(url.toString(), "_blank", "noopener,noreferrer"); - }; - - const renderRepeaterCard = (title, fields, data) => { - if (!data || !Array.isArray(data) || data.length === 0) return null; - return ( -
-

{title}

- {data.map((item, i) => ( -
- {fields.map((field) => - field.condition === undefined || field.condition(item) ? ( -

- {field.label}:{" "} - {item[field.key] !== undefined && item[field.key] !== null ? item[field.key].toString() : "—"} -

- ) : null - )} -
- ))} -
- ); - }; - - if (loading) return
Loading…
; - if (!setting) return
Setting not found.
; - - return ( -
-
-
-
-

{setting.setting_title}

-

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

-
- - ← Back to CO₂ Gantry Settings - -
- -
-
- {setting.photo?.filename_disk && ( - Preview - )} -
-
-

- 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` : "—"}

-
-
-
- -
-
-

Laser

-

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

-

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

-

- Lens:{" "} - openSearchInNewTab(setting.lens?.name)}> - {setting.lens?.name || "—"} - -

-

Lens Config: {setting.lens_conf?.name || "—"}

-
- -
-

Focus

-

Focus: {setting.focus || "—"}

-
-
- -
-

Notes

-
- {setting.setting_notes || "—"} -
-
- -
- - {renderRepeaterCard("Fill Settings", [ - { key: "name", label: "Fill Name" }, - { key: "power", label: "Power (%)" }, - { key: "speed", label: "Speed (mm/s)" }, - { key: "interval", label: "Interval (mm)" }, - { key: "pass", label: "Passes" }, - { key: "type", label: "Type" }, - { key: "flood", label: "Flood Fill" }, - { key: "air", label: "Air Assist" }, - ], setting.fill_settings)} - - {renderRepeaterCard("Line Settings", [ - { key: "name", label: "Line Name" }, - { key: "power", label: "Power (%)" }, - { key: "speed", label: "Speed (mm/s)" }, - { key: "perf", label: "Perforation" }, - { key: "cut", label: "Cut Power Override" }, - { key: "skip", label: "Skip Pass" }, - { key: "pass", label: "Passes" }, - { key: "air", label: "Air Assist" }, - ], setting.line_settings)} - - {renderRepeaterCard("Raster Settings", [ - { key: "name", label: "Raster Name" }, - { key: "power", label: "Power (%)" }, - { key: "speed", label: "Speed (mm/s)" }, - { key: "type", label: "Type" }, - { key: "dither", label: "Dither" }, - { key: "halftone_cell", label: "Halftone Cell" }, - { key: "halftone_angle", label: "Angle" }, - { key: "inversion", label: "Inversion" }, - { key: "interval", label: "Interval" }, - { key: "dot", label: "Dot Size" }, - { key: "pass", label: "Passes" }, - { key: "air", label: "Air Assist" }, - ], setting.raster_settings)} -
- ); +// app/settings/co2-gantry/[id]/page.tsx +import { redirect } from "next/navigation"; +export default function Page({ params }: { params: { id: string } }) { + redirect(`/portal/laser-settings?t=co2-gantry&id=${encodeURIComponent(params.id)}`); } - diff --git a/app/settings/fiber/[id]/fiber.tsx b/app/settings/fiber/[id]/fiber.tsx new file mode 100644 index 00000000..a826bd02 --- /dev/null +++ b/app/settings/fiber/[id]/fiber.tsx @@ -0,0 +1,179 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; +import Image from "next/image"; +import Markdown from "react-markdown"; + +export default function FiberSettingDetailPage() { + const { id } = useParams(); + const [setting, setSetting] = useState(null); + const [loading, setLoading] = useState(true); + + 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)); + }, [id]); + + if (loading) return

Loading setting...

; + if (!setting) return

Setting not found.

; + + const formatBoolean = (val) => val ? "Enabled" : val === false ? "Disabled" : "—"; + + const renderRepeaterCard = (title, fields, items) => { + 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 || "—"}

; + })} +
+ ))} +
+
+ ); + }; + + const openSearchInNewTab = (value) => { + const url = new URL("/fiber-settings", window.location.origin); + url.searchParams.set("query", value); + const anchor = document.createElement("a"); + anchor.href = url.toString(); + anchor.target = "_blank"; + anchor.rel = "noopener noreferrer"; + anchor.click(); + }; + + return ( +
+
+
+
+

{setting.setting_title}

+

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

+
+ + ← Back to Fiber Settings + +
+
+
+ {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} +
+ )} + +
+ + {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)} + + {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)} + + {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)} +
+ ); +} + diff --git a/app/settings/fiber/[id]/page.tsx b/app/settings/fiber/[id]/page.tsx index a826bd02..03972276 100644 --- a/app/settings/fiber/[id]/page.tsx +++ b/app/settings/fiber/[id]/page.tsx @@ -1,179 +1,5 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useParams } from "next/navigation"; -import Image from "next/image"; -import Markdown from "react-markdown"; - -export default function FiberSettingDetailPage() { - const { id } = useParams(); - const [setting, setSetting] = useState(null); - const [loading, setLoading] = useState(true); - - 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)); - }, [id]); - - if (loading) return

Loading setting...

; - if (!setting) return

Setting not found.

; - - const formatBoolean = (val) => val ? "Enabled" : val === false ? "Disabled" : "—"; - - const renderRepeaterCard = (title, fields, items) => { - 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 || "—"}

; - })} -
- ))} -
-
- ); - }; - - const openSearchInNewTab = (value) => { - const url = new URL("/fiber-settings", window.location.origin); - url.searchParams.set("query", value); - const anchor = document.createElement("a"); - anchor.href = url.toString(); - anchor.target = "_blank"; - anchor.rel = "noopener noreferrer"; - anchor.click(); - }; - - return ( -
-
-
-
-

{setting.setting_title}

-

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

-
- - ← Back to Fiber Settings - -
-
-
- {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} -
- )} - -
- - {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)} - - {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)} - - {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)} -
- ); +// app/settings/fiber/[id]/page.tsx +import { redirect } from "next/navigation"; +export default function Page({ params }: { params: { id: string } }) { + redirect(`/portal/laser-settings?t=fiber&id=${encodeURIComponent(params.id)}`); } - diff --git a/app/settings/fiber/[id]/page.tsx.back b/app/settings/fiber/[id]/page.tsx.back deleted file mode 100644 index c7916b1b..00000000 --- a/app/settings/fiber/[id]/page.tsx.back +++ /dev/null @@ -1,59 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useParams } from "next/navigation"; -import Image from "next/image"; -import Markdown from "react-markdown"; - -export default function FiberSettingDetailPage() { - const { id } = useParams(); - const [setting, setSetting] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_fiber/${id}?fields=submission_id,setting_title,uploader,setting_notes,photo.filename_disk,mat.name,mat_coat.name,mat_color.name,mat_opacity.opacity,source.model,lens.field_size` - ) - .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.

; - - return ( -
-

{setting.setting_title}

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

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

-

Material: {setting.mat?.name || "—"}

-

Coating: {setting.mat_coat?.name || "—"}

-

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

-

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

-

Source Model: {setting.source?.model || "—"}

-

Lens Field Size: {setting.lens?.field_size || "—"}

-
- {setting.setting_notes && ( -
-

Notes

- {setting.setting_notes} -
- )} -
- ); -} - diff --git a/app/settings/uv/[id]/page.tsx b/app/settings/uv/[id]/page.tsx index 1d69b7e3..7bea97cd 100644 --- a/app/settings/uv/[id]/page.tsx +++ b/app/settings/uv/[id]/page.tsx @@ -1,168 +1,5 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useParams } from "next/navigation"; -import Image from "next/image"; -import Markdown from "react-markdown"; - -export default function UVSettingDetailPage() { - const { id } = useParams(); - const [setting, setSetting] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - if (!id) return; - fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_uv/${id}?fields=submission_id,setting_title,uploader,setting_notes,photo.filename_disk,mat.name,mat_coat.name,mat_color.name,mat_opacity.opacity,mat_thickness,source.model,lens.field_size,lens.focal_length,focus,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); - setLoading(false); - }) - .catch(() => setLoading(false)); - }, [id]); - - const openSearchInNewTab = (value) => { - if (!value || typeof window === "undefined") return; - const url = new URL("/uv-settings", window.location.origin); - url.searchParams.set("query", value); - window.open(url.toString(), "_blank", "noopener,noreferrer"); - }; - - const renderRepeaterCard = (title, fields, data) => { - if (!data || !Array.isArray(data) || data.length === 0) return null; - return ( -
-

{title}

- {data.map((item, i) => ( -
- {fields.map((field) => - field.condition === undefined || field.condition(item) ? ( -

- {field.label}:{" "} - {item[field.key] !== undefined && item[field.key] !== null ? item[field.key].toString() : "—"} -

- ) : null - )} -
- ))} -
- ); - }; - - if (loading) return
Loading…
; - if (!setting) return
Setting not found.
; - - return ( -
-
-
-
-

{setting.setting_title}

-

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

-
- - ← Back to UV Settings - -
- -
-
- {setting.photo?.filename_disk && ( - Preview - )} -
-
-

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` : "—"}

-
-
-
- -
-
-

Laser

-

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

-

Lens: {setting.lens?.field_size || "—"} mm | {setting.lens?.focal_length || "—"} mm

-
- -
-

Focus

-

Focus: {setting.focus || "—"}

-
-
- -
-

Notes

-
- {setting.setting_notes || "—"} -
-
- -
- - {renderRepeaterCard("Fill Settings", [ - { key: "name", label: "Fill Name" }, - { 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)} - - {renderRepeaterCard("Line Settings", [ - { key: "name", label: "Line Name" }, - { 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 Override" }, - { key: "skip", label: "Skip Pass" }, - { key: "wobble", label: "Wobble Enabled" }, - { key: "step", label: "Wobble Step" }, - { key: "size", label: "Wobble Size" }, - { key: "pass", label: "Passes" }, - { key: "air", label: "Air Assist" }, - ], setting.line_settings)} - - {renderRepeaterCard("Raster Settings", [ - { key: "name", label: "Raster Name" }, - { 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: "Halftone Cell" }, - { key: "halftone_angle", label: "Halftone Angle" }, - { key: "inversion", label: "Invert Colors" }, - { key: "interval", label: "Interval (mm)" }, - { key: "dot", label: "Dot Size" }, - { key: "pass", label: "Passes" }, - { key: "cross", label: "Crosshatch" }, - { key: "air", label: "Air Assist" }, - ], setting.raster_settings)} -
- ); +// app/settings/uv/[id]/page.tsx +import { redirect } from "next/navigation"; +export default function Page({ params }: { params: { id: string } }) { + redirect(`/portal/laser-settings?t=uv&id=${encodeURIComponent(params.id)}`); } - diff --git a/app/settings/uv/[id]/uv.tsx b/app/settings/uv/[id]/uv.tsx new file mode 100644 index 00000000..1d69b7e3 --- /dev/null +++ b/app/settings/uv/[id]/uv.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; +import Image from "next/image"; +import Markdown from "react-markdown"; + +export default function UVSettingDetailPage() { + const { id } = useParams(); + const [setting, setSetting] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!id) return; + fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_uv/${id}?fields=submission_id,setting_title,uploader,setting_notes,photo.filename_disk,mat.name,mat_coat.name,mat_color.name,mat_opacity.opacity,mat_thickness,source.model,lens.field_size,lens.focal_length,focus,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); + setLoading(false); + }) + .catch(() => setLoading(false)); + }, [id]); + + const openSearchInNewTab = (value) => { + if (!value || typeof window === "undefined") return; + const url = new URL("/uv-settings", window.location.origin); + url.searchParams.set("query", value); + window.open(url.toString(), "_blank", "noopener,noreferrer"); + }; + + const renderRepeaterCard = (title, fields, data) => { + if (!data || !Array.isArray(data) || data.length === 0) return null; + return ( +
+

{title}

+ {data.map((item, i) => ( +
+ {fields.map((field) => + field.condition === undefined || field.condition(item) ? ( +

+ {field.label}:{" "} + {item[field.key] !== undefined && item[field.key] !== null ? item[field.key].toString() : "—"} +

+ ) : null + )} +
+ ))} +
+ ); + }; + + if (loading) return
Loading…
; + if (!setting) return
Setting not found.
; + + return ( +
+
+
+
+

{setting.setting_title}

+

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

+
+ + ← Back to UV Settings + +
+ +
+
+ {setting.photo?.filename_disk && ( + Preview + )} +
+
+

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` : "—"}

+
+
+
+ +
+
+

Laser

+

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

+

Lens: {setting.lens?.field_size || "—"} mm | {setting.lens?.focal_length || "—"} mm

+
+ +
+

Focus

+

Focus: {setting.focus || "—"}

+
+
+ +
+

Notes

+
+ {setting.setting_notes || "—"} +
+
+ +
+ + {renderRepeaterCard("Fill Settings", [ + { key: "name", label: "Fill Name" }, + { 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)} + + {renderRepeaterCard("Line Settings", [ + { key: "name", label: "Line Name" }, + { 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 Override" }, + { key: "skip", label: "Skip Pass" }, + { key: "wobble", label: "Wobble Enabled" }, + { key: "step", label: "Wobble Step" }, + { key: "size", label: "Wobble Size" }, + { key: "pass", label: "Passes" }, + { key: "air", label: "Air Assist" }, + ], setting.line_settings)} + + {renderRepeaterCard("Raster Settings", [ + { key: "name", label: "Raster Name" }, + { 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: "Halftone Cell" }, + { key: "halftone_angle", label: "Halftone Angle" }, + { key: "inversion", label: "Invert Colors" }, + { key: "interval", label: "Interval (mm)" }, + { key: "dot", label: "Dot Size" }, + { key: "pass", label: "Passes" }, + { key: "cross", label: "Crosshatch" }, + { key: "air", label: "Air Assist" }, + ], setting.raster_settings)} +
+ ); +} +