Files
mrp-qrcode/app/admin/projects/[id]/assemblies/[assemblyId]/parts/[partId]/page.tsx
T
jason 04ae88ca0d
Build and Push Docker Image / build (push) Successful in 45s
QoL changes and additions
2026-04-22 13:16:42 -05:00

152 lines
4.5 KiB
TypeScript

import { notFound } from "next/navigation";
import { prisma } from "@/lib/prisma";
import PartDetailClient from "./PartDetailClient";
export const dynamic = "force-dynamic";
export default async function AdminPartDetailPage({
params,
}: {
params: Promise<{ id: string; assemblyId: string; partId: string }>;
}) {
const { id, assemblyId, partId } = await params;
const [part, machines, templates] = await Promise.all([
prisma.part.findFirst({
where: { id: partId, assemblyId, assembly: { projectId: id } },
include: {
assembly: {
select: {
id: true,
code: true,
name: true,
project: { select: { id: true, code: true, name: true } },
},
},
stepFile: true,
drawingFile: true,
cutFile: true,
thumbnailFile: true,
operations: {
orderBy: { sequence: "asc" },
include: {
machine: { select: { id: true, name: true } },
template: { select: { id: true, name: true } },
timeLogs: {
orderBy: { startedAt: "desc" },
include: { operator: { select: { id: true, name: true } } },
},
},
},
},
}),
prisma.machine.findMany({
where: { active: true },
orderBy: { name: "asc" },
select: { id: true, name: true },
}),
prisma.operationTemplate.findMany({
where: { active: true },
orderBy: { name: "asc" },
select: {
id: true,
name: true,
machineId: true,
defaultSettings: true,
defaultInstructions: true,
qcRequired: true,
},
}),
]);
if (!part) notFound();
// QC history across every op on the part — newest first. Same data feeds
// two views: the "QC history" strip on the part page and the failure-digest
// on the admin dashboard. Keeping it to the last 50 per part is plenty for
// any realistic production run.
const qcRecords = await prisma.qCRecord.findMany({
where: { operation: { partId: part.id } },
orderBy: { createdAt: "desc" },
take: 50,
include: {
operator: { select: { id: true, name: true } },
operation: { select: { id: true, sequence: true, name: true } },
},
});
const fileView = (f: typeof part.stepFile) =>
f
? {
id: f.id,
originalName: f.originalName,
sizeBytes: f.sizeBytes,
kind: f.kind,
mimeType: f.mimeType,
}
: null;
return (
<PartDetailClient
project={part.assembly.project}
assembly={{ id: part.assembly.id, code: part.assembly.code, name: part.assembly.name }}
part={{
id: part.id,
code: part.code,
name: part.name,
material: part.material,
qty: part.qty,
notes: part.notes,
stepFile: fileView(part.stepFile),
drawingFile: fileView(part.drawingFile),
cutFile: fileView(part.cutFile),
thumbnailFileId: part.thumbnailFileId,
}}
operations={part.operations.map((op) => ({
id: op.id,
sequence: op.sequence,
name: op.name,
kind: op.kind,
machineId: op.machineId,
machineName: op.machine?.name ?? null,
templateId: op.templateId,
templateName: op.template?.name ?? null,
settings: op.settings,
materialNotes: op.materialNotes,
instructions: op.instructions,
qcRequired: op.qcRequired,
plannedMinutes: op.plannedMinutes,
plannedUnits: op.plannedUnits,
status: op.status,
qrToken: op.qrToken,
timeLogs: op.timeLogs.map((l) => ({
id: l.id,
startedAt: l.startedAt.toISOString(),
endedAt: l.endedAt ? l.endedAt.toISOString() : null,
unitsProcessed: l.unitsProcessed,
note: l.note,
operatorName: l.operator.name,
})),
}))}
machines={machines}
templates={templates.map((t) => ({
id: t.id,
name: t.name,
machineId: t.machineId,
defaultSettings: t.defaultSettings,
defaultInstructions: t.defaultInstructions,
qcRequired: t.qcRequired,
}))}
qcRecords={qcRecords.map((r) => ({
id: r.id,
kind: r.kind,
passed: r.passed,
notes: r.notes,
measurements: r.measurements,
createdAt: r.createdAt.toISOString(),
operatorName: r.operator.name,
operationSequence: r.operation.sequence,
operationName: r.operation.name,
}))}
/>
);
}