added edit flow
This commit is contained in:
parent
4e2fd5d813
commit
fd0553f49f
3 changed files with 771 additions and 512 deletions
|
|
@ -15,15 +15,15 @@ type Rec = {
|
||||||
photo?: { id?: string } | string | null;
|
photo?: { id?: string } | string | null;
|
||||||
screen?: { id?: string } | string | null;
|
screen?: { id?: string } | string | null;
|
||||||
|
|
||||||
// shapes pass-through for form
|
// relations (may be id or object)
|
||||||
mat?: any;
|
mat?: { id?: string | number } | string | number | null;
|
||||||
mat_coat?: any;
|
mat_coat?: { id?: string | number } | string | number | null;
|
||||||
mat_color?: any;
|
mat_color?: { id?: string | number } | string | number | null;
|
||||||
mat_opacity?: any;
|
mat_opacity?: { id?: string | number } | string | number | null;
|
||||||
mat_thickness?: number | null;
|
mat_thickness?: number | null;
|
||||||
|
|
||||||
source?: any;
|
source?: { submission_id?: string | number } | string | number | null;
|
||||||
lens?: any;
|
lens?: { id?: string | number } | string | number | null;
|
||||||
focus?: number | null;
|
focus?: number | null;
|
||||||
|
|
||||||
laser_soft?: any;
|
laser_soft?: any;
|
||||||
|
|
@ -69,25 +69,35 @@ export default function CO2GalvoSettingDetailPage() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setErr(null);
|
setErr(null);
|
||||||
|
|
||||||
|
// Request the specific IDs needed to prefill selects
|
||||||
const fields = [
|
const fields = [
|
||||||
"submission_id",
|
"submission_id",
|
||||||
"setting_title",
|
"setting_title",
|
||||||
"setting_notes",
|
"setting_notes",
|
||||||
"photo.id",
|
"photo.id",
|
||||||
"screen.id",
|
"screen.id",
|
||||||
"mat",
|
|
||||||
"mat_coat",
|
// relations: request their ids explicitly
|
||||||
"mat_color",
|
"mat.id",
|
||||||
"mat_opacity",
|
"mat_coat.id",
|
||||||
|
"mat_color.id",
|
||||||
|
"mat_opacity.id",
|
||||||
"mat_thickness",
|
"mat_thickness",
|
||||||
"source",
|
|
||||||
"lens",
|
// source is keyed by submission_id in the selector
|
||||||
|
"source.submission_id",
|
||||||
|
|
||||||
|
// lens select expects numeric id
|
||||||
|
"lens.id",
|
||||||
"focus",
|
"focus",
|
||||||
|
|
||||||
"laser_soft",
|
"laser_soft",
|
||||||
"repeat_all",
|
"repeat_all",
|
||||||
|
|
||||||
"fill_settings",
|
"fill_settings",
|
||||||
"line_settings",
|
"line_settings",
|
||||||
"raster_settings",
|
"raster_settings",
|
||||||
|
|
||||||
"owner.id",
|
"owner.id",
|
||||||
"owner.username",
|
"owner.username",
|
||||||
"uploader",
|
"uploader",
|
||||||
|
|
@ -121,8 +131,20 @@ export default function CO2GalvoSettingDetailPage() {
|
||||||
if (!rec) return null;
|
if (!rec) return null;
|
||||||
|
|
||||||
// normalize existing file refs to ids for the form
|
// normalize existing file refs to ids for the form
|
||||||
const photoId = typeof rec.photo === "string" ? rec.photo : rec.photo?.id ?? null;
|
const photoId = typeof rec.photo === "string" || typeof rec.photo === "number" ? String(rec.photo) : rec.photo?.id ?? null;
|
||||||
const screenId = typeof rec.screen === "string" ? rec.screen : rec.screen?.id ?? null;
|
const screenId = typeof rec.screen === "string" || typeof rec.screen === "number" ? String(rec.screen) : rec.screen?.id ?? null;
|
||||||
|
|
||||||
|
// normalize relations to id strings expected by <select>
|
||||||
|
const toId = (v: any) =>
|
||||||
|
v == null ? null : (typeof v === "object" ? (v.id ?? v.submission_id ?? null) : v);
|
||||||
|
|
||||||
|
const matId = toId(rec.mat);
|
||||||
|
const coatId = toId(rec.mat_coat);
|
||||||
|
const colorId = toId(rec.mat_color);
|
||||||
|
const opacityId = toId(rec.mat_opacity);
|
||||||
|
const lensId = toId(rec.lens);
|
||||||
|
// laser_source options use submission_id as value
|
||||||
|
const sourceId = rec.source && typeof rec.source === "object" ? (rec.source.submission_id ?? null) : rec.source ?? null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setting_title: rec.setting_title ?? "",
|
setting_title: rec.setting_title ?? "",
|
||||||
|
|
@ -131,22 +153,22 @@ export default function CO2GalvoSettingDetailPage() {
|
||||||
photo: photoId,
|
photo: photoId,
|
||||||
screen: screenId,
|
screen: screenId,
|
||||||
|
|
||||||
mat: rec.mat ?? null,
|
mat: matId ? String(matId) : null,
|
||||||
mat_coat: rec.mat_coat ?? null,
|
mat_coat: coatId ? String(coatId) : null,
|
||||||
mat_color: rec.mat_color ?? null,
|
mat_color: colorId ? String(colorId) : null,
|
||||||
mat_opacity: rec.mat_opacity ?? null,
|
mat_opacity: opacityId ? String(opacityId) : null,
|
||||||
mat_thickness: rec.mat_thickness ?? null,
|
mat_thickness: rec.mat_thickness ?? null,
|
||||||
|
|
||||||
source: rec.source ?? null,
|
source: sourceId != null ? String(sourceId) : null,
|
||||||
lens: rec.lens ?? null,
|
lens: lensId != null ? String(lensId) : null,
|
||||||
focus: rec.focus ?? null,
|
focus: rec.focus ?? null,
|
||||||
|
|
||||||
laser_soft: rec.laser_soft ?? null,
|
laser_soft: rec.laser_soft ?? null,
|
||||||
repeat_all: rec.repeat_all ?? null,
|
repeat_all: rec.repeat_all ?? null,
|
||||||
|
|
||||||
fill_settings: rec.fill_settings ?? [],
|
fill_settings: rec.fill_settings ?? [],
|
||||||
line_settings: rec.line_settings ?? [],
|
line_settings: rec.line_settings ?? [],
|
||||||
raster_settings: rec.raster_settings ?? [],
|
raster_settings: rec.raster_settings ?? [],
|
||||||
};
|
};
|
||||||
}, [rec]);
|
}, [rec]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@ import { useSearchParams } from "next/navigation";
|
||||||
type Owner =
|
type Owner =
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
| { id?: string | number; username?: string | null; first_name?: string | null; last_name?: string | null; email?: string | null }
|
| {
|
||||||
|
id?: string | number;
|
||||||
|
username?: string | null;
|
||||||
|
first_name?: string | null;
|
||||||
|
last_name?: string | null;
|
||||||
|
email?: string | null;
|
||||||
|
}
|
||||||
| null
|
| null
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
|
|
@ -34,6 +40,9 @@ export default function CO2GalvoSettingsPage() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [resolvingOwners, setResolvingOwners] = useState(false);
|
const [resolvingOwners, setResolvingOwners] = useState(false);
|
||||||
|
|
||||||
|
// current signed-in user id (for "Edit" visibility)
|
||||||
|
const [meId, setMeId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Debounce search box
|
// Debounce search box
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const t = setTimeout(() => setDebouncedQuery(query), 300);
|
const t = setTimeout(() => setDebouncedQuery(query), 300);
|
||||||
|
|
@ -50,13 +59,35 @@ export default function CO2GalvoSettingsPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load current user id (ignore errors; unauth just means no edit buttons)
|
||||||
|
useEffect(() => {
|
||||||
|
let dead = false;
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/dx/users/me?fields=id`, {
|
||||||
|
cache: "no-store",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
if (!r.ok) return; // unauth or error → no edit display
|
||||||
|
const j = await readJson(r);
|
||||||
|
const id = j?.data?.id ?? j?.id ?? null;
|
||||||
|
if (!dead) setMeId(id ? String(id) : null);
|
||||||
|
} catch {
|
||||||
|
/* swallow */
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
dead = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// ✅ include parent field `owner` AND subfields; use auth proxy
|
// ✅ include parent field `owner` AND subfields; use auth proxy
|
||||||
const fields = [
|
const fields = [
|
||||||
"submission_id",
|
"submission_id",
|
||||||
"setting_title",
|
"setting_title",
|
||||||
"uploader",
|
"uploader",
|
||||||
"owner", // ← add comma here
|
"owner",
|
||||||
"owner.id",
|
"owner.id",
|
||||||
"owner.username",
|
"owner.username",
|
||||||
"photo.id",
|
"photo.id",
|
||||||
|
|
@ -67,7 +98,9 @@ export default function CO2GalvoSettingsPage() {
|
||||||
"lens.field_size",
|
"lens.field_size",
|
||||||
].join(",");
|
].join(",");
|
||||||
|
|
||||||
const url = `/api/dx/items/settings_co2gal?fields=${encodeURIComponent(fields)}&limit=-1`;
|
const url = `/api/dx/items/settings_co2gal?fields=${encodeURIComponent(
|
||||||
|
fields
|
||||||
|
)}&limit=-1`;
|
||||||
|
|
||||||
fetch(url, { cache: "no-store", credentials: "include" })
|
fetch(url, { cache: "no-store", credentials: "include" })
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
|
|
@ -154,6 +187,13 @@ export default function CO2GalvoSettingsPage() {
|
||||||
})();
|
})();
|
||||||
}, [settings, ownerMap]);
|
}, [settings, ownerMap]);
|
||||||
|
|
||||||
|
const isMine = (o: Owner) => {
|
||||||
|
if (!meId || !o) return false;
|
||||||
|
if (typeof o === "string" || typeof o === "number") return String(o) === meId;
|
||||||
|
if (o.id != null) return String(o.id) === meId;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const ownerLabel = (o: Owner) => {
|
const ownerLabel = (o: Owner) => {
|
||||||
if (!o) return "—";
|
if (!o) return "—";
|
||||||
|
|
||||||
|
|
@ -173,7 +213,10 @@ export default function CO2GalvoSettingsPage() {
|
||||||
|
|
||||||
const highlight = (text?: string) => {
|
const highlight = (text?: string) => {
|
||||||
if (!debouncedQuery) return text || "";
|
if (!debouncedQuery) return text || "";
|
||||||
const regex = new RegExp(`(${debouncedQuery.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")})`, "gi");
|
const regex = new RegExp(
|
||||||
|
`(${debouncedQuery.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")})`,
|
||||||
|
"gi"
|
||||||
|
);
|
||||||
return (text || "").replace(regex, "<mark>$1</mark>");
|
return (text || "").replace(regex, "<mark>$1</mark>");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -214,31 +257,71 @@ export default function CO2GalvoSettingsPage() {
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-muted">
|
<tr className="bg-muted">
|
||||||
<th className="px-2 py-2 text-left">Title</th>
|
<th className="px-2 py-2 text-left">Title</th>
|
||||||
<th className="px-2 py-2 text-left">Owner {resolvingOwners ? "…resolving" : ""}</th>
|
<th className="px-2 py-2 text-left">
|
||||||
|
Owner {resolvingOwners ? "…resolving" : ""}
|
||||||
|
</th>
|
||||||
<th className="px-2 py-2 text-left">Material</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">Coating</th>
|
||||||
<th className="px-2 py-2 text-left">Model</th>
|
<th className="px-2 py-2 text-left">Model</th>
|
||||||
<th className="px-2 py-2 text-left">Field</th>
|
<th className="px-2 py-2 text-left">Field</th>
|
||||||
|
<th className="px-2 py-2 text-left">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filtered.map((s) => (
|
{filtered.map((s) => {
|
||||||
<tr key={s.submission_id} className="border-b hover:bg-muted/30">
|
const mine = isMine(s.owner);
|
||||||
<td className="px-2 py-2 whitespace-nowrap">
|
const ownerText =
|
||||||
<Link href={`/settings/co2-galvo/${s.submission_id}`} className="underline">
|
ownerLabel(s.owner) + (mine ? " (you)" : "");
|
||||||
<span dangerouslySetInnerHTML={{ __html: highlight(s.setting_title || "Untitled") }} />
|
return (
|
||||||
</Link>
|
<tr
|
||||||
</td>
|
key={s.submission_id}
|
||||||
<td
|
className="border-b hover:bg-muted/30"
|
||||||
className="px-2 py-2 whitespace-nowrap"
|
>
|
||||||
dangerouslySetInnerHTML={{ __html: highlight(ownerLabel(s.owner)) }}
|
<td className="px-2 py-2 whitespace-nowrap">
|
||||||
/>
|
<Link
|
||||||
<td className="px-2 py-2 whitespace-nowrap">{s.mat?.name || "—"}</td>
|
href={`/settings/co2-galvo/${s.submission_id}`}
|
||||||
<td className="px-2 py-2 whitespace-nowrap">{s.mat_coat?.name || "—"}</td>
|
className="underline"
|
||||||
<td className="px-2 py-2 whitespace-nowrap">{s.source?.model || "—"}</td>
|
>
|
||||||
<td className="px-2 py-2 whitespace-nowrap">{s.lens?.field_size || "—"}</td>
|
<span
|
||||||
</tr>
|
dangerouslySetInnerHTML={{
|
||||||
))}
|
__html: highlight(s.setting_title || "Untitled"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="px-2 py-2 whitespace-nowrap"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: highlight(ownerText),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<td className="px-2 py-2 whitespace-nowrap">
|
||||||
|
{s.mat?.name || "—"}
|
||||||
|
</td>
|
||||||
|
<td className="px-2 py-2 whitespace-nowrap">
|
||||||
|
{s.mat_coat?.name || "—"}
|
||||||
|
</td>
|
||||||
|
<td className="px-2 py-2 whitespace-nowrap">
|
||||||
|
{s.source?.model || "—"}
|
||||||
|
</td>
|
||||||
|
<td className="px-2 py-2 whitespace-nowrap">
|
||||||
|
{s.lens?.field_size || "—"}
|
||||||
|
</td>
|
||||||
|
<td className="px-2 py-2 whitespace-nowrap">
|
||||||
|
{mine ? (
|
||||||
|
<Link
|
||||||
|
href={`/settings/co2-galvo/${s.submission_id}?edit=1`}
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="opacity-50">—</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue