From d79aaf6ef8e5c8a6fca8e4d9670e8d816bc46c37 Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 21 Apr 2026 08:56:51 -0500 Subject: [PATCH] phase 2 and 3 --- .claude/settings.local.json | 4 +- app/admin/machines/MachinesClient.tsx | 196 +++++ app/admin/machines/page.tsx | 15 + app/admin/operations/TemplatesClient.tsx | 235 ++++++ app/admin/operations/page.tsx | 33 + app/admin/page.tsx | 169 +++- app/admin/projects/ProjectsClient.tsx | 279 +++++++ .../projects/[id]/ProjectDetailClient.tsx | 268 ++++++ .../[assemblyId]/AssemblyDetailClient.tsx | 338 ++++++++ .../[id]/assemblies/[assemblyId]/page.tsx | 53 ++ .../parts/[partId]/PartDetailClient.tsx | 784 ++++++++++++++++++ .../[assemblyId]/parts/[partId]/page.tsx | 111 +++ app/admin/projects/[id]/page.tsx | 48 ++ app/admin/projects/page.tsx | 24 + app/admin/users/UsersClient.tsx | 287 +++++++ app/admin/users/page.tsx | 20 + app/api/v1/assemblies/[id]/parts/route.ts | 54 ++ app/api/v1/assemblies/[id]/route.ts | 78 ++ app/api/v1/files/[id]/download/route.ts | 33 + app/api/v1/files/[id]/route.ts | 64 ++ app/api/v1/files/route.ts | 53 ++ app/api/v1/machines/[id]/route.ts | 79 ++ app/api/v1/machines/route.ts | 46 + app/api/v1/operation-templates/[id]/route.ts | 84 ++ app/api/v1/operation-templates/route.ts | 48 ++ app/api/v1/operations/[id]/route.ts | 126 +++ .../v1/parts/[id]/operations/reorder/route.ts | 87 ++ app/api/v1/parts/[id]/operations/route.ts | 133 +++ app/api/v1/parts/[id]/route.ts | 90 ++ app/api/v1/projects/[id]/assemblies/route.ts | 53 ++ app/api/v1/projects/[id]/route.ts | 84 ++ app/api/v1/projects/route.ts | 48 ++ app/api/v1/users/[id]/route.ts | 114 +++ app/api/v1/users/route.ts | 81 ++ components/ui.tsx | 226 +++++ lib/api.ts | 73 ++ lib/client-api.ts | 29 + lib/env.ts | 19 +- lib/files.ts | 162 ++++ lib/qr.ts | 14 + lib/schemas.ts | 237 ++++++ tsconfig.tsbuildinfo | 2 +- 42 files changed, 4962 insertions(+), 19 deletions(-) create mode 100644 app/admin/machines/MachinesClient.tsx create mode 100644 app/admin/machines/page.tsx create mode 100644 app/admin/operations/TemplatesClient.tsx create mode 100644 app/admin/operations/page.tsx create mode 100644 app/admin/projects/ProjectsClient.tsx create mode 100644 app/admin/projects/[id]/ProjectDetailClient.tsx create mode 100644 app/admin/projects/[id]/assemblies/[assemblyId]/AssemblyDetailClient.tsx create mode 100644 app/admin/projects/[id]/assemblies/[assemblyId]/page.tsx create mode 100644 app/admin/projects/[id]/assemblies/[assemblyId]/parts/[partId]/PartDetailClient.tsx create mode 100644 app/admin/projects/[id]/assemblies/[assemblyId]/parts/[partId]/page.tsx create mode 100644 app/admin/projects/[id]/page.tsx create mode 100644 app/admin/projects/page.tsx create mode 100644 app/admin/users/UsersClient.tsx create mode 100644 app/admin/users/page.tsx create mode 100644 app/api/v1/assemblies/[id]/parts/route.ts create mode 100644 app/api/v1/assemblies/[id]/route.ts create mode 100644 app/api/v1/files/[id]/download/route.ts create mode 100644 app/api/v1/files/[id]/route.ts create mode 100644 app/api/v1/files/route.ts create mode 100644 app/api/v1/machines/[id]/route.ts create mode 100644 app/api/v1/machines/route.ts create mode 100644 app/api/v1/operation-templates/[id]/route.ts create mode 100644 app/api/v1/operation-templates/route.ts create mode 100644 app/api/v1/operations/[id]/route.ts create mode 100644 app/api/v1/parts/[id]/operations/reorder/route.ts create mode 100644 app/api/v1/parts/[id]/operations/route.ts create mode 100644 app/api/v1/parts/[id]/route.ts create mode 100644 app/api/v1/projects/[id]/assemblies/route.ts create mode 100644 app/api/v1/projects/[id]/route.ts create mode 100644 app/api/v1/projects/route.ts create mode 100644 app/api/v1/users/[id]/route.ts create mode 100644 app/api/v1/users/route.ts create mode 100644 components/ui.tsx create mode 100644 lib/api.ts create mode 100644 lib/client-api.ts create mode 100644 lib/files.ts create mode 100644 lib/qr.ts create mode 100644 lib/schemas.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d9be7c4..07fd7ac 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,9 @@ "Bash(npm install *)", "Bash(DATABASE_URL=\"file:./dev.db\" npx prisma migrate dev --name init --skip-seed)", "Bash(DATABASE_URL=\"file:./dev.db\" APP_SECRET=\"dev-only-change-me-dev-only-change-me-dev-only-change-me\" npx tsc --noEmit)", - "Bash(DATABASE_URL=\"file:./dev.db\" APP_SECRET=\"dev-only-change-me-dev-only-change-me-dev-only-change-me\" npm run build)" + "Bash(DATABASE_URL=\"file:./dev.db\" APP_SECRET=\"dev-only-change-me-dev-only-change-me-dev-only-change-me\" npm run build)", + "Bash(npx tsc *)", + "Bash(npx next *)" ] } } diff --git a/app/admin/machines/MachinesClient.tsx b/app/admin/machines/MachinesClient.tsx new file mode 100644 index 0000000..52a0114 --- /dev/null +++ b/app/admin/machines/MachinesClient.tsx @@ -0,0 +1,196 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Badge, Button, Card, ErrorBanner, Field, Input, Modal, PageHeader, Select, Textarea } from "@/components/ui"; +import { apiFetch, ApiClientError } from "@/lib/client-api"; + +interface MachineRow { + id: string; + name: string; + kind: string; + location: string | null; + notes: string | null; + active: boolean; + createdAt: string; + updatedAt: string; +} + +const KINDS = ["NCT_PUNCH", "PRESS_BRAKE", "RIVET", "WELD", "LASER", "SHEAR", "ASSEMBLY", "OTHER"] as const; + +const KIND_LABEL: Record = { + NCT_PUNCH: "NCT punch", + PRESS_BRAKE: "Press brake", + RIVET: "Rivet", + WELD: "Weld", + LASER: "Laser", + SHEAR: "Shear", + ASSEMBLY: "Assembly", + OTHER: "Other", +}; + +export default function MachinesClient({ initial }: { initial: MachineRow[] }) { + const router = useRouter(); + const [edit, setEdit] = useState(null); + const [newOpen, setNewOpen] = useState(false); + + return ( +
+ setNewOpen(true)}>New machine} + /> + + + + + + + + + + + + + {initial.map((m) => ( + + + + + + + + ))} + {initial.length === 0 && ( + + + + )} + +
NameTypeLocationStatus
{m.name}{KIND_LABEL[m.kind] ?? m.kind}{m.location ?? "—"} + {m.active ? "active" : "inactive"} + + +
+ No machines yet. +
+
+ + {newOpen && setNewOpen(false)} onSaved={() => router.refresh()} />} + {edit && setEdit(null)} onSaved={() => router.refresh()} />} +
+ ); +} + +function MachineModal({ + machine, + onClose, + onSaved, +}: { + machine?: MachineRow; + onClose: () => void; + onSaved: () => void; +}) { + const editing = !!machine; + const [name, setName] = useState(machine?.name ?? ""); + const [kind, setKind] = useState(machine?.kind ?? "OTHER"); + const [location, setLocation] = useState(machine?.location ?? ""); + const [notes, setNotes] = useState(machine?.notes ?? ""); + const [active, setActive] = useState(machine?.active ?? true); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setBusy(true); + setError(null); + try { + if (editing) { + await apiFetch(`/api/v1/machines/${machine!.id}`, { + method: "PATCH", + body: JSON.stringify({ name, kind, location, notes, active }), + }); + } else { + await apiFetch("/api/v1/machines", { + method: "POST", + body: JSON.stringify({ name, kind, location, notes }), + }); + } + onSaved(); + onClose(); + } catch (err) { + setError(err instanceof ApiClientError ? err.message : "Save failed"); + } finally { + setBusy(false); + } + } + + async function deactivate() { + if (!machine || !confirm(`Deactivate ${machine.name}?`)) return; + setBusy(true); + setError(null); + try { + await apiFetch(`/api/v1/machines/${machine.id}`, { method: "DELETE" }); + onSaved(); + onClose(); + } catch (err) { + setError(err instanceof ApiClientError ? err.message : "Deactivate failed"); + setBusy(false); + } + } + + return ( + + {editing && machine!.active && ( + + )} +
+ + + + } + > +
+ + setName(e.target.value)} required maxLength={200} /> + + + + + + setLocation(e.target.value)} /> + + +