phase 2 and 3
This commit is contained in:
+153
-16
@@ -1,27 +1,164 @@
|
||||
export default function AdminDashboardPage() {
|
||||
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">Dashboard</h1>
|
||||
<p className="text-slate-500 mt-1">
|
||||
Project planning, machines, operations, and users will appear here as each area is built.
|
||||
</p>
|
||||
<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">
|
||||
<Card title="Projects" desc="Plan work: assemblies, parts, and operations." />
|
||||
<Card title="Machines" desc="Manage shop-floor equipment." />
|
||||
<Card title="Operation templates" desc="Reusable step recipes." />
|
||||
<Card title="Fasteners & POs" desc="Aggregate BOM, generate purchase orders." />
|
||||
<Card title="Users" desc="Admins and operator PIN accounts." />
|
||||
<Card title="Audit log" desc="Who did what, when." />
|
||||
<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 Card({ title, desc }: { title: string; desc: string }) {
|
||||
function Tile({
|
||||
href,
|
||||
title,
|
||||
primary,
|
||||
secondary,
|
||||
}: {
|
||||
href: string;
|
||||
title: string;
|
||||
primary: number;
|
||||
secondary: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-xl bg-white border border-slate-200 p-5">
|
||||
<h2 className="font-medium">{title}</h2>
|
||||
<p className="text-sm text-slate-500 mt-1">{desc}</p>
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user