update on the user page regardinga the dispatch and order page and the deliveries page
This commit is contained in:
323
src/components/DeliveriesView.tsx
Normal file
323
src/components/DeliveriesView.tsx
Normal file
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* @license
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Deliveries page — replicated from the operations console (nearle_console/
|
||||
* deliveries), rebuilt against the shared console UI kit (`./consoleUi`) so it
|
||||
* matches the source design (gradient header, KPI cards, batch + status pill
|
||||
* tabs, STATUS_META colours, metric-pill table). The board loads the day's
|
||||
* deliveries once and filters client-side by delivery wave, lifecycle status, and
|
||||
* keyword. Rider write-actions (reassign/cancel/notify) need the dispatch + FCM
|
||||
* backends this tenant doesn't expose, so they surface an "awaiting backend" note.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Truck, Clock, CheckCircle2, XCircle, Calendar, Sun, Sunset, Moon, Layers, UserCheck, MapPin, Phone, Package, Loader2, X, Bike,
|
||||
} from 'lucide-react';
|
||||
import { useFiestaDeliverySummary, useFiestaDeliveries, useFiestaRiders, useFiestaOrderDetails } from '../services/fiestaQueries';
|
||||
import { FIESTA_TENANT_ID, num as fnum, str as fstr, ymd, type Row } from '../services/fiestaApi';
|
||||
import { shortTime } from '../services/fiestaMappers';
|
||||
import AwaitingApi from './AwaitingApi';
|
||||
import {
|
||||
GradientHeader, LiveStatus, KpiStrip, Pill, StatusChip, MetricPill, SearchPill, FilterBar, TH_STYLE,
|
||||
DELIVERY_STATUS, statusColor, BRAND, BRAND_LIGHT, TEXT, TEXT_2, TEXT_3, BORDER, DIVIDER, SURFACE_ALT, tint, soft, edge,
|
||||
} from './consoleUi';
|
||||
|
||||
interface DeliveriesViewProps { searchQuery?: string; locationid?: number; }
|
||||
|
||||
type DeliveryStatus = 'pending' | 'accepted' | 'arrived' | 'picked' | 'active' | 'skipped' | 'delivered' | 'cancelled';
|
||||
const STATUS_TABS: Array<{ key: DeliveryStatus; label: string }> = [
|
||||
{ key: 'pending', label: 'Pending' }, { key: 'accepted', label: 'Accepted' }, { key: 'arrived', label: 'Arrived' },
|
||||
{ key: 'picked', label: 'Picked' }, { key: 'active', label: 'Active' }, { key: 'skipped', label: 'Skipped' },
|
||||
{ key: 'delivered', label: 'Delivered' }, { key: 'cancelled', label: 'Cancelled' },
|
||||
];
|
||||
|
||||
// Batch waves — canonical half-open hour ranges (match Dispatch).
|
||||
type BatchId = 'all' | 'morning' | 'afternoon' | 'evening';
|
||||
const BATCHES: Array<{ id: BatchId; label: string; range: string; color: string; icon: typeof Sun }> = [
|
||||
{ id: 'all', label: 'All', range: 'Full day', color: '#7c3aed', icon: Layers },
|
||||
{ id: 'morning', label: 'Morning', range: '12 AM – 8 AM', color: '#0ea5e9', icon: Sun },
|
||||
{ id: 'afternoon', label: 'Afternoon', range: '9 AM – 12:30 PM', color: '#f59e0b', icon: Sunset },
|
||||
{ id: 'evening', label: 'Evening', range: '4 PM – 7 PM', color: '#6366f1', icon: Moon },
|
||||
];
|
||||
function rowHourFrac(r: Row): number | null {
|
||||
const m = (fstr(r.assigntime) || fstr(r.deliverytime) || fstr(r.deliverydate)).match(/[ T](\d{1,2}):(\d{2})/);
|
||||
return m ? Number(m[1]) + Number(m[2]) / 60 : null;
|
||||
}
|
||||
function inBatch(r: Row, b: BatchId): boolean {
|
||||
if (b === 'all') return true;
|
||||
const h = rowHourFrac(r);
|
||||
if (h == null) return false;
|
||||
if (b === 'morning') return h >= 0 && h < 8;
|
||||
if (b === 'afternoon') return h >= 9 && h < 12.5;
|
||||
return h >= 16 && h < 19;
|
||||
}
|
||||
function initialBatch(): BatchId {
|
||||
const h = new Date().getHours();
|
||||
if (h >= 0 && h < 8) return 'morning';
|
||||
if (h >= 9 && h < 12.5) return 'afternoon';
|
||||
if (h >= 16 && h < 19) return 'evening';
|
||||
return 'all';
|
||||
}
|
||||
|
||||
export default function DeliveriesView({ searchQuery = '', locationid }: DeliveriesViewProps) {
|
||||
const today = new Date();
|
||||
const [fromdate, setFromdate] = useState<string>(ymd(today));
|
||||
const [todate, setTodate] = useState<string>(ymd(today));
|
||||
const dayOffset = (n: number) => { const d = new Date(); d.setDate(d.getDate() - n); return ymd(d); };
|
||||
const presets = [
|
||||
{ key: 'today', label: 'Today', from: ymd(today), to: ymd(today) },
|
||||
{ key: 'yesterday', label: 'Yesterday', from: dayOffset(1), to: dayOffset(1) },
|
||||
{ key: '7d', label: 'Last 7 Days', from: dayOffset(6), to: ymd(today) },
|
||||
];
|
||||
const activePreset = presets.find((p) => p.from === fromdate && p.to === todate)?.key ?? 'custom';
|
||||
|
||||
const [batch, setBatch] = useState<BatchId>(initialBatch());
|
||||
const [status, setStatus] = useState<DeliveryStatus>('pending');
|
||||
const [localSearch, setLocalSearch] = useState('');
|
||||
const [detailRow, setDetailRow] = useState<Row | null>(null);
|
||||
|
||||
const summaryQ = useFiestaDeliverySummary({ tenantid: FIESTA_TENANT_ID, fromdate, todate });
|
||||
const deliveriesQ = useFiestaDeliveries({ tenantid: FIESTA_TENANT_ID, fromdate, todate });
|
||||
const ridersQ = useFiestaRiders({ tenantid: FIESTA_TENANT_ID });
|
||||
|
||||
const allRows = deliveriesQ.data ?? [];
|
||||
const summary = summaryQ.data;
|
||||
|
||||
const batchRows = useMemo(() => allRows.filter((r) => (!locationid || fnum(r.locationid) === locationid) && inBatch(r, batch)), [allRows, batch, locationid]);
|
||||
const statusCounts = useMemo(() => {
|
||||
const acc: Record<string, number> = {};
|
||||
for (const r of batchRows) { const s = fstr(r.orderstatus).toLowerCase(); acc[s] = (acc[s] ?? 0) + 1; }
|
||||
return acc;
|
||||
}, [batchRows]);
|
||||
const rows = useMemo(() => {
|
||||
const term = (localSearch || searchQuery).toLowerCase();
|
||||
return batchRows.filter((r) => {
|
||||
if (fstr(r.orderstatus).toLowerCase() !== status) return false;
|
||||
if (!term) return true;
|
||||
return [r.orderid, r.deliverycustomer, r.deliveryaddress, r.deliverysuburb, r.pickupcustomer, r.ridername, r.username].some((f) => fstr(f).toLowerCase().includes(term));
|
||||
});
|
||||
}, [batchRows, status, localSearch, searchQuery]);
|
||||
|
||||
const activeFleet = (ridersQ.data ?? []).filter((r) => fstr(r.starttime)).length;
|
||||
const total = summary?.total ?? 0;
|
||||
const pct = (n: number) => (total > 0 ? `${Math.round((n / total) * 100)}% of total` : 'In range');
|
||||
const kpis = [
|
||||
{ label: 'Total Deliveries', value: total.toLocaleString('en-IN'), color: '#6366f1', icon: <Truck size={20} />, badge: undefined },
|
||||
{ label: 'Pending', value: (summary?.pending ?? 0).toLocaleString('en-IN'), color: '#f59e0b', icon: <Clock size={20} />, badge: pct(summary?.pending ?? 0) },
|
||||
{ label: 'Delivered', value: (summary?.delivered ?? 0).toLocaleString('en-IN'), color: '#10b981', icon: <CheckCircle2 size={20} />, badge: pct(summary?.delivered ?? 0) },
|
||||
{ label: 'Cancelled', value: (summary?.cancelled ?? 0).toLocaleString('en-IN'), color: '#ef4444', icon: <XCircle size={20} />, badge: pct(summary?.cancelled ?? 0) },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="animate-in fade-in duration-300">
|
||||
<GradientHeader
|
||||
title="Deliveries"
|
||||
subtitle="Dispatch board for in-transit orders — tracked across the rider lifecycle and grouped into delivery waves."
|
||||
status={
|
||||
deliveriesQ.isLoading ? <LiveStatus state="loading" label="Loading live deliveries…" />
|
||||
: deliveriesQ.isError ? <LiveStatus state="error" label="Live data unavailable" />
|
||||
: <LiveStatus state="live" label={`Live · ${batchRows.length} in this wave · ${activeFleet} riders on duty`} />
|
||||
}
|
||||
right={
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full font-extrabold" style={{ padding: '6px 12px', fontSize: 12, background: tint(BRAND), border: `1.5px solid ${edge(BRAND)}`, color: BRAND }}>
|
||||
<MapPin size={13} /> Coimbatore
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="mb-4"><KpiStrip items={kpis} loading={summaryQ.isLoading} /></div>
|
||||
|
||||
{/* Date + waves */}
|
||||
<FilterBar className="mb-4">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="inline-flex items-center gap-1.5 text-[10px] font-extrabold uppercase tracking-widest pr-1" style={{ color: TEXT_2 }}><Calendar size={13} style={{ color: BRAND }} /> View</span>
|
||||
{presets.map((p) => (
|
||||
<React.Fragment key={p.key}><Pill active={activePreset === p.key} color={BRAND} onClick={() => { setFromdate(p.from); setTodate(p.to); }}>{p.label}</Pill></React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<input type="date" value={fromdate} max={todate} onChange={(e) => setFromdate(e.target.value)} className="rounded-full outline-none font-semibold" style={{ padding: '6px 12px', border: `1.5px solid ${edge('#f59e0b')}`, background: tint('#f59e0b'), color: '#b45309' }} />
|
||||
<span style={{ color: TEXT_3 }}>→</span>
|
||||
<input type="date" value={todate} min={fromdate} max={ymd(today)} onChange={(e) => setTodate(e.target.value)} className="rounded-full outline-none font-semibold" style={{ padding: '6px 12px', border: `1.5px solid ${edge('#f59e0b')}`, background: tint('#f59e0b'), color: '#b45309' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap pt-3 mt-3 border-t" style={{ borderColor: DIVIDER }}>
|
||||
<span className="text-[10px] font-extrabold uppercase tracking-widest pr-1" style={{ color: TEXT_2 }}>Wave</span>
|
||||
{BATCHES.map((b) => {
|
||||
const Icon = b.icon;
|
||||
const count = allRows.filter((r) => (!locationid || fnum(r.locationid) === locationid) && inBatch(r, b.id)).length;
|
||||
return (
|
||||
<React.Fragment key={b.id}>
|
||||
<Pill active={batch === b.id} color={b.color} onClick={() => setBatch(b.id)} title={b.range} count={count}><Icon size={13} /> {b.label}</Pill>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</FilterBar>
|
||||
|
||||
{/* Status tabs + search */}
|
||||
<FilterBar className="mb-4">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center gap-3">
|
||||
<div className="flex items-center gap-2 overflow-x-auto py-0.5 flex-1 min-w-0">
|
||||
{STATUS_TABS.map((t) => {
|
||||
const color = statusColor(DELIVERY_STATUS, t.key);
|
||||
return (
|
||||
<React.Fragment key={t.key}>
|
||||
<Pill active={status === t.key} color={color} onClick={() => setStatus(t.key)} count={statusCounts[t.key] ?? 0}>{t.label}</Pill>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="w-full lg:w-72 lg:shrink-0"><SearchPill value={localSearch} onChange={setLocalSearch} placeholder="Search by order, rider…" color="#6366f1" /></div>
|
||||
</div>
|
||||
</FilterBar>
|
||||
|
||||
{/* Table */}
|
||||
<div className="bg-white border rounded-2xl overflow-hidden" style={{ borderColor: BORDER }}>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full" style={{ minWidth: 1040 }}>
|
||||
<thead>
|
||||
<tr>{['#', 'Status', 'Order', 'Drop', 'Rider', 'ETA', 'KMs', 'Amount', ''].map((h, i) => (<th key={i} className="px-3 py-2.5 text-left" style={TH_STYLE}>{h}</th>))}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{deliveriesQ.isLoading ? (
|
||||
<tr><td colSpan={9} className="px-3 py-12 text-center" style={{ color: TEXT_3 }}><span className="inline-flex items-center gap-2 text-xs font-semibold"><Loader2 size={15} className="animate-spin" style={{ color: BRAND }} /> Loading deliveries…</span></td></tr>
|
||||
) : rows.length === 0 ? (
|
||||
<tr><td colSpan={9} className="px-3 py-12 text-center text-xs" style={{ color: TEXT_3 }}>No {status} deliveries in this wave. Try another status, wave, or date.</td></tr>
|
||||
) : (
|
||||
rows.map((r, i) => {
|
||||
const st = fstr(r.orderstatus).toLowerCase();
|
||||
const rider = fstr(r.ridername) || fstr(r.username);
|
||||
const kms = fnum(r.kms); const actualKms = fnum(r.cumulativekms);
|
||||
const charge = fnum(r.deliverycharges); const amt = fnum(r.deliveryamt);
|
||||
return (
|
||||
<tr key={fstr(r.deliveryid) || fstr(r.orderid) || i} className="transition-colors align-top" style={{ borderBottom: `1px solid ${DIVIDER}` }}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.background = SURFACE_ALT)} onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}>
|
||||
<td className="px-3 py-2.5 font-mono" style={{ color: TEXT_3 }}>{i + 1}</td>
|
||||
<td className="px-3 py-2.5"><StatusChip label={st || '—'} color={statusColor(DELIVERY_STATUS, st)} /></td>
|
||||
<td className="px-3 py-2.5">
|
||||
<p className="font-extrabold font-mono text-[13px]" style={{ color: TEXT }}>{fstr(r.orderid) || `DLV-${fstr(r.deliveryid)}`}</p>
|
||||
<p className="text-[10px]" style={{ color: TEXT_2 }}>{shortTime(r.assigntime || r.deliverydate)}</p>
|
||||
</td>
|
||||
<td className="px-3 py-2.5">
|
||||
<p className="font-bold text-[12px] truncate max-w-[160px]" style={{ color: TEXT }}>{fstr(r.deliverycustomer) || '—'}</p>
|
||||
<p className="text-[10px] truncate max-w-[160px]" style={{ color: TEXT_2 }}>{fstr(r.deliverysuburb) || fstr(r.deliveryaddress)}</p>
|
||||
</td>
|
||||
<td className="px-3 py-2.5">
|
||||
{rider ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<span className="rounded-full flex items-center justify-center shrink-0" style={{ width: 26, height: 26, background: soft('#8b5cf6'), color: '#8b5cf6' }}><Bike size={13} /></span>
|
||||
<span className="font-bold text-[12px] truncate max-w-[100px]" style={{ color: TEXT }}>{rider}</span>
|
||||
</span>
|
||||
) : <span className="text-[11px] italic" style={{ color: TEXT_3 }}>Unassigned</span>}
|
||||
</td>
|
||||
<td className="px-3 py-2.5"><MetricPill color="#06b6d4">{shortTime(r.expecteddeliverytime) || '—'}</MetricPill></td>
|
||||
<td className="px-3 py-2.5">
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<MetricPill color="#ef4444" minWidth={64}>{kms ? kms.toFixed(1) : '—'}</MetricPill>
|
||||
{actualKms > 0 && <MetricPill color="#10b981" minWidth={64}>{actualKms.toFixed(1)}</MetricPill>}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2.5">
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
{charge > 0 && <MetricPill color="#ef4444" minWidth={72}>₹{charge.toLocaleString('en-IN')}</MetricPill>}
|
||||
{amt > 0 && <MetricPill color="#10b981" minWidth={72}>₹{amt.toLocaleString('en-IN')}</MetricPill>}
|
||||
{charge === 0 && amt === 0 && <span style={{ color: TEXT_3 }}>—</span>}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2.5 text-right">
|
||||
<button onClick={() => setDetailRow(r)} className="rounded-full font-extrabold cursor-pointer" style={{ padding: '4px 12px', fontSize: 11, color: BRAND, background: tint(BRAND), border: `1px solid ${edge(BRAND)}` }}>View</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="px-4 py-2.5 border-t text-[10px] font-bold uppercase tracking-wider" style={{ borderColor: BORDER, background: SURFACE_ALT, color: TEXT_2 }}>
|
||||
{rows.length} {status} · {BATCHES.find((b) => b.id === batch)?.label} wave
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{detailRow && <DeliveryDetailModal row={detailRow} onClose={() => setDetailRow(null)} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Delivery details modal ──────────────────────────────────────────────────────
|
||||
function DeliveryDetailModal({ row, onClose }: { row: Row; onClose: () => void }) {
|
||||
const orderheaderid = row.orderheaderid ?? row.orderid;
|
||||
const detailsQ = useFiestaOrderDetails(orderheaderid as number | string);
|
||||
const lines = (detailsQ.data ?? []).map((d) => {
|
||||
const quantity = fnum(d.quantity) || fnum(d.qty) || fnum(d.orderqty);
|
||||
const price = fnum(d.price) || fnum(d.unitprice) || fnum(d.retailprice);
|
||||
return { name: fstr(d.productname) || fstr(d.itemname) || 'Item', quantity, price, lineTotal: fnum(d.amount) || fnum(d.productsumprice) || price * quantity };
|
||||
});
|
||||
const st = fstr(row.orderstatus).toLowerCase();
|
||||
const rider = fstr(row.ridername) || fstr(row.username);
|
||||
const steps = [
|
||||
{ label: 'Assigned', field: 'assigntime' }, { label: 'Accepted', field: 'acceptedtime' }, { label: 'Arrived', field: 'arrivaltime' },
|
||||
{ label: 'Picked', field: 'pickuptime' }, { label: 'Delivered', field: 'deliverytime' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[200] flex items-center justify-center p-4" style={{ background: 'rgba(15,23,42,0.4)', backdropFilter: 'blur(4px)' }} onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
|
||||
<div className="bg-white w-full max-w-lg max-h-[90vh] flex flex-col overflow-hidden rounded-2xl animate-in zoom-in-95 duration-200" style={{ border: `1px solid ${BORDER}`, boxShadow: '0 18px 50px rgba(15,23,42,0.18)' }}>
|
||||
<div style={{ height: 4, background: `linear-gradient(90deg, #6366f1 0%, ${soft('#6366f1')} 100%)` }} />
|
||||
<div className="p-4 border-b flex justify-between items-center shrink-0" style={{ borderColor: BORDER, background: SURFACE_ALT }}>
|
||||
<h4 className="font-extrabold flex items-center gap-2" style={{ color: TEXT }}><Truck size={16} style={{ color: '#6366f1' }} /> {fstr(row.orderid) || `Delivery ${fstr(row.deliveryid)}`}</h4>
|
||||
<button onClick={onClose} className="p-1 rounded-full cursor-pointer" style={{ color: TEXT_3 }}><X size={16} /></button>
|
||||
</div>
|
||||
<div className="p-4 space-y-4 overflow-y-auto flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<StatusChip label={st || '—'} color={statusColor(DELIVERY_STATUS, st)} />
|
||||
<span className="inline-flex items-center gap-1.5 text-[11px] font-medium" style={{ color: TEXT_2 }}><UserCheck size={12} /> {rider || 'Unassigned'}</span>
|
||||
</div>
|
||||
<div className="p-3 rounded-xl space-y-1.5" style={{ background: SURFACE_ALT, border: `1px solid ${BORDER}` }}>
|
||||
<div className="font-bold" style={{ color: TEXT }}>{fstr(row.deliverycustomer) || 'Customer'}</div>
|
||||
{fstr(row.deliverycontactno) && <div className="flex items-center gap-2 font-mono text-xs" style={{ color: TEXT_2 }}><Phone size={12} /> {fstr(row.deliverycontactno)}</div>}
|
||||
<div className="flex items-start gap-2 text-xs" style={{ color: TEXT_2 }}><MapPin size={12} className="mt-0.5 shrink-0" /> <span className="leading-relaxed">{fstr(row.deliveryaddress) || fstr(row.deliverysuburb) || 'Address unavailable'}</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[10px] font-extrabold uppercase tracking-wide block mb-2" style={{ color: TEXT_2 }}>Delivery Timeline</span>
|
||||
<div className="space-y-2.5 pl-1">
|
||||
{steps.map((s) => {
|
||||
const ts = fstr(row[s.field]); const done = Boolean(ts);
|
||||
return (
|
||||
<div key={s.field} className="flex items-center gap-2.5">
|
||||
<CheckCircle2 size={13} style={{ color: done ? '#10b981' : '#cbd5e1' }} />
|
||||
<span className="font-semibold text-xs" style={{ color: done ? TEXT : TEXT_3 }}>{s.label}</span>
|
||||
<span className="ml-auto text-[10px] font-mono" style={{ color: TEXT_3 }}>{done ? shortTime(ts) : '—'}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[10px] font-extrabold uppercase tracking-wide mb-2 flex items-center gap-1.5" style={{ color: TEXT_2 }}><Package size={12} /> Items</span>
|
||||
<div className="rounded-xl p-3" style={{ background: 'rgba(248,250,252,0.6)', border: `1px solid ${BORDER}` }}>
|
||||
{detailsQ.isLoading && <div className="py-2 flex items-center gap-1.5 text-[11px] font-medium" style={{ color: TEXT_3 }}><Loader2 size={12} className="animate-spin" /> Loading items…</div>}
|
||||
{!detailsQ.isLoading && lines.length === 0 && <div className="py-2 text-[11px] font-medium" style={{ color: TEXT_3 }}>No line items returned.</div>}
|
||||
{lines.map((item, idx) => (
|
||||
<div key={idx} className="py-2 flex justify-between items-center" style={{ borderTop: idx ? `1px solid ${DIVIDER}` : undefined }}>
|
||||
<div><p className="font-bold text-xs" style={{ color: TEXT }}>{item.name}</p><p className="text-[10px]" style={{ color: TEXT_2 }}>Qty: {item.quantity} × ₹{item.price}</p></div>
|
||||
<span className="font-extrabold font-mono text-xs" style={{ color: TEXT }}>₹{item.lineTotal.toLocaleString('en-IN')}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<AwaitingApi label="Reassign · Cancel · Notify rider" api="dispatch backend" compact />
|
||||
</div>
|
||||
<div className="p-3 border-t flex justify-end shrink-0" style={{ borderColor: BORDER, background: SURFACE_ALT }}>
|
||||
<button onClick={onClose} className="rounded-full font-bold cursor-pointer text-white" style={{ padding: '8px 16px', background: `linear-gradient(135deg, ${BRAND}, ${BRAND_LIGHT})` }}>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user