feat(admin): apply design system to Orders list/detail/edit/table
- Add PageHeader + Operations eyebrow on list page - Detail page: PageHeader with order-id/date eyebrow, customer title, total/status subtitle, AdminBadge tone for pickup + payment processor - AdminOrdersPanel: KPIStat for stat cards, EmptyState with Create your first order CTA, AdminBadge tone for status pills, lucide-react icons, all hardcoded Tailwind colors replaced with var(--admin-*) tokens - OrderTableBody: AdminBadge tone for status + pickup pills, all hardcoded colors replaced with tokens - OrderEditForm: ha-field-label + ha-field-input / ha-field-textarea classes for form fields, ha-segment control for status buttons, semantic pickup toggle with primary/warning tokens, all hardcoded colors replaced Behavior preserved end-to-end. Type-check clean.
This commit is contained in:
@@ -198,8 +198,22 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{error && (
|
||||
<div className="rounded-xl bg-red-50 border border-red-200 p-4 text-sm text-red-600 flex items-start gap-3">
|
||||
<svg className="h-5 w-5 shrink-0 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<div
|
||||
className="rounded-xl border p-4 text-sm flex items-start gap-3"
|
||||
style={{
|
||||
borderColor: "var(--admin-danger-soft)",
|
||||
backgroundColor: "var(--admin-danger-soft)",
|
||||
color: "var(--admin-danger)",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className="h-5 w-5 shrink-0"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
style={{ color: "var(--admin-danger)" }}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{error}
|
||||
@@ -208,11 +222,17 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
|
||||
{/* Order items */}
|
||||
<div>
|
||||
<p className="mb-3 text-sm font-semibold text-stone-700">
|
||||
<p className="ha-field-label mb-3">
|
||||
Order Items ({visibleItems.length})
|
||||
</p>
|
||||
{visibleItems.length === 0 ? (
|
||||
<p className="rounded-xl border border-dashed border-stone-300 p-4 text-center text-sm text-stone-400">
|
||||
<p
|
||||
className="rounded-xl border border-dashed p-4 text-center text-sm"
|
||||
style={{
|
||||
borderColor: "var(--admin-border-strong)",
|
||||
color: "var(--admin-text-muted)",
|
||||
}}
|
||||
>
|
||||
No items
|
||||
</p>
|
||||
) : (
|
||||
@@ -220,13 +240,23 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
{visibleItems.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="rounded-xl border border-stone-200 bg-white p-4"
|
||||
className="rounded-xl border p-4"
|
||||
style={{
|
||||
borderColor: "var(--admin-border)",
|
||||
backgroundColor: "var(--admin-card-bg)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<p className="font-medium text-stone-900">{item.productName}</p>
|
||||
<p
|
||||
className="font-medium"
|
||||
style={{ color: "var(--admin-text-primary)" }}
|
||||
>
|
||||
{item.productName}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => removeItem(item.id)}
|
||||
className="shrink-0 rounded-lg px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50"
|
||||
className="shrink-0 rounded-lg px-2 py-1 text-xs font-medium"
|
||||
style={{ color: "var(--admin-danger)" }}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
@@ -234,7 +264,7 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
|
||||
<div className="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-stone-500 mb-1">Qty</label>
|
||||
<label className="ha-field-label mb-1">Qty</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -242,13 +272,18 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
onChange={(e) =>
|
||||
updateItem(item.id, "quantity", Number(e.target.value))
|
||||
}
|
||||
className="w-full rounded-xl border border-stone-200 bg-stone-50 px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)] focus:ring-2 focus:ring-[var(--admin-accent)]/20"
|
||||
className="ha-field-input ha-field-input-mono"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-stone-500 mb-1">Price</label>
|
||||
<label className="ha-field-label mb-1">Price</label>
|
||||
<div className="relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-stone-400 text-sm">$</span>
|
||||
<span
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 text-sm"
|
||||
style={{ color: "var(--admin-text-muted)" }}
|
||||
>
|
||||
$
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
@@ -257,13 +292,16 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
onChange={(e) =>
|
||||
updateItem(item.id, "price", Number(e.target.value))
|
||||
}
|
||||
className="w-full rounded-xl border border-stone-200 bg-stone-50 pl-8 pr-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)] focus:ring-2 focus:ring-[var(--admin-accent)]/20"
|
||||
className="ha-field-input ha-field-input-mono pl-8 pr-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-2 text-right text-sm font-semibold text-stone-900">
|
||||
<p
|
||||
className="mt-2 text-right text-sm font-semibold"
|
||||
style={{ color: "var(--admin-text-primary)" }}
|
||||
>
|
||||
{formatCurrency(Number(item.price) * item.quantity)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -280,48 +318,71 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
step="0.01"
|
||||
value={subtotal}
|
||||
readOnly
|
||||
className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-2.5 text-sm text-stone-500"
|
||||
className="ha-field-input ha-field-input-mono"
|
||||
style={{ color: "var(--admin-text-muted)" }}
|
||||
/>
|
||||
</AdminInput>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-stone-500 mb-1.5">Discount Amount</label>
|
||||
<label className="ha-field-label mb-1.5">Discount Amount</label>
|
||||
<div className="relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-stone-400 text-sm">$</span>
|
||||
<span
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 text-sm"
|
||||
style={{ color: "var(--admin-text-muted)" }}
|
||||
>
|
||||
$
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
value={discount_amount}
|
||||
onChange={(e) => setDiscount_amount(Number(e.target.value))}
|
||||
className={`w-full rounded-xl border px-8 pr-3 py-2.5 text-sm outline-none focus:ring-2 focus:ring-[var(--admin-accent)]/20 ${
|
||||
className="ha-field-input ha-field-input-mono pl-8 pr-3"
|
||||
style={
|
||||
fieldErrors.discount_amount
|
||||
? "border-red-400 bg-red-50"
|
||||
: "border-stone-200 bg-white focus:border-[var(--admin-accent)]"
|
||||
}`}
|
||||
? { borderColor: "var(--admin-danger)", backgroundColor: "var(--admin-danger-soft)" }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{fieldErrors.discount_amount && (
|
||||
<p className="mt-1 text-xs text-red-500">{fieldErrors.discount_amount}</p>
|
||||
<p className="mt-1 text-xs" style={{ color: "var(--admin-danger)" }}>
|
||||
{fieldErrors.discount_amount}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-stone-500 mb-1.5">Discount Reason</label>
|
||||
<label className="ha-field-label mb-1.5">Discount Reason</label>
|
||||
<input
|
||||
type="text"
|
||||
value={discount_reason}
|
||||
onChange={(e) => setDiscount_reason(e.target.value)}
|
||||
placeholder="Optional"
|
||||
className="w-full rounded-xl border border-stone-200 bg-white px-3 py-2.5 text-sm outline-none focus:border-[var(--admin-accent)] focus:ring-2 focus:ring-[var(--admin-accent)]/20"
|
||||
className="ha-field-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-stone-200 bg-stone-50 p-4">
|
||||
<div
|
||||
className="rounded-xl border p-4"
|
||||
style={{
|
||||
borderColor: "var(--admin-border)",
|
||||
backgroundColor: "var(--admin-bg-subtle)",
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm font-medium text-stone-600">Total</span>
|
||||
<span className="text-xl font-bold text-stone-900">
|
||||
<span
|
||||
className="text-sm font-medium"
|
||||
style={{ color: "var(--admin-text-secondary)" }}
|
||||
>
|
||||
Total
|
||||
</span>
|
||||
<span
|
||||
className="text-xl font-bold"
|
||||
style={{ color: "var(--admin-text-primary)" }}
|
||||
>
|
||||
{formatCurrency(subtotal + Number(order.tax_amount ?? 0) - discount_amount)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -331,8 +392,9 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
{/* Customer fields */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-stone-500 mb-1.5">
|
||||
Name <span className="text-red-500">*</span>
|
||||
<label className="ha-field-label mb-1.5">
|
||||
<span>Name</span>
|
||||
<span className="ha-field-label-required">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -345,14 +407,17 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
className={`w-full rounded-xl border px-3 py-2.5 text-sm outline-none focus:ring-2 focus:ring-[var(--admin-accent)]/20 ${
|
||||
className="ha-field-input"
|
||||
style={
|
||||
fieldErrors.customer_name
|
||||
? "border-red-400 bg-red-50"
|
||||
: "border-stone-200 bg-white focus:border-[var(--admin-accent)]"
|
||||
}`}
|
||||
? { borderColor: "var(--admin-danger)", backgroundColor: "var(--admin-danger-soft)" }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{fieldErrors.customer_name && (
|
||||
<p className="mt-1 text-xs text-red-500">{fieldErrors.customer_name}</p>
|
||||
<p className="mt-1 text-xs" style={{ color: "var(--admin-danger)" }}>
|
||||
{fieldErrors.customer_name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
@@ -376,17 +441,15 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
{/* Status & pickup */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-2 block text-xs font-semibold text-stone-500">Status</label>
|
||||
<div className="flex gap-2">
|
||||
<label className="ha-field-label mb-2">Status</label>
|
||||
<div className="ha-segment">
|
||||
{["pending", "confirmed", "cancelled"].map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
type="button"
|
||||
onClick={() => setStatus(s)}
|
||||
className={`flex-1 rounded-xl px-4 py-2.5 text-sm font-semibold capitalize transition-colors ${
|
||||
status === s
|
||||
? "bg-[var(--admin-accent)] text-white"
|
||||
: "bg-white text-stone-600 border border-stone-200 hover:bg-stone-50"
|
||||
className={`ha-segment-btn capitalize ${
|
||||
status === s ? "ha-segment-btn--active" : ""
|
||||
}`}
|
||||
>
|
||||
{s}
|
||||
@@ -396,15 +459,24 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-2 block text-xs font-semibold text-stone-500">Pickup</label>
|
||||
<label className="ha-field-label mb-2">Pickup</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickup_complete((v) => !v)}
|
||||
className={`w-full rounded-xl px-4 py-2.5 text-sm font-semibold transition-colors ${
|
||||
className="ha-segment-btn w-full justify-center"
|
||||
style={
|
||||
pickup_complete
|
||||
? "bg-green-100 text-green-700 border border-green-200"
|
||||
: "bg-amber-100 text-amber-700 border border-amber-200"
|
||||
}`}
|
||||
? {
|
||||
backgroundColor: "var(--admin-primary-soft)",
|
||||
color: "var(--admin-primary)",
|
||||
border: "1px solid var(--admin-primary)",
|
||||
}
|
||||
: {
|
||||
backgroundColor: "var(--admin-warning-soft)",
|
||||
color: "var(--admin-warning)",
|
||||
border: "1px solid var(--admin-warning)",
|
||||
}
|
||||
}
|
||||
>
|
||||
{pickup_complete ? "✓ Picked Up" : "○ Not Picked Up"}
|
||||
</button>
|
||||
@@ -428,9 +500,10 @@ export default function OrderEditForm({ order, brandId }: OrderEditFormProps) {
|
||||
variant="primary"
|
||||
fullWidth
|
||||
size="lg"
|
||||
className="ha-btn-primary"
|
||||
>
|
||||
{saving ? "Saving..." : "Save Changes"}
|
||||
</AdminButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user