This commit is contained in:
@@ -75,6 +75,7 @@ export default function AssemblyDetailClient({
|
||||
const router = useRouter();
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [newPartOpen, setNewPartOpen] = useState(false);
|
||||
const [dupeOpen, setDupeOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-6xl px-4 py-8">
|
||||
@@ -103,6 +104,9 @@ export default function AssemblyDetailClient({
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<Button variant="secondary" onClick={() => setDupeOpen(true)}>
|
||||
Duplicate
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => setEditOpen(true)}>
|
||||
Edit assembly
|
||||
</Button>
|
||||
@@ -253,10 +257,97 @@ export default function AssemblyDetailClient({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{dupeOpen && (
|
||||
<DuplicateAssemblyModal
|
||||
assembly={assembly}
|
||||
onClose={() => setDupeOpen(false)}
|
||||
onCreated={(newAssemblyId) => {
|
||||
setDupeOpen(false);
|
||||
router.push(`/admin/projects/${project.id}/assemblies/${newAssemblyId}`);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DuplicateAssemblyModal({
|
||||
assembly,
|
||||
onClose,
|
||||
onCreated,
|
||||
}: {
|
||||
assembly: AssemblyInfo;
|
||||
onClose: () => void;
|
||||
onCreated: (id: string) => void;
|
||||
}) {
|
||||
const [code, setCode] = useState(`${assembly.code}-COPY`);
|
||||
const [name, setName] = useState(assembly.name);
|
||||
const [includeOperations, setIncludeOperations] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function submit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await apiFetch<{ assembly: { id: string } }>(
|
||||
`/api/v1/assemblies/${assembly.id}/duplicate`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ code, name, includeOperations }),
|
||||
},
|
||||
);
|
||||
onCreated(res.assembly.id);
|
||||
} catch (err) {
|
||||
setError(err instanceof ApiClientError ? err.message : "Duplicate failed");
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open
|
||||
onClose={onClose}
|
||||
title={`Duplicate ${assembly.code}`}
|
||||
footer={
|
||||
<>
|
||||
<div className="flex-1" />
|
||||
<Button variant="secondary" onClick={onClose} disabled={busy}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" form="asm-dup-form" disabled={busy}>
|
||||
{busy ? "Duplicating…" : "Duplicate"}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<form id="asm-dup-form" onSubmit={submit} className="space-y-4">
|
||||
<p className="text-sm text-slate-600">
|
||||
Creates a new assembly in the same project. Every part is cloned along
|
||||
with its file attachments. Operations get fresh QR codes and reset to
|
||||
pending.
|
||||
</p>
|
||||
<Field label="New code" required hint="Must be unique within this project.">
|
||||
<Input value={code} onChange={(e) => setCode(e.target.value)} required autoFocus />
|
||||
</Field>
|
||||
<Field label="Name">
|
||||
<Input value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</Field>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={includeOperations}
|
||||
onChange={(e) => setIncludeOperations(e.target.checked)}
|
||||
/>
|
||||
Copy operations (fresh QR codes, status reset to pending)
|
||||
</label>
|
||||
<ErrorBanner message={error} />
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function EditAssemblyModal({
|
||||
assembly,
|
||||
onClose,
|
||||
|
||||
Reference in New Issue
Block a user