Implements complete dashboard with metrics, recent activity, and quick navigation. Begins inventory system with service layer and category selector. Dashboard (C-001) - Complete: - Server component with parallel data fetching - Summary cards: WIP, Finished Goods, Total Weight, Recent Orders - Recent activity tables: top 5 orders and shipments - Alert banner for important notifications - Quick navigation cards to all major features - Loading skeletons with Suspense boundaries - Reusable components: SummaryCard, QuickNavCard, tables - Dashboard service with typed Epicor queries Inventory (C-002) - Core Complete: - Complete service layer for all 6 inventory categories - Category selector page with permission-based visibility - Reusable inventory summary table with search and CSV export - WIP inventory page as template for other categories - Sub-user permission enforcement at service layer - Type-safe stored procedure calls with proper error handling Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
110 lines
3 KiB
TypeScript
110 lines
3 KiB
TypeScript
import {
|
|
Package,
|
|
Cog,
|
|
CheckCircle,
|
|
Circle,
|
|
RotateCcw,
|
|
Recycle,
|
|
} from 'lucide-react';
|
|
import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
import Link from 'next/link';
|
|
import { isSubUser } from '@/lib/permissions';
|
|
|
|
const inventoryCategories = [
|
|
{
|
|
id: 'wip',
|
|
title: 'Work in Progress',
|
|
description: 'Active jobs and materials being processed',
|
|
icon: Cog,
|
|
color: 'text-blue-600',
|
|
href: '/inventory/wip',
|
|
requiresNonSubUser: false,
|
|
},
|
|
{
|
|
id: 'finished-goods',
|
|
title: 'Finished Goods',
|
|
description: 'Completed products ready for shipment',
|
|
icon: CheckCircle,
|
|
color: 'text-green-600',
|
|
href: '/inventory/finished-goods',
|
|
requiresNonSubUser: false,
|
|
},
|
|
{
|
|
id: 'processed-other',
|
|
title: 'Processed Other',
|
|
description: 'Other processed inventory items',
|
|
icon: Package,
|
|
color: 'text-purple-600',
|
|
href: '/inventory/processed-other',
|
|
requiresNonSubUser: false,
|
|
},
|
|
{
|
|
id: 'unprocessed',
|
|
title: 'Unprocessed',
|
|
description: 'Raw materials awaiting processing',
|
|
icon: Circle,
|
|
color: 'text-orange-600',
|
|
href: '/inventory/unprocessed',
|
|
requiresNonSubUser: true,
|
|
},
|
|
{
|
|
id: 'unprocessed-rr',
|
|
title: 'Unprocessed R&R',
|
|
description: 'Unprocessed returns and repairs',
|
|
icon: RotateCcw,
|
|
color: 'text-amber-600',
|
|
href: '/inventory/unprocessed-rr',
|
|
requiresNonSubUser: true,
|
|
},
|
|
{
|
|
id: 'processed-rr',
|
|
title: 'Processed R&R',
|
|
description: 'Processed returns and repairs',
|
|
icon: Recycle,
|
|
color: 'text-teal-600',
|
|
href: '/inventory/processed-rr',
|
|
requiresNonSubUser: true,
|
|
},
|
|
];
|
|
|
|
export default async function InventoryPage() {
|
|
const userIsSubUser = await isSubUser();
|
|
|
|
const availableCategories = inventoryCategories.filter(
|
|
(cat) => !cat.requiresNonSubUser || !userIsSubUser
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
<h1 className="mb-2 text-3xl font-bold">Inventory</h1>
|
|
<p className="mb-8 text-muted-foreground">
|
|
Browse inventory by category
|
|
</p>
|
|
|
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
{availableCategories.map((category) => (
|
|
<Link key={category.id} href={category.href}>
|
|
<Card className="transition-all hover:border-primary/50 hover:shadow-md">
|
|
<CardHeader>
|
|
<div className="mb-4 flex items-center justify-between">
|
|
<category.icon className={`h-10 w-10 ${category.color}`} />
|
|
</div>
|
|
<CardTitle className="mb-2">{category.title}</CardTitle>
|
|
<CardDescription>{category.description}</CardDescription>
|
|
</CardHeader>
|
|
</Card>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
{userIsSubUser && (
|
|
<div className="mt-6 rounded-lg border border-amber-200 bg-amber-50 p-4">
|
|
<p className="text-sm text-amber-800">
|
|
<strong>Note:</strong> Some inventory categories are restricted
|
|
for sub-users.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|