Initial commit
This commit is contained in:
commit
78f8d225ee
21173 changed files with 2907774 additions and 0 deletions
179
app/fiber-settings/[id]/page.tsx
Normal file
179
app/fiber-settings/[id]/page.tsx
Normal file
|
|
@ -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 <p className="p-6">Loading setting...</p>;
|
||||
if (!setting) return <p className="p-6">Setting not found.</p>;
|
||||
|
||||
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 (
|
||||
<div className="mt-6">
|
||||
<h2 className="text-2xl font-semibold mb-2">{title}</h2>
|
||||
<div className="grid gap-4 grid-cols-1 md:grid-cols-2">
|
||||
{filtered.map((item, i) => (
|
||||
<div key={i} className="border border-border rounded-lg p-4 bg-card">
|
||||
{fields.map(({ key, label, condition }) => {
|
||||
const value = item[key];
|
||||
if (condition && !condition(item)) return null;
|
||||
return <p key={key} className="text-sm"><strong>{label}:</strong> {typeof value === "boolean" ? formatBoolean(value) : value || "—"}</p>;
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
<div className="card bg-card p-4 flex flex-col justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold mb-1">{setting.setting_title}</h1>
|
||||
<p className="text-muted-foreground mb-4">Uploaded by: {setting.uploader || "—"}</p>
|
||||
</div>
|
||||
<a
|
||||
href="/fiber-settings"
|
||||
className="inline-block mt-2 px-4 py-2 bg-accent text-background rounded-md text-sm self-start"
|
||||
>
|
||||
← Back to Fiber Settings
|
||||
</a>
|
||||
</div>
|
||||
<div className="card bg-card p-4 grid grid-cols-1 md:grid-cols-2 gap-4 items-start">
|
||||
<div className="flex justify-center">
|
||||
{setting.photo?.filename_disk && (
|
||||
<a href={`https://forms.lasereverything.net/assets/${setting.photo.filename_disk}`} target="_blank" rel="noopener noreferrer">
|
||||
<Image
|
||||
src={`https://forms.lasereverything.net/assets/${setting.photo.filename_disk}`}
|
||||
alt="Laser preview"
|
||||
width={250}
|
||||
height={250}
|
||||
className="rounded object-contain max-w-[250px] max-h-[250px]"
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-2">Material</h2>
|
||||
<p><strong>Material:</strong> <span className="cursor-pointer underline hover:text-accent" onClick={() => openSearchInNewTab(setting.mat?.name)}>{setting.mat?.name || "—"}</span></p>
|
||||
<p><strong>Coating:</strong> <span className="cursor-pointer underline hover:text-accent" onClick={() => openSearchInNewTab(setting.mat_coat?.name)}>{setting.mat_coat?.name || "—"}</span></p>
|
||||
<p><strong>Color:</strong> {setting.mat_color?.name || "—"}</p>
|
||||
<p><strong>Opacity:</strong> {setting.mat_opacity?.opacity || "—"}</p>
|
||||
<p><strong>Thickness:</strong> {setting.mat_thickness ? `${setting.mat_thickness} mm` : "Not Applicable"}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card p-4">
|
||||
<h2 className="text-xl font-semibold mb-2">Setup</h2>
|
||||
<p><strong>Software:</strong> {setting.laser_soft?.name || "—"}</p>
|
||||
<p><strong>Repeat All (global):</strong> {setting.repeat_all ?? "—"}</p>
|
||||
<p className="mt-4"><strong>Focus:</strong> {setting.focus ?? "—"} mm</p>
|
||||
<small>-Values Focus Closer | +Values Focus Further</small>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card p-4">
|
||||
<h2 className="text-xl font-semibold mb-2">Laser</h2>
|
||||
<p><strong>Source Make:</strong> {setting.source?.make || "—"}</p>
|
||||
<p><strong>Source Model:</strong> <span className="cursor-pointer underline hover:text-accent" onClick={() => openSearchInNewTab(setting.source?.model)}>{setting.source?.model || "—"}</span></p>
|
||||
<p><strong>Lens:</strong> <span className="cursor-pointer underline hover:text-accent" onClick={() => openSearchInNewTab(setting.lens?.field_size)}>{setting.lens?.field_size || "—"}</span> mm | {setting.lens?.focal_length || "—"}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{setting.setting_notes && (
|
||||
<div className="prose dark:prose-invert mt-6">
|
||||
<h2>Notes</h2>
|
||||
<Markdown>{setting.setting_notes}</Markdown>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr className="my-6 border-muted" />
|
||||
|
||||
{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)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
59
app/fiber-settings/[id]/page.tsx.back
Normal file
59
app/fiber-settings/[id]/page.tsx.back
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"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 <p className="p-6">Loading setting...</p>;
|
||||
if (!setting) return <p className="p-6">Setting not found.</p>;
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-4xl mx-auto">
|
||||
<h1 className="text-3xl font-bold mb-4">{setting.setting_title}</h1>
|
||||
{setting.photo?.filename_disk && (
|
||||
<Image
|
||||
src={`https://forms.lasereverything.net/assets/${setting.photo.filename_disk}`}
|
||||
alt="Laser preview"
|
||||
width={512}
|
||||
height={512}
|
||||
className="rounded mb-4"
|
||||
/>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<p><strong>Uploader:</strong> {setting.uploader || "—"}</p>
|
||||
<p><strong>Material:</strong> {setting.mat?.name || "—"}</p>
|
||||
<p><strong>Coating:</strong> {setting.mat_coat?.name || "—"}</p>
|
||||
<p><strong>Color:</strong> {setting.mat_color?.name || "—"}</p>
|
||||
<p><strong>Opacity:</strong> {setting.mat_opacity?.opacity || "—"}</p>
|
||||
<p><strong>Source Model:</strong> {setting.source?.model || "—"}</p>
|
||||
<p><strong>Lens Field Size:</strong> {setting.lens?.field_size || "—"}</p>
|
||||
</div>
|
||||
{setting.setting_notes && (
|
||||
<div className="mt-6 prose dark:prose-invert">
|
||||
<h2>Notes</h2>
|
||||
<Markdown>{setting.setting_notes}</Markdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
4
app/fiber-settings/layout.tsx
Normal file
4
app/fiber-settings/layout.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { Suspense } from "react";
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return <Suspense fallback={null}>{children}</Suspense>;
|
||||
}
|
||||
230
app/fiber-settings/page.tsx
Normal file
230
app/fiber-settings/page.tsx
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function FiberSettingsPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const initialQuery = searchParams.get("query") || "";
|
||||
|
||||
const [query, setQuery] = useState(initialQuery);
|
||||
const [debouncedQuery, setDebouncedQuery] = useState(initialQuery);
|
||||
const [settings, setSettings] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedQuery(query), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_fiber?fields=submission_id,setting_title,uploader,photo.id,photo.title,mat.name,mat_coat.name,source.model,lens.field_size&limit=-1`
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setSettings(data.data || []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const highlight = (text) => {
|
||||
if (!debouncedQuery) return text;
|
||||
const regex = new RegExp(`(${debouncedQuery})`, "gi");
|
||||
return text?.replace(regex, '<mark>$1</mark>');
|
||||
};
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = debouncedQuery.toLowerCase();
|
||||
return settings.filter((entry) => {
|
||||
const fieldsToSearch = [
|
||||
entry.setting_title,
|
||||
entry.uploader,
|
||||
entry.mat?.name,
|
||||
entry.mat_coat?.name,
|
||||
entry.source?.model,
|
||||
entry.lens?.field_size,
|
||||
];
|
||||
return fieldsToSearch.filter(Boolean).some((field) =>
|
||||
field.toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
}, [settings, debouncedQuery]);
|
||||
|
||||
const totalSettings = settings.length;
|
||||
const uniqueMaterials = new Set(settings.map(s => s.mat?.name).filter(Boolean)).size;
|
||||
|
||||
const commonLens = settings.reduce((acc, cur) => {
|
||||
const lens = cur.lens?.field_size;
|
||||
if (!lens) return acc;
|
||||
acc[lens] = (acc[lens] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const mostCommonLens = Object.entries(commonLens)
|
||||
.sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—";
|
||||
|
||||
const sourceModels = settings.reduce((acc, cur) => {
|
||||
const model = cur.source?.model;
|
||||
if (!model) return acc;
|
||||
acc[model] = (acc[model] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const mostCommonSource = Object.entries(sourceModels)
|
||||
.sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—";
|
||||
|
||||
const recentSettings = [...settings]
|
||||
.sort((a, b) => b.submission_id - a.submission_id)
|
||||
.slice(0, 5);
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
<style jsx global>{`
|
||||
mark {
|
||||
background: #ffde59;
|
||||
color: #242424;
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 mb-6">
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h1 className="text-2xl font-bold mb-2">Fiber Laser Settings</h1>
|
||||
<input
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search settings by material, uploader, etc..."
|
||||
className="w-full mb-4 dark:bg-background border border-border rounded-md p-2"
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
View and explore detailed fiber laser settings with context.
|
||||
</p>
|
||||
<a
|
||||
href="/"
|
||||
className="inline-block mt-2 px-4 py-2 bg-accent text-background rounded-md text-sm"
|
||||
>
|
||||
← Back to Main Menu
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">How to Use</h2>
|
||||
<p className="text-sm">
|
||||
Browse real-world fiber laser settings from the community. Use the search to narrow results. Click any setting to view its full configuration, notes, and photos. Click any linked term to find related settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">Stats Summary</h2>
|
||||
<ul className="text-sm space-y-1">
|
||||
<li>Total Settings: {totalSettings}</li>
|
||||
<li>Unique Materials: {uniqueMaterials}</li>
|
||||
<li>Most Common Lens: {mostCommonLens}</li>
|
||||
<li>Most Used Source: {mostCommonSource}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">Recently Added</h2>
|
||||
<ul className="text-sm space-y-1">
|
||||
{recentSettings.map((s) => (
|
||||
<li key={s.submission_id}>
|
||||
<Link href={`/fiber-settings/${s.submission_id}`} className="underline text-accent">
|
||||
{s.setting_title || "Untitled"}
|
||||
</Link> by {s.uploader || "—"}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">Resources</h2>
|
||||
<ul className="text-sm space-y-1">
|
||||
<li>
|
||||
<a href="/materials" target="_blank" rel="noopener noreferrer" className="underline text-accent">Material Safety Guide</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://lasereverything.net/scripts/laspwrconvert.php" target="_blank" rel="noopener noreferrer" className="underline text-accent">Laser Parameter Calculator</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://jptoe.com/downloads" target="_blank" rel="noopener noreferrer" className="underline text-accent">JPT Datasheets</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4 flex flex-col justify-between">
|
||||
<div>
|
||||
<h2 className="text-md font-semibold mb-2">Submit a Setting</h2>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Have a reliable fiber setting to share? Contribute to the community database.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/submit/settings?target=settings_fiber"
|
||||
className="bg-accent text-background text-sm px-4 py-2 rounded hover:opacity-90 transition"
|
||||
>
|
||||
Submit a Setting
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<p className="text-muted">Loading settings...</p>
|
||||
) : filtered.length === 0 ? (
|
||||
<p className="text-muted">No fiber settings found.</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-2 py-2 text-left">Photo</th>
|
||||
<th className="px-2 py-2 text-left">Title</th>
|
||||
<th className="px-2 py-2 text-left">Uploader</th>
|
||||
<th className="px-2 py-2 text-left">Material</th>
|
||||
<th className="px-2 py-2 text-left">Coating</th>
|
||||
<th className="px-2 py-2 text-left">Source</th>
|
||||
<th className="px-2 py-2 text-left">Lens</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map((setting) => (
|
||||
<tr key={setting.submission_id} className="border-t border-border">
|
||||
<td className="px-2 py-2">
|
||||
{setting.photo?.id ? (
|
||||
<Image
|
||||
src={`https://forms.lasereverything.net/assets/${setting.photo.id}`}
|
||||
alt={setting.photo.title || "laser preview"}
|
||||
width={64}
|
||||
height={64}
|
||||
className="rounded-md"
|
||||
/>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</td>
|
||||
<td className="px-2 py-2 whitespace-nowrap">
|
||||
<Link
|
||||
href={`/fiber-settings/${setting.submission_id}`}
|
||||
className="text-accent underline"
|
||||
dangerouslySetInnerHTML={{ __html: highlight(setting.setting_title || "—") }}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.uploader || "—") }} />
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.mat?.name || "—") }} />
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.mat_coat?.name || "—") }} />
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.source?.model || "—") }} />
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.lens?.field_size || "—") }} />
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
227
app/fiber-settings/page.tsx.bak
Normal file
227
app/fiber-settings/page.tsx.bak
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function FiberSettingsPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const initialQuery = searchParams.get("query") || "";
|
||||
|
||||
const [query, setQuery] = useState(initialQuery);
|
||||
const [debouncedQuery, setDebouncedQuery] = useState(initialQuery);
|
||||
const [settings, setSettings] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedQuery(query), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/settings_fiber?fields=submission_id,setting_title,uploader,photo.filename_disk,photo.title,mat.name,mat_coat.name,source.model,lens.field_size&limit=-1`
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setSettings(data.data || []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const highlight = (text) => {
|
||||
if (!debouncedQuery) return text;
|
||||
const regex = new RegExp(`(${debouncedQuery})`, "gi");
|
||||
return text?.replace(regex, '<mark>$1</mark>');
|
||||
};
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = debouncedQuery.toLowerCase();
|
||||
return settings.filter((entry) => {
|
||||
const fieldsToSearch = [
|
||||
entry.setting_title,
|
||||
entry.uploader,
|
||||
entry.mat?.name,
|
||||
entry.mat_coat?.name,
|
||||
entry.source?.model,
|
||||
entry.lens?.field_size,
|
||||
];
|
||||
return fieldsToSearch.filter(Boolean).some((field) =>
|
||||
field.toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
}, [settings, debouncedQuery]);
|
||||
|
||||
const totalSettings = settings.length;
|
||||
const uniqueMaterials = new Set(settings.map(s => s.mat?.name).filter(Boolean)).size;
|
||||
|
||||
const commonLens = settings.reduce((acc, cur) => {
|
||||
const lens = cur.lens?.field_size;
|
||||
if (!lens) return acc;
|
||||
acc[lens] = (acc[lens] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const mostCommonLens = Object.entries(commonLens)
|
||||
.sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—";
|
||||
|
||||
const sourceModels = settings.reduce((acc, cur) => {
|
||||
const model = cur.source?.model;
|
||||
if (!model) return acc;
|
||||
acc[model] = (acc[model] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const mostCommonSource = Object.entries(sourceModels)
|
||||
.sort((a, b) => (Number(b[1]) || 0) - (Number(a[1]) || 0))[0]?.[0] || "—";
|
||||
|
||||
const recentSettings = [...settings]
|
||||
.sort((a, b) => b.submission_id - a.submission_id)
|
||||
.slice(0, 5);
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
<style jsx global>{`
|
||||
mark {
|
||||
background: #ffde59;
|
||||
color: #242424;
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 mb-6">
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h1 className="text-2xl font-bold mb-2">Fiber Laser Settings</h1>
|
||||
<input
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search settings by material, uploader, etc..."
|
||||
className="w-full mb-4 dark:bg-background border border-border rounded-md p-2"
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
View and explore detailed fiber laser settings with context.
|
||||
</p>
|
||||
<a
|
||||
href="/"
|
||||
className="inline-block mt-2 px-4 py-2 bg-accent text-background rounded-md text-sm"
|
||||
>
|
||||
← Back to Main Menu
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">How to Use</h2>
|
||||
<p className="text-sm">
|
||||
Browse real-world fiber laser settings from the community. Use the search to narrow results. Click any setting to view its full configuration, notes, and photos. Click any linked term to find related settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">Stats Summary</h2>
|
||||
<ul className="text-sm space-y-1">
|
||||
<li>Total Settings: {totalSettings}</li>
|
||||
<li>Unique Materials: {uniqueMaterials}</li>
|
||||
<li>Most Common Lens: {mostCommonLens}</li>
|
||||
<li>Most Used Source: {mostCommonSource}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">Recently Added</h2>
|
||||
<ul className="text-sm space-y-1">
|
||||
{recentSettings.map((s) => (
|
||||
<li key={s.submission_id}>
|
||||
<Link href={`/fiber-settings/${s.submission_id}`} className="underline text-accent">
|
||||
{s.setting_title || "Untitled"}
|
||||
</Link> by {s.uploader || "—"}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4">
|
||||
<h2 className="text-lg font-semibold mb-2">Resources</h2>
|
||||
<ul className="text-sm space-y-1">
|
||||
<li>
|
||||
<a href="/materials" target="_blank" rel="noopener noreferrer" className="underline text-accent">Material Safety Guide</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://lasereverything.net/scripts/laspwrconvert.php" target="_blank" rel="noopener noreferrer" className="underline text-accent">Laser Parameter Calculator</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://jptoe.com/downloads" target="_blank" rel="noopener noreferrer" className="underline text-accent">JPT Datasheets</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="card bg-card text-card-foreground p-4 flex flex-col justify-between">
|
||||
<div>
|
||||
<h2 className="text-md font-semibold mb-2">Submit a Setting</h2>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Have a reliable fiber setting to share? Contribute to the community database.
|
||||
</p>
|
||||
</div>
|
||||
<button disabled className="bg-muted text-foreground text-sm px-4 py-2 rounded opacity-50 cursor-not-allowed">
|
||||
Coming Soon
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<p className="text-muted">Loading settings...</p>
|
||||
) : filtered.length === 0 ? (
|
||||
<p className="text-muted">No fiber settings found.</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-2 py-2 text-left">Photo</th>
|
||||
<th className="px-2 py-2 text-left">Title</th>
|
||||
<th className="px-2 py-2 text-left">Uploader</th>
|
||||
<th className="px-2 py-2 text-left">Material</th>
|
||||
<th className="px-2 py-2 text-left">Coating</th>
|
||||
<th className="px-2 py-2 text-left">Source</th>
|
||||
<th className="px-2 py-2 text-left">Lens</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map((setting) => (
|
||||
<tr key={setting.submission_id} className="border-t border-border">
|
||||
<td className="px-2 py-2">
|
||||
{setting.photo?.filename_disk ? (
|
||||
<Image
|
||||
src={`https://forms.lasereverything.net/assets/${setting.photo.filename_disk}`}
|
||||
alt={setting.photo.title || "laser preview"}
|
||||
width={64}
|
||||
height={64}
|
||||
className="rounded-md"
|
||||
/>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</td>
|
||||
<td className="px-2 py-2 whitespace-nowrap">
|
||||
<Link
|
||||
href={`/fiber-settings/${setting.submission_id}`}
|
||||
className="text-accent underline"
|
||||
dangerouslySetInnerHTML={{ __html: highlight(setting.setting_title || "—") }}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.uploader || "—") }} />
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.mat?.name || "—") }} />
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.mat_coat?.name || "—") }} />
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.source?.model || "—") }} />
|
||||
<td className="px-2 py-2 whitespace-nowrap" dangerouslySetInnerHTML={{ __html: highlight(setting.lens?.field_size || "—") }} />
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue