Files
mrp-qrcode/app/admin/page.tsx
T
2026-04-21 08:56:51 -05:00

165 lines
5.6 KiB
TypeScript

import Link from "next/link";
import { prisma } from "@/lib/prisma";
export const dynamic = "force-dynamic";
export default async function AdminDashboardPage() {
const [
projectsTotal,
projectsActive,
assembliesTotal,
partsTotal,
operationsTotal,
operationsInProgress,
machinesActive,
templatesActive,
operatorsActive,
adminsActive,
recentProjects,
] = await Promise.all([
prisma.project.count(),
prisma.project.count({ where: { status: { in: ["planning", "in_progress"] } } }),
prisma.assembly.count(),
prisma.part.count(),
prisma.operation.count(),
prisma.operation.count({ where: { status: "in_progress" } }),
prisma.machine.count({ where: { active: true } }),
prisma.operationTemplate.count({ where: { active: true } }),
prisma.user.count({ where: { role: "operator", active: true } }),
prisma.user.count({ where: { role: "admin", active: true } }),
prisma.project.findMany({
orderBy: { updatedAt: "desc" },
take: 5,
select: {
id: true,
code: true,
name: true,
status: true,
updatedAt: true,
_count: { select: { assemblies: true } },
},
}),
]);
return (
<div className="mx-auto max-w-7xl px-4 py-8">
<h1 className="text-2xl font-semibold tracking-tight">Dashboard</h1>
<p className="text-slate-500 mt-1">Snapshot of the shop. Click any tile to dive in.</p>
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<Tile
href="/admin/projects"
title="Projects"
primary={projectsTotal}
secondary={`${projectsActive} active · ${assembliesTotal} assemblies · ${partsTotal} parts`}
/>
<Tile
href="/admin/projects"
title="Operations"
primary={operationsTotal}
secondary={`${operationsInProgress} in progress`}
/>
<Tile
href="/admin/machines"
title="Machines"
primary={machinesActive}
secondary="active"
/>
<Tile
href="/admin/operations"
title="Operation templates"
primary={templatesActive}
secondary="active"
/>
<Tile
href="/admin/users"
title="Users"
primary={adminsActive + operatorsActive}
secondary={`${adminsActive} admin${adminsActive === 1 ? "" : "s"} · ${operatorsActive} operator${operatorsActive === 1 ? "" : "s"}`}
/>
<div className="rounded-xl bg-white border border-slate-200 p-5">
<h2 className="font-medium text-slate-700">Fasteners & POs</h2>
<p className="text-sm text-slate-500 mt-1">Purchasing lifecycle lands in step 6.</p>
</div>
</div>
<section className="mt-10">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold">Recent projects</h2>
<Link href="/admin/projects" className="text-sm text-blue-600 hover:underline">
All projects
</Link>
</div>
<div className="rounded-xl bg-white border border-slate-200 overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-slate-50 text-left text-slate-600 border-b border-slate-200">
<tr>
<th className="px-4 py-2 font-medium">Code</th>
<th className="px-4 py-2 font-medium">Name</th>
<th className="px-4 py-2 font-medium">Status</th>
<th className="px-4 py-2 font-medium">Assemblies</th>
<th className="px-4 py-2 font-medium">Updated</th>
</tr>
</thead>
<tbody>
{recentProjects.map((p) => (
<tr key={p.id} className="border-b border-slate-100 last:border-0">
<td className="px-4 py-3 font-mono">
<Link href={`/admin/projects/${p.id}`} className="text-blue-600 hover:underline">
{p.code}
</Link>
</td>
<td className="px-4 py-3">{p.name}</td>
<td className="px-4 py-3 text-slate-600">{p.status}</td>
<td className="px-4 py-3 text-slate-600">{p._count.assemblies}</td>
<td className="px-4 py-3 text-slate-500">
{p.updatedAt.toLocaleDateString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
})}
</td>
</tr>
))}
{recentProjects.length === 0 && (
<tr>
<td colSpan={5} className="px-4 py-10 text-center text-slate-500">
No projects yet.{" "}
<Link href="/admin/projects" className="text-blue-600 hover:underline">
Create one
</Link>
.
</td>
</tr>
)}
</tbody>
</table>
</div>
</section>
</div>
);
}
function Tile({
href,
title,
primary,
secondary,
}: {
href: string;
title: string;
primary: number;
secondary: string;
}) {
return (
<Link
href={href}
className="block rounded-xl bg-white border border-slate-200 p-5 transition hover:border-slate-400 hover:shadow-sm"
>
<h2 className="font-medium text-slate-700">{title}</h2>
<p className="text-3xl font-semibold tracking-tight mt-2">{primary}</p>
<p className="text-xs text-slate-500 mt-1">{secondary}</p>
</Link>
);
}