Files
route-commerce/src/components/wholesale/admin/OrdersTab.tsx
T
Nora 16a6756ad1 fix: react-doctor label-has-associated-control (-15, nested-interactive -1)
Add htmlFor/id pairs to label/input pairs across ~24 files.
Convert section-header labels (Role/Permissions/Visibility/Environment/
Send via/Quick messages/Campaign Type/When to Send/Preview) to <p>.
Convert clickable divs to buttons (AbandonedCartDashboard → native dialog).
Hoist regex patterns out of loops in ai-import.ts.
2026-06-26 04:02:10 -06:00

537 lines
29 KiB
TypeScript

import { useEffect, useState } from "react";
import { AdminButton, AdminSearchInput } from "@/components/admin/design-system";
import { openHtmlInPopup } from "@/lib/safe-window";
import {
type WholesaleOrder,
type WholesaleCustomer,
bulkFulfillWholesaleOrders,
bulkRecordWholesaleDeposit,
markWholesaleOrderFulfilled,
recordWholesaleDeposit,
updateWholesaleOrderStatus,
deleteWholesaleOrder,
getWholesaleSettings,
enqueueWholesaleNotification,
} from "@/actions/wholesale";
import OrderDetailsModal from "@/components/wholesale/OrderDetailsModal";
import StatusBadge from "./StatusBadge";
import type { MsgFn } from "./types";
interface OrdersTabProps {
orders: WholesaleOrder[];
customers: WholesaleCustomer[];
brandId: string;
onMsg: MsgFn;
onRefresh: () => void;
}
// Orders tab — list/filter orders, fulfill, record deposits, bulk operations,
// cancel/delete, generate manifests, and dispatch notification emails on fulfillment.
export default function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: OrdersTabProps) {
const [showDepForm, setShowDepForm] = useState<string | null>(null);
const [depAmount, setDepAmount] = useState("");
const [depMethod, setDepMethod] = useState("cash");
const [fulfilling, setFulfilling] = useState<string | null>(null);
const [manifestLoading, setManifestLoading] = useState(false);
const [selected, setSelected] = useState<Set<string>>(new Set());
const [statusFilter, setStatusFilter] = useState("all");
const [dateFrom, setDateFrom] = useState("");
const [dateTo, setDateTo] = useState("");
const [searchQuery, setSearchQuery] = useState("");
const [bulkDepAmount, setBulkDepAmount] = useState("");
const [bulkDepMethod, setBulkDepMethod] = useState("cash");
const [showBulkDep, setShowBulkDep] = useState(false);
const [bulkLoading, setBulkLoading] = useState(false);
const [openActions, setOpenActions] = useState<string | null>(null);
const [showViewOrder, setShowViewOrder] = useState<WholesaleOrder | null>(null);
const [deleting, setDeleting] = useState<string | null>(null);
const filtered = orders.filter(o => {
if (statusFilter !== "all" && o.status !== statusFilter) return false;
if (dateFrom && (!o.anticipated_pickup_date || o.anticipated_pickup_date < dateFrom)) return false;
if (dateTo && (!o.anticipated_pickup_date || o.anticipated_pickup_date > dateTo)) return false;
if (searchQuery) {
const q = searchQuery.toLowerCase();
if (!(o.company_name?.toLowerCase().includes(q) || o.invoice_number?.toLowerCase().includes(q) || o.customer_email?.toLowerCase().includes(q))) return false;
}
return true;
});
function toggleSelect(id: string) {
setSelected(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
}
function toggleAll() {
if (selected.size === filtered.length) { setSelected(new Set()); }
else { setSelected(new Set(filtered.map(o => o.id))); }
}
async function handleBulkFulfill() {
if (selected.size === 0) return;
setBulkLoading(true);
const result = await bulkFulfillWholesaleOrders(Array.from(selected));
setBulkLoading(false);
if (result.success) {
onMsg("success", `${result.count} order(s) marked as fulfilled.`);
setSelected(new Set());
onRefresh();
} else {
onMsg("error", result.error ?? "Bulk fulfill failed.");
}
}
async function handleBulkDeposit() {
if (selected.size === 0 || !bulkDepAmount) return;
setBulkLoading(true);
const result = await bulkRecordWholesaleDeposit(Array.from(selected), Number(bulkDepAmount), bulkDepMethod);
setBulkLoading(false);
if (result.success) {
onMsg("success", `Deposit recorded on ${result.count} order(s).`);
setSelected(new Set());
setShowBulkDep(false);
setBulkDepAmount("");
onRefresh();
} else {
onMsg("error", result.error ?? "Bulk deposit failed.");
}
}
async function handleFulfill(orderId: string) {
setFulfilling(orderId);
const result = await markWholesaleOrderFulfilled(orderId);
setFulfilling(null);
if (result.success) {
onMsg("success", "Order marked as fulfilled.");
onRefresh();
// Enqueue order fulfilled notification
const order = orders.find(o => o.id === orderId);
const customer = order ? customers.find(c => c.email === order.customer_email) : null;
if (order && customer) {
enqueueWholesaleNotification({
brandId: brandId,
customerId: customer.id,
orderId,
type: "order_fulfilled",
emailTo: customer.email,
subject: `Order ${order.invoice_number ?? orderId.slice(0, 8)} Ready for Pickup`,
bodyHtml: `
<h2>Your Order is Ready!</h2>
<p>Hi ${customer.company_name},</p>
<p>Your wholesale order <strong>${order.invoice_number ?? orderId.slice(0, 8)}</strong> has been fulfilled and is ready for pickup${order.anticipated_pickup_date ? ` on ${order.anticipated_pickup_date}` : ""}.</p>
<p>Thank you for your business!</p>
`,
bodyText: `Order ${order.invoice_number ?? orderId.slice(0, 8)} has been fulfilled and is ready for pickup${order.anticipated_pickup_date ? ` on ${order.anticipated_pickup_date}` : ""}. Thank you!`,
});
// Also notify the admin
const settings = await getWholesaleSettings(brandId);
const adminEmail = settings?.notification_email ?? settings?.from_email ?? settings?.invoice_business_email ?? null;
if (adminEmail) {
enqueueWholesaleNotification({
brandId: brandId,
customerId: customer.id,
orderId,
type: "order_fulfilled",
emailTo: adminEmail,
subject: `[Admin] Order Fulfilled — ${order.invoice_number ?? orderId.slice(0, 8)}`,
bodyHtml: `
<h2>Order Fulfilled</h2>
<p>Order <strong>${order.invoice_number ?? orderId.slice(0, 8)}</strong> for <strong>${customer.company_name}</strong> has been marked as fulfilled.</p>
${order.anticipated_pickup_date ? `<p><strong>Pickup Date:</strong> ${order.anticipated_pickup_date}</p>` : ""}
`,
bodyText: `Order ${order.invoice_number ?? orderId.slice(0, 8)} for ${customer.company_name} has been fulfilled.`,
});
}
// Trigger notification send (fire-and-forget)
fetch(`/api/wholesale/notifications/send`, { method: "POST" }).catch(() => {});
}
} else {
onMsg("error", result.error ?? "Failed to fulfill order.");
}
}
// Close actions dropdown when clicking outside
useEffect(() => {
function handleClick(e: MouseEvent) {
if (!(e.target as Element).closest(".actions-cell")) {
setOpenActions(null);
}
}
document.addEventListener("click", handleClick);
return () => document.removeEventListener("click", handleClick);
}, []);
function toggleActions(orderId: string, e: React.MouseEvent) {
e.stopPropagation();
setOpenActions(prev => prev === orderId ? null : orderId);
}
async function handleRecordDeposit(orderId: string) {
const result = await recordWholesaleDeposit(orderId, Number(depAmount), depMethod);
setShowDepForm(null);
setDepAmount("");
if (result.success) {
onMsg("success", "Deposit recorded.");
onRefresh();
} else {
onMsg("error", result.error ?? "Failed to record deposit.");
}
}
async function handleCancelOrder(orderId: string) {
if (!confirm("Cancel this order? This cannot be undone.")) return;
const result = await updateWholesaleOrderStatus(orderId, "cancelled");
if (result.success) {
onMsg("success", "Order cancelled.");
onRefresh();
} else {
onMsg("error", result.error ?? "Failed to cancel order.");
}
}
async function handleDeleteOrder(orderId: string) {
if (!confirm("Delete this order? Fulfilled and paid orders cannot be deleted. This cannot be undone.")) return;
setDeleting(orderId);
const result = await deleteWholesaleOrder(orderId);
setDeleting(null);
if (result.success) {
onMsg("success", "Order deleted.");
onRefresh();
} else {
onMsg("error", result.error ?? "Failed to delete order.");
}
}
return (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-[var(--admin-text-primary)]">Wholesale Orders ({orders.length})</h2>
{/* Filter bar */}
<div className="flex flex-wrap items-center gap-3 rounded-2xl bg-white border border-[var(--admin-border)] p-4 shadow-sm">
<div>
<label htmlFor="fld-1-status" className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Status</label>
<select id="fld-1-status" value={statusFilter} onChange={e => setStatusFilter(e.target.value)}
className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]">
<option value="all">All Statuses</option>
<option value="pending">Pending</option>
<option value="awaiting_deposit">Awaiting Deposit</option>
<option value="confirmed">Confirmed</option>
<option value="fulfilled">Fulfilled</option>
</select>
</div>
<div>
<label htmlFor="fld-2-pickup-from" className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Pickup From</label>
<input id="fld-2-pickup-from" type="date" value={dateFrom} onChange={e => setDateFrom(e.target.value)}
className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]" />
</div>
<div>
<label htmlFor="fld-3-pickup-to" className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Pickup To</label>
<input id="fld-3-pickup-to" type="date" value={dateTo} onChange={e => setDateTo(e.target.value)}
className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]" />
</div>
<div className="flex-1 min-w-[180px]">
<label className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-search">Search</label>
<AdminSearchInput
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
onClear={() => setSearchQuery("")}
placeholder="Invoice, customer, email..."
/>
</div>
{filtered.length !== orders.length && (
<div className="self-end pb-1">
<span className="text-xs text-[var(--admin-text-muted)]">{filtered.length} of {orders.length}</span>
</div>
)}
</div>
{/* Bulk actions */}
{selected.size > 0 && (
<div className="flex items-center gap-3 rounded-2xl bg-[var(--admin-accent-light)] border border-[var(--admin-accent)] px-4 py-3">
<span className="text-sm font-medium text-[var(--admin-accent-text)]">{selected.size} selected</span>
<AdminButton variant="primary" size="sm" onClick={handleBulkFulfill} disabled={bulkLoading} isLoading={bulkLoading}>
{bulkLoading ? "..." : "Bulk Fulfill"}
</AdminButton>
<AdminButton variant="secondary" size="sm" onClick={() => setShowBulkDep(true)}>
Bulk Record Deposit
</AdminButton>
<button type="button" onClick={() => setSelected(new Set())} className="ml-auto text-xs text-[var(--admin-text-muted)] hover:text-[var(--admin-text-secondary)]">Clear selection</button>
</div>
)}
{/* Manifest section */}
<div className="rounded-2xl bg-white border border-[var(--admin-border)] p-4 flex items-center gap-4 shadow-sm">
<div>
<p className="text-sm font-medium text-[var(--admin-text-secondary)]">Load Manifest</p>
<p className="text-xs text-[var(--admin-text-muted)]">Generate a printable manifest for pending/active orders.</p>
</div>
<AdminButton
variant="secondary"
size="sm"
onClick={async () => {
setManifestLoading(true);
const pending = filtered.filter(o => o.fulfillment_status !== "fulfilled");
if (pending.length === 0) { setManifestLoading(false); return; }
const html = await fetch("/api/wholesale/manifest", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ brandId, orders: pending }),
}).then(r => r.text());
openHtmlInPopup(html);
setManifestLoading(false);
}}
disabled={manifestLoading}
isLoading={manifestLoading}
>
{manifestLoading ? "Generating..." : "Generate Manifest"}
</AdminButton>
</div>
<div className="rounded-2xl bg-white border border-[var(--admin-border)] shadow-sm overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-[var(--admin-bg-subtle)] text-[var(--admin-text-muted)]">
<tr>
<th className="px-5 py-3 font-semibold text-left w-10">
<input id="fld-search" type="checkbox" checked={selected.size === filtered.length && filtered.length > 0} onChange={toggleAll} className="rounded" />
</th>
<th className="px-5 py-3 font-semibold text-left">Invoice</th>
<th className="px-5 py-3 font-semibold text-left">Customer</th>
<th className="px-5 py-3 font-semibold text-left">Pickup Date</th>
<th className="px-5 py-3 font-semibold text-right">Subtotal</th>
<th className="px-5 py-3 font-semibold text-right">Deposit</th>
<th className="px-5 py-3 font-semibold text-right">Balance</th>
<th className="px-5 py-3 font-semibold text-left">Status</th>
<th className="px-5 py-3 font-semibold text-left">Payment</th>
<th className="px-5 py-3 font-semibold text-left">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-[var(--admin-border-light)]">
{filtered.length === 0 ? (
<tr><td colSpan={10} className="py-8 text-center text-[var(--admin-text-muted)]">No orders match the current filters.</td></tr>
) : filtered.map(o => (
<tr key={o.id} className={`hover:bg-[var(--admin-bg-subtle)] ${selected.has(o.id) ? "bg-[var(--admin-accent-light)]" : ""}`}>
<td className="px-5 py-3">
<input type="checkbox" checked={selected.has(o.id)} onChange={() => toggleSelect(o.id)} className="rounded" />
</td>
<td className="px-5 py-3 font-mono text-xs text-[var(--admin-text-muted)]">{o.invoice_number ?? "—"}</td>
<td className="px-5 py-3 font-medium text-[var(--admin-text-primary)]">{o.company_name}</td>
<td className="px-5 py-3 text-[var(--admin-text-secondary)]">{o.anticipated_pickup_date ?? "—"}</td>
<td className="px-5 py-3 text-right font-semibold text-[var(--admin-text-primary)]">${Number(o.subtotal).toFixed(2)}</td>
<td className="px-5 py-3 text-right text-[var(--admin-text-secondary)]">${Number(o.deposit_paid).toFixed(2)} / ${Number(o.deposit_required).toFixed(2)}</td>
<td className="px-5 py-3 text-right">
<span className={Number(o.balance_due) > 0 ? "text-[var(--admin-warning)] font-medium" : "text-[var(--admin-accent)]"}>
${Number(o.balance_due).toFixed(2)}
</span>
</td>
<td className="px-5 py-3"><StatusBadge status={o.status} /></td>
<td className="px-5 py-3">
<span className={`text-xs font-medium ${o.payment_status === "paid" ? "text-[var(--admin-accent)]" : "text-[var(--admin-warning)]"}`}>
{o.payment_status}
</span>
</td>
<td className="px-5 py-4 actions-cell relative">
<div className="flex items-center gap-2">
{/* Primary: Mark Fulfilled — only when order is not yet fulfilled */}
{o.fulfillment_status !== "fulfilled" && (
<AdminButton
variant="primary"
size="sm"
onClick={() => !fulfilling && handleFulfill(o.id)}
disabled={fulfilling === o.id}
isLoading={fulfilling === o.id}
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}><path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /></svg>
{fulfilling === o.id ? "..." : "Fulfill"}
</AdminButton>
)}
{/* Primary: Record Deposit — when there's a balance due */}
{Number(o.balance_due) > 0 && o.fulfillment_status !== "fulfilled" && (
<AdminButton
variant="secondary"
size="sm"
onClick={() => setShowDepForm(o.id)}
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M12 6v12m-9-9h18" /></svg>
Deposit
</AdminButton>
)}
{/* Invoice download — always visible as icon button */}
<a aria-label="Download Invoice"
href={`/api/wholesale/invoice/${o.id}/pdf`}
target="_blank"
rel="noopener noreferrer" title="Download Invoice"
className="inline-flex items-center justify-center rounded-xl w-9 h-9 text-[var(--admin-text-muted)] hover:text-[var(--admin-accent)] hover:bg-[var(--admin-bg-subtle)] transition-colors">
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12h9m-9-6h6m-3 12a9 9 0 110 12H15M6 2h9l4 4v14a2 2 0 01-2 2H6a2 2 0 01-2-2V4a2 2 0 012-2z"/></svg>
</a>
{/* ⋮ Actions dropdown — opens upward to avoid table cutoff */}
<div className="relative">
<button type="button"
onClick={(e) => toggleActions(o.id, e)}
title="More actions"
className="inline-flex items-center justify-center rounded-xl w-9 h-9 text-[var(--admin-text-muted)] hover:text-[var(--admin-text-secondary)] hover:bg-[var(--admin-bg-subtle)] transition-colors"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><circle cx="10" cy="4" r="1.5"/><circle cx="10" cy="10" r="1.5"/><circle cx="10" cy="16" r="1.5"/></svg>
</button>
{openActions === o.id && (
<div
className="absolute bottom-full right-0 mb-1 z-30 w-56 rounded-xl bg-white shadow-xl ring-1 ring-[var(--admin-border)] py-1 text-sm"
onClick={e => e.stopPropagation()}
>
<button type="button"
onClick={() => { setOpenActions(null); setShowViewOrder(o); }}
className="w-full text-left px-4 py-3 text-[var(--admin-accent)] hover:bg-[var(--admin-accent-light)] flex items-center gap-3 font-medium"
>
<svg className="w-4 h-4 text-[var(--admin-accent)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
View / Edit Order
</button>
<button type="button"
onClick={() => {
setOpenActions(null);
fetch("/api/wholesale/manifest", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ brandId, orders: [o] }),
})
.then((r) => r.text())
.then((html) => openHtmlInPopup(html));
}}
className="w-full text-left px-4 py-3 text-[var(--admin-text-secondary)] hover:bg-[var(--admin-bg-subtle)] flex items-center gap-3"
>
<svg className="w-4 h-4 text-[var(--admin-text-muted)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12h9m-9-6h6m-3 12a9 9 0 01-18 0 9 9 0 0118 0z"/></svg>
Generate Manifest
</button>
<button type="button"
onClick={() => {
setOpenActions(null);
fetch("/api/wholesale/price-sheet", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ customerIds: [o.customer_id], brandId }),
}).then(r => r.json()).then(d => onMsg("success", `Price sheet sent to customer.`)).catch(() => onMsg("error", "Failed to send price sheet."));
}}
className="w-full text-left px-4 py-3 text-[var(--admin-text-secondary)] hover:bg-[var(--admin-bg-subtle)] flex items-center gap-3"
>
<svg className="w-4 h-4 text-[var(--admin-text-muted)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
Send Price Sheet
</button>
<div className="my-1 border-t border-[var(--admin-border)]" />
{o.status !== "cancelled" && o.fulfillment_status !== "fulfilled" && (
<button type="button"
onClick={() => { setOpenActions(null); handleCancelOrder(o.id); }}
className="w-full text-left px-4 py-3 text-[var(--admin-danger)] hover:bg-[var(--admin-danger-light)] flex items-center gap-3"
>
<svg className="w-4 h-4 text-[var(--admin-danger)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/></svg>
Cancel Order
</button>
)}
{o.fulfillment_status !== "fulfilled" && (
<button type="button"
onClick={() => { setOpenActions(null); handleDeleteOrder(o.id); }}
className="w-full text-left px-4 py-3 text-[var(--admin-danger)] hover:bg-[var(--admin-danger-light)] flex items-center gap-3"
>
<svg className="w-4 h-4 text-[var(--admin-danger)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
Delete Order
</button>
)}
</div>
)}
</div>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Deposit modal */}
{showDepForm && (
<div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50">
<div className="bg-white rounded-2xl p-6 w-96 shadow-xl border border-[var(--admin-border)]">
<h3 className="font-semibold text-[var(--admin-text-primary)] mb-4">Record Deposit</h3>
<div className="space-y-3">
<div>
<label htmlFor="fld-4-amount" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Amount ($)</label>
<input id="fld-4-amount" type="number" value={depAmount} onChange={e => setDepAmount(e.target.value)}
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" step="0.01" />
</div>
<div>
<label htmlFor="fld-5-method" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Method</label>
<select id="fld-5-method" value={depMethod} onChange={e => setDepMethod(e.target.value)}
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]">
<option value="cash">Cash</option>
<option value="check">Check</option>
<option value="wire">Wire</option>
<option value="card">Card</option>
</select>
</div>
</div>
<div className="mt-4 flex gap-3">
<AdminButton onClick={() => handleRecordDeposit(showDepForm)} variant="primary">
Record Deposit
</AdminButton>
<AdminButton onClick={() => setShowDepForm(null)} variant="secondary">
Cancel
</AdminButton>
</div>
</div>
</div>
)}
{/* Bulk Deposit modal */}
{showBulkDep && (
<div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50">
<div className="bg-white rounded-2xl p-6 w-96 shadow-xl border border-[var(--admin-border)]">
<h3 className="font-semibold text-[var(--admin-text-primary)] mb-4">Bulk Record Deposit ({selected.size} orders)</h3>
<div className="space-y-3">
<div>
<label htmlFor="fld-6-amount-per-order" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Amount per order ($)</label>
<input id="fld-6-amount-per-order" type="number" value={bulkDepAmount} onChange={e => setBulkDepAmount(e.target.value)}
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" step="0.01" placeholder="0.00" />
</div>
<div>
<label htmlFor="fld-7-method-2" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Method</label>
<select id="fld-7-method-2" value={bulkDepMethod} onChange={e => setBulkDepMethod(e.target.value)}
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]">
<option value="cash">Cash</option>
<option value="check">Check</option>
<option value="wire">Wire</option>
<option value="card">Card</option>
</select>
</div>
</div>
<div className="mt-4 flex gap-3">
<AdminButton onClick={handleBulkDeposit} variant="primary" disabled={!bulkDepAmount || bulkLoading} isLoading={bulkLoading}>
{bulkLoading ? "Recording..." : "Record on All"}
</AdminButton>
<AdminButton onClick={() => setShowBulkDep(false)} variant="secondary">
Cancel
</AdminButton>
</div>
</div>
</div>
)}
{/* Order Details modal */}
{showViewOrder && (
<OrderDetailsModal
order={showViewOrder}
onClose={() => setShowViewOrder(null)}
onFulfill={handleFulfill}
onRecordDeposit={(id) => { setShowViewOrder(null); setShowDepForm(id); }}
fulfilling={fulfilling}
/>
)}
</div>
);
}