100 lines
5.3 KiB
TypeScript
100 lines
5.3 KiB
TypeScript
import { formatCurrency } from "@/lib/format";
|
|
import { getDashboardStats, getLowStockParts, getPurchaseOrders, getSalesOrders } from "@/lib/repository";
|
|
|
|
export default function DashboardPage() {
|
|
const stats = getDashboardStats();
|
|
const salesOrders = getSalesOrders().slice(0, 5);
|
|
const purchaseOrders = getPurchaseOrders().slice(0, 5);
|
|
const lowStockParts = getLowStockParts().slice(0, 8);
|
|
|
|
return (
|
|
<div className="grid">
|
|
<section className="grid cards">
|
|
<article className="card"><div className="muted">Stocked Parts</div><div className="stats-value">{stats.totalParts}</div></article>
|
|
<article className="card"><div className="muted">Assemblies</div><div className="stats-value">{stats.totalAssemblies}</div></article>
|
|
<article className="card"><div className="muted">Open Sales Orders</div><div className="stats-value">{stats.openSalesOrders}</div></article>
|
|
<article className="card"><div className="muted">Open Purchase Orders</div><div className="stats-value">{stats.openPurchaseOrders}</div></article>
|
|
<article className="card"><div className="muted">Open Invoices</div><div className="stats-value">{stats.openInvoices}</div></article>
|
|
<article className="card"><div className="muted">Open Vendor Bills</div><div className="stats-value">{stats.openVendorBills}</div></article>
|
|
<article className="card"><div className="muted">Low Stock Alerts</div><div className="stats-value">{stats.lowStockCount}</div></article>
|
|
<article className="card"><div className="muted">Inventory Value</div><div className="stats-value">{formatCurrency(stats.inventoryValue)}</div></article>
|
|
<article className="card"><div className="muted">Accounts Receivable</div><div className="stats-value">{formatCurrency(stats.accountsReceivable)}</div></article>
|
|
<article className="card"><div className="muted">Accounts Payable</div><div className="stats-value">{formatCurrency(stats.accountsPayable)}</div></article>
|
|
</section>
|
|
<section className="two-up">
|
|
<article className="panel">
|
|
<h2 className="section-title">Recent Sales Orders</h2>
|
|
<p className="section-copy">Track outbound work and spot orders that are ready to ship.</p>
|
|
<div className="table-wrap">
|
|
<table className="table">
|
|
<thead><tr><th>Order</th><th>Customer</th><th>Status</th><th>Total</th></tr></thead>
|
|
<tbody>
|
|
{salesOrders.length === 0 ? (
|
|
<tr><td colSpan={4} className="muted">No sales orders yet.</td></tr>
|
|
) : (
|
|
salesOrders.map((order) => (
|
|
<tr key={order.id}>
|
|
<td>{order.orderNumber}</td>
|
|
<td>{order.customerName}</td>
|
|
<td><span className={`pill ${order.status === "shipped" ? "" : "warning"}`}>{order.status}</span></td>
|
|
<td>{formatCurrency(order.totalAmount)}</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</article>
|
|
<article className="panel">
|
|
<h2 className="section-title">Recent Purchase Orders</h2>
|
|
<p className="section-copy">Move supply from ordered to received and feed inventory automatically.</p>
|
|
<div className="table-wrap">
|
|
<table className="table">
|
|
<thead><tr><th>Order</th><th>Vendor</th><th>Status</th><th>Total</th></tr></thead>
|
|
<tbody>
|
|
{purchaseOrders.length === 0 ? (
|
|
<tr><td colSpan={4} className="muted">No purchase orders yet.</td></tr>
|
|
) : (
|
|
purchaseOrders.map((order) => (
|
|
<tr key={order.id}>
|
|
<td>{order.orderNumber}</td>
|
|
<td>{order.vendorName}</td>
|
|
<td><span className={`pill ${order.status === "received" ? "" : "warning"}`}>{order.status}</span></td>
|
|
<td>{formatCurrency(order.totalAmount)}</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</article>
|
|
</section>
|
|
<section className="panel">
|
|
<h2 className="section-title">Replenishment Watchlist</h2>
|
|
<p className="section-copy">Items at or below reorder point, with a suggested quantity to bring them back up.</p>
|
|
<div className="table-wrap">
|
|
<table className="table">
|
|
<thead><tr><th>SKU</th><th>Name</th><th>On Hand</th><th>Reorder Point</th><th>Suggested Reorder</th><th>Last Vendor</th></tr></thead>
|
|
<tbody>
|
|
{lowStockParts.length === 0 ? (
|
|
<tr><td colSpan={6} className="muted">No low stock items right now.</td></tr>
|
|
) : (
|
|
lowStockParts.map((part) => (
|
|
<tr key={part.id}>
|
|
<td>{part.sku}</td>
|
|
<td>{part.name}</td>
|
|
<td>{part.quantityOnHand} {part.unitOfMeasure}</td>
|
|
<td>{part.reorderPoint}</td>
|
|
<td>{part.suggestedReorderQuantity}</td>
|
|
<td>{part.preferredVendorName || "—"}</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|