refactor(wholesale): split 2391-line WholesaleClient into focused modules
The Wholesale Portal admin client was the largest single file in the app (2391 lines) and mixed 11 distinct UI concerns in one module. Split into focused files under src/components/wholesale/admin/: types.ts shared types (MsgFn, PendingRegistration, ...) WholesaleIcon.tsx header SVG WholesaleLoadingSkeleton.tsx loading state StatusBadge.tsx order status pill AddRecipientForm.tsx notification recipient input PriceSheetModal.tsx bulk price-sheet send confirmation CustomerPricingPanel.tsx per-customer pricing overrides WebhookSettingsSection.tsx outbound webhook config + test dispatch DashboardTab.tsx stat cards + recent orders + webhook feed ProductsTab.tsx wholesale product CRUD CustomersTab.tsx customers + registrations + pricing OrdersTab.tsx orders + bulk ops + deposit modals SettingsTab.tsx portal settings + webhook + recipients WholesaleClient.tsx is now a 189-line shell that owns data loading and tab routing. No behavior changes; the original logic was preserved verbatim. Mechanical extraction only. Also fixed an existing import bug where getPendingWholesaleRegistrations was being imported from @/actions/wholesale instead of @/actions/wholesale-register (typecheck caught it). Verified: typecheck clean for new code (pre-existing dahlia/fetch mock errors unrelated). Build compiles through to the same pre-existing billing type error. Zero new lint warnings introduced. Refactor-progress tracked at /tmp/refactor-routecomm.md.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import type { WholesaleOrder, WholesaleDashboardStats } from "@/actions/wholesale";
|
||||
import StatusBadge from "./StatusBadge";
|
||||
import type { MsgFn, WebhookActivityEntry } from "./types";
|
||||
|
||||
interface DashboardTabProps {
|
||||
stats: WholesaleDashboardStats;
|
||||
recentOrders: WholesaleOrder[];
|
||||
webhookActivity: WebhookActivityEntry[];
|
||||
}
|
||||
|
||||
// Top-level dashboard: stat cards + recent orders table + recent webhook activity.
|
||||
export default function DashboardTab({ stats, recentOrders, webhookActivity }: DashboardTabProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Stat cards */}
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-6">
|
||||
{[
|
||||
{ label: "Open Orders", value: stats.open_orders, variant: "default" },
|
||||
{ label: "Pickup Today", value: stats.pickup_today, variant: "default" },
|
||||
{ label: "Past Due", value: stats.past_due, variant: "danger" },
|
||||
{ label: "Total Unpaid", value: `$${stats.total_unpaid.toFixed(2)}`, variant: "default" },
|
||||
{ label: "Awaiting Deposit", value: stats.awaiting_deposit, variant: "warning" },
|
||||
{ label: "Fulfilled Today", value: stats.fulfilled_today, variant: "success" },
|
||||
].map((card) => (
|
||||
<div key={card.label} className={`rounded-xl border shadow-sm ${card.variant === "danger" ? "bg-[var(--admin-danger-light)] border-[var(--admin-danger)]" : card.variant === "warning" ? "bg-[var(--admin-warning-light)] border-[var(--admin-warning)]" : card.variant === "success" ? "bg-[var(--admin-accent-light)] border-[var(--admin-accent)]" : "bg-white border-[var(--admin-border)]"}`}>
|
||||
<p className={`text-xs ${card.variant === "danger" ? "text-[var(--admin-danger)]" : card.variant === "warning" ? "text-[var(--admin-warning)]" : card.variant === "success" ? "text-[var(--admin-accent-text)]" : "text-[var(--admin-text-muted)]"}`}>{card.label}</p>
|
||||
<p className="mt-1 text-2xl font-bold text-[var(--admin-text-primary)]">{card.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Recent orders */}
|
||||
<div className="rounded-2xl bg-white border border-[var(--admin-border)] p-6 shadow-sm">
|
||||
<h2 className="text-lg font-semibold text-[var(--admin-text-primary)] mb-4">Recent Orders</h2>
|
||||
{recentOrders.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-14 h-14 bg-slate-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-7 h-7 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-base font-semibold text-slate-600 mb-1">No wholesale orders yet</p>
|
||||
<p className="text-sm text-slate-400">Wholesale orders placed by your customers will appear here.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-left text-[var(--admin-text-muted)] border-b">
|
||||
<th className="pb-3 font-medium">Invoice</th>
|
||||
<th className="pb-3 font-medium">Customer</th>
|
||||
<th className="pb-3 font-medium">Pickup Date</th>
|
||||
<th className="pb-3 font-medium text-right">Total</th>
|
||||
<th className="pb-3 font-medium">Status</th>
|
||||
<th className="pb-3 font-medium">Payment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--admin-border-light)]">
|
||||
{recentOrders.map((order) => (
|
||||
<tr key={order.id} className="hover:bg-[var(--admin-bg-subtle)]">
|
||||
<td className="py-3 font-mono text-xs text-[var(--admin-text-muted)]">{order.invoice_number ?? "—"}</td>
|
||||
<td className="py-3 font-medium text-[var(--admin-text-primary)]">{order.company_name}</td>
|
||||
<td className="py-3 text-[var(--admin-text-secondary)]">{order.anticipated_pickup_date ?? "—"}</td>
|
||||
<td className="py-3 text-right font-semibold text-[var(--admin-text-primary)]">${Number(order.subtotal).toFixed(2)}</td>
|
||||
<td className="py-3">
|
||||
<StatusBadge status={order.status} />
|
||||
</td>
|
||||
<td className="py-3">
|
||||
<span className={`text-xs font-medium ${
|
||||
order.payment_status === "paid" ? "text-[var(--admin-accent)]" : "text-[var(--admin-warning)]"
|
||||
}`}>
|
||||
{order.payment_status === "paid" ? "Paid" : order.balance_due > 0 ? `$${Number(order.balance_due).toFixed(2)} due` : "Partial"}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Recent webhook activity */}
|
||||
{webhookActivity.length > 0 && (
|
||||
<div className="rounded-2xl bg-white border border-[var(--admin-border)] p-6 shadow-sm">
|
||||
<h2 className="text-lg font-semibold text-[var(--admin-text-primary)] mb-4">Recent Webhook Activity</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-left text-[var(--admin-text-muted)] border-b">
|
||||
<th className="pb-3 font-medium">Event</th>
|
||||
<th className="pb-3 font-medium">Order</th>
|
||||
<th className="pb-3 font-medium">Status</th>
|
||||
<th className="pb-3 font-medium">Attempts</th>
|
||||
<th className="pb-3 font-medium">Sent At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[var(--admin-border-light)]">
|
||||
{webhookActivity.map((entry) => (
|
||||
<tr key={entry.id} className="hover:bg-[var(--admin-bg-subtle)]">
|
||||
<td className="py-3">
|
||||
<span className="font-mono text-xs bg-[var(--admin-accent)] text-white px-2 py-0.5 rounded">{entry.event_type}</span>
|
||||
</td>
|
||||
<td className="py-3 font-mono text-xs text-[var(--admin-text-muted)]">{entry.order_id ? entry.order_id.slice(0, 8) : "—"}</td>
|
||||
<td className="py-3">
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${
|
||||
entry.status === "sent" ? "bg-[var(--admin-accent-light)] text-[var(--admin-accent-text)]" :
|
||||
entry.status === "failed" ? "bg-[var(--admin-danger-light)] text-[var(--admin-danger)]" :
|
||||
entry.status === "retrying" ? "bg-[var(--admin-warning-light)] text-[var(--admin-warning)]" :
|
||||
"bg-[var(--admin-border)] text-[var(--admin-text-secondary)]"
|
||||
}`}>
|
||||
{entry.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 text-[var(--admin-text-muted)]">{entry.attempts}</td>
|
||||
<td className="py-3 text-[var(--admin-text-muted)]">{new Date(entry.created_at).toLocaleString()}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user