Files
daily_merchant_web/src/components/OrdersDeliveriesView.tsx

750 lines
38 KiB
TypeScript

/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import React, { useState, useEffect } from 'react';
import {
ShoppingBag,
Truck,
CheckCircle2,
Clock,
UserCheck,
MapPin,
TrendingUp,
Plus,
ChevronRight,
Package,
ArrowRight,
AlertCircle,
Clock4,
Search,
Check,
Calendar,
X
} from 'lucide-react';
import { CustomerOrder } from '../types';
import {
useFiestaDeliveries,
useFiestaDeliverySummary,
useFiestaRiders,
} from '../services/fiestaQueries';
import { FIESTA_TENANT_ID, num as fnum, str as fstr, ymd } from '../services/fiestaApi';
import { deliveryRowToOrder } from '../services/fiestaMappers';
interface OrdersDeliveriesViewProps {
searchQuery?: string;
isCoimbatoreView?: boolean;
locationid?: number;
}
interface DeliveryExecutive {
id: string;
name: string;
phone: string;
status: 'Active Duty' | 'Idle' | 'Offline';
rating: number;
completedToday: number;
currentZone: string;
avatar: string;
}
const RIDER_AVATARS = [
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=150&q=80',
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=150&q=80',
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=150&q=80',
];
function riderRowToExecutive(row: Record<string, unknown>, idx: number): DeliveryExecutive {
return {
id: `DE-${fstr(row.userid) || idx}`,
name: fstr(row.fullname) || `${fstr(row.firstname)} ${fstr(row.lastname)}`.trim() || 'Rider',
phone: fstr(row.contactno) || '—',
status: fstr(row.starttime) ? 'Active Duty' : 'Idle',
rating: 4.7,
completedToday: fnum(row.completed) || fnum(row.deliverycount),
currentZone: fstr(row.city) || fstr(row.vehiclename) || fstr(row.vehicleno) || 'Coimbatore',
avatar: RIDER_AVATARS[idx % RIDER_AVATARS.length],
};
}
export default function OrdersDeliveriesView({ searchQuery = '', isCoimbatoreView = false, locationid }: OrdersDeliveriesViewProps) {
// ── Live deliveries / fleet (Fiesta) ──────────────────────────────────────
// Order feed + dispatch controls run off the live deliveries board; the KPI
// strip uses the delivery summary; the fleet panel uses the active riders.
// A date-range filter lets the user view orders/deliveries day-wise.
const today = new Date();
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
const [fromdate, setFromdate] = useState<string>(ymd(monthStart));
const [todate, setTodate] = useState<string>(ymd(today));
// Quick-range presets (computed off the current day; no Date.now in render path).
const dayOffset = (n: number) => {
const d = new Date();
d.setDate(d.getDate() - n);
return ymd(d);
};
const presets: Array<{ key: string; label: string; from: string; to: string }> = [
{ 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) },
{ key: '30d', label: 'Last 30 Days', from: dayOffset(29), to: ymd(today) },
{ key: 'month', label: 'This Month', from: ymd(monthStart), to: ymd(today) },
];
const activePreset = presets.find((p) => p.from === fromdate && p.to === todate)?.key ?? 'custom';
const deliveriesQ = useFiestaDeliveries({ tenantid: FIESTA_TENANT_ID, fromdate, todate });
const summaryQ = useFiestaDeliverySummary({ tenantid: FIESTA_TENANT_ID, fromdate, todate });
const ridersQ = useFiestaRiders({ tenantid: FIESTA_TENANT_ID });
const [orders, setOrders] = useState<CustomerOrder[]>([]);
const [executives, setExecutives] = useState<DeliveryExecutive[]>([]);
const [selectedOrder, setSelectedOrder] = useState<CustomerOrder | null>(null);
const [filterStatus, setFilterStatus] = useState<string>('ALL');
const [localSearch, setLocalSearch] = useState('');
// Seed local state once live data arrives so existing dispatch/create handlers
// continue to mutate in-session.
useEffect(() => {
if (deliveriesQ.data) {
const mapped = deliveriesQ.data.map(deliveryRowToOrder);
setOrders(mapped);
// Keep the current selection only if it's still in the new range; otherwise
// fall back to the first order so the detail panel stays in sync.
setSelectedOrder((prev) =>
(prev && mapped.some((o) => o.id === prev.id)) ? prev : mapped[0] ?? null,
);
}
}, [deliveriesQ.data]);
useEffect(() => {
if (ridersQ.data) setExecutives(ridersQ.data.map(riderRowToExecutive));
}, [ridersQ.data]);
const summary = summaryQ.data;
// Local filtered list of orders
const storeOrders = locationid ? orders.filter(o => o.locationid === locationid) : orders;
const filteredOrdersList = storeOrders.filter(o => {
const term = (localSearch || searchQuery).toLowerCase();
const matchesSearch = o.id.toLowerCase().includes(term) ||
o.customerName.toLowerCase().includes(term) ||
o.address.toLowerCase().includes(term);
const matchesFilter = filterStatus === 'ALL' || o.status === filterStatus;
return matchesSearch && matchesFilter;
});
// Calculate dynamic stats for metrics cards based on filtered storeOrders
const totalDeliveriesCount = storeOrders.length;
const pendingFulfillmentCount = storeOrders.filter(o => o.status === 'PROCESSING' || o.status === 'CONFIRMED').length;
const activeDispatchCount = storeOrders.filter(o => o.status === 'OUT_FOR_DELIVERY').length;
const completedDeliveriesCount = storeOrders.filter(o => o.status === 'DELIVERED').length;
const MOCK_NAMES = ['Aravind Swamy', 'Karthik Raja', 'Priya Mani', 'Meera Jasmine', 'Sanjay Dutt', 'Divya Spandana', 'Vijay Sethupathi', 'Nayan Thara'];
const MOCK_STREETS = ['Avarampalayam Rd', 'DB Road', 'Cross Cut Road', 'Avinashi Road', 'Trichy Road', 'NSR Road', 'Sathy Road', 'Marudhamalai Road'];
const MOCK_ITEMS = [
{ name: 'Tata Salt Premium Iodized 1kg', price: 28 },
{ name: 'Gold Winner Sunflower Oil 1L', price: 145 },
{ name: 'Britannia Marie Gold Biscuit 250g', price: 35 },
{ name: 'MTR Sambar Powder 200g', price: 85 },
{ name: 'Aavin Salted Butter 500g', price: 260 },
{ name: 'Ponni Boiled Rice 5kg', price: 380 },
{ name: 'Fresh Ooty Carrots 500g', price: 45 },
{ name: 'Nescafe Classic Coffee 100g', price: 185 },
];
const handleCreateMockOrder = () => {
const randomName = MOCK_NAMES[Math.floor(Math.random() * MOCK_NAMES.length)];
const randomStreet = MOCK_STREETS[Math.floor(Math.random() * MOCK_STREETS.length)];
const numItems = Math.floor(Math.random() * 3) + 1; // 1 to 3 items
const selectedItems = [];
let amount = 0;
for (let k = 0; k < numItems; k++) {
const it = MOCK_ITEMS[Math.floor(Math.random() * MOCK_ITEMS.length)];
const qty = Math.floor(Math.random() * 2) + 1;
selectedItems.push({ name: it.name, quantity: qty, price: it.price });
amount += it.price * qty;
}
const newId = `ORD-${Math.floor(100000 + Math.random() * 900000)}`;
const newOrder: CustomerOrder = {
id: newId,
customerName: randomName,
phone: `9${Math.floor(100000000 + Math.random() * 900000000)}`,
address: `${Math.floor(10 + Math.random() * 190)}, ${randomStreet}, Coimbatore`,
items: selectedItems,
amount,
time: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
status: 'PROCESSING',
assignedRider: 'Pending Assignment',
hub: locationid ? `Outlet Node #${locationid}` : 'Coimbatore Hub',
locationid: locationid ?? 1097,
};
setOrders(prev => [newOrder, ...prev]);
setSelectedOrder(newOrder);
};
const handleUpdateStatus = (newStatus: CustomerOrder['status']) => {
if (!selectedOrder) return;
setOrders(prev => prev.map(o => {
if (o.id === selectedOrder.id) {
const updated = { ...o, status: newStatus };
setSelectedOrder(updated);
return updated;
}
return o;
}));
};
const handleAssignRider = (riderName: string) => {
if (!selectedOrder) return;
setOrders(prev => prev.map(o => {
if (o.id === selectedOrder.id) {
const updated = {
...o,
assignedRider: riderName,
status: o.status === 'PROCESSING' ? 'CONFIRMED' : o.status
};
setSelectedOrder(updated);
return updated;
}
return o;
}));
};
return (
<div className="space-y-lg animate-in fade-in duration-500">
{/* View Header with Statistics Overview */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-md border-b border-[#e2e8f0] pb-xl">
<div>
<h1 className="font-sans font-bold text-2xl tracking-tight text-[#0f172a]">
Orders & Delivery Operations
</h1>
<p className="text-zinc-500 font-sans text-xs mt-1">
Real-time tracking of app orders, dispatch queues, and active delivery partners across Coimbatore regional sub-hubs.
</p>
<div className="mt-1.5">
{deliveriesQ.isLoading ? (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-zinc-400 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-zinc-300 animate-pulse" /> Loading live deliveries
</span>
) : deliveriesQ.isError ? (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-rose-600 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-rose-500" /> Live data unavailable
</span>
) : (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-emerald-600 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" /> Live · {orders.length} deliveries · {executives.length} riders
</span>
)}
</div>
</div>
</div>
{/* Top Level Delivery Performance Indicators */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-gutter font-sans">
<div className="bg-white border border-[#e2e8f0] p-md rounded-xl flex items-center gap-md shadow-sm">
<div className="p-2 bg-purple-50 text-[#581c87] rounded-lg">
<ShoppingBag size={20} />
</div>
<div>
<p className="text-[10px] text-zinc-400 uppercase tracking-wider font-bold">Deliveries in Range</p>
<p className="font-sans font-bold text-lg text-zinc-800">{totalDeliveriesCount.toLocaleString('en-IN')} total</p>
<p className="text-[10px] text-emerald-600 font-semibold mt-0.5">{fromdate === todate ? fromdate : `${fromdate}${todate}`}</p>
</div>
</div>
<div className="bg-white border border-[#e2e8f0] p-md rounded-xl flex items-center gap-md shadow-sm">
<div className="p-2 bg-amber-50 text-amber-600 rounded-lg">
<Clock size={20} />
</div>
<div>
<p className="text-[10px] text-zinc-400 uppercase tracking-wider font-bold">Pending Fulfilment</p>
<p className="font-sans font-bold text-lg text-zinc-800">
{pendingFulfillmentCount + activeDispatchCount} active
</p>
<p className="text-[10px] text-amber-600 font-semibold mt-0.5">Awaiting dispatch / in transit</p>
</div>
</div>
<div className="bg-white border border-[#e2e8f0] p-md rounded-xl flex items-center gap-md shadow-sm">
<div className="p-2 bg-emerald-50 text-emerald-600 rounded-lg">
<Truck size={20} />
</div>
<div>
<p className="text-[10px] text-zinc-400 uppercase tracking-wider font-bold">Successful Deliveries</p>
<p className="font-sans font-bold text-lg text-zinc-800">
{completedDeliveriesCount} done
</p>
<p className="text-[10px] text-[#581c87] font-semibold mt-0.5">{locationid ? 'At this location' : 'Across all locations'}</p>
</div>
</div>
<div className="bg-white border border-[#e2e8f0] p-md rounded-xl flex items-center gap-md shadow-sm">
<div className="p-2 bg-purple-50 text-purple-600 rounded-lg">
<UserCheck size={20} />
</div>
<div>
<p className="text-[10px] text-zinc-400 uppercase tracking-wider font-bold">Active Delivery Fleet</p>
<p className="font-sans font-bold text-lg text-zinc-800">
{executives.filter(e => e.status !== 'Offline').length} partners
</p>
<p className="text-[10px] text-purple-600 font-semibold mt-0.5">{executives.length} riders registered</p>
</div>
</div>
</div>
{/* Day-wise date filter — drives the live deliveries + summary queries */}
<div className="bg-white border border-[#e2e8f0] rounded-xl p-md shadow-sm flex flex-col lg:flex-row lg:items-center justify-between gap-md">
<div className="flex items-center gap-sm flex-wrap">
<span className="flex items-center gap-1.5 text-[10px] font-bold text-zinc-400 uppercase tracking-widest pr-1">
<Calendar size={13} className="text-[#581c87]" /> View
</span>
{presets.map((p) => (
<button
key={p.key}
onClick={() => { setFromdate(p.from); setTodate(p.to); }}
className={`px-3 py-1.5 rounded-lg text-[11px] font-bold transition-all border cursor-pointer ${
activePreset === p.key
? 'bg-[#581c87] text-white border-[#581c87] shadow-sm'
: 'bg-white text-zinc-600 border-[#e2e8f0] hover:bg-zinc-50'
}`}
>
{p.label}
</button>
))}
</div>
<div className="flex items-center gap-sm text-xs">
<div className="flex items-center gap-1.5">
<label className="text-[10px] font-bold text-zinc-400 uppercase tracking-wider">From</label>
<input
type="date"
value={fromdate}
max={todate}
onChange={(e) => setFromdate(e.target.value)}
className="border border-[#e2e8f0] rounded-lg px-2 py-1.5 text-zinc-700 font-medium outline-none focus:ring-1 focus:ring-[#581c87] cursor-pointer"
/>
</div>
<span className="text-zinc-300"></span>
<div className="flex items-center gap-1.5">
<label className="text-[10px] font-bold text-zinc-400 uppercase tracking-wider">To</label>
<input
type="date"
value={todate}
min={fromdate}
max={ymd(today)}
onChange={(e) => setTodate(e.target.value)}
className="border border-[#e2e8f0] rounded-lg px-2 py-1.5 text-zinc-700 font-medium outline-none focus:ring-1 focus:ring-[#581c87] cursor-pointer"
/>
</div>
</div>
</div>
{/* Main interactive segment splits */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-gutter text-xs font-sans">
{/* Left List of Customer App Orders */}
<div className="lg:col-span-2 space-y-md">
<div className="bg-white border border-[#e2e8f0] rounded-xl shadow-sm overflow-hidden flex flex-col justify-between">
<div>
<div className="p-md border-b border-[#e2e8f0] bg-[#f8fafc] flex flex-col gap-md">
<div className="flex flex-col sm:flex-row justify-between sm:items-center gap-sm">
<div>
<h4 className="font-sans font-bold text-sm text-[#0f172a]">
Customer Orders Feed ({filteredOrdersList.length})
</h4>
<p className="text-[10px] text-zinc-400 font-medium mt-0.5">Interactive list of customer purchases made via client app</p>
</div>
<button
onClick={handleCreateMockOrder}
className="bg-[#581c87] text-white px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wider flex items-center justify-center gap-1 cursor-pointer hover:bg-purple-800 transition shadow-sm"
>
<Plus size={12} />
Create Simulated Order
</button>
</div>
<div className="flex flex-col sm:flex-row items-center gap-sm w-full">
{/* Local Search Input */}
<div className="relative w-full sm:max-w-xs">
<Search size={13} className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" />
<input
type="text"
placeholder="Search orders by customer, street, ID..."
value={localSearch}
onChange={(e) => setLocalSearch(e.target.value)}
className="w-full pl-8 pr-4 py-1.5 border border-[#e2e8f0] rounded-lg text-[11px] outline-none bg-white focus:ring-1 focus:ring-[#581c87] transition-all"
/>
</div>
{/* Filter Status buttons */}
<div className="flex gap-1 overflow-x-auto w-full sm:w-auto">
{['ALL', 'PROCESSING', 'CONFIRMED', 'OUT_FOR_DELIVERY', 'DELIVERED'].map((st) => (
<button
key={st}
onClick={() => setFilterStatus(st)}
className={`px-2 py-1.5 rounded text-[9px] font-bold uppercase transition-all border outline-none cursor-pointer whitespace-nowrap ${
filterStatus === st
? 'bg-[#581c87] text-white border-[#581c87] shadow-sm'
: 'bg-white text-zinc-500 border-[#e2e8f0] hover:bg-zinc-50'
}`}
>
{st.replace(/_/g, ' ')}
</button>
))}
</div>
</div>
</div>
{/* Order item rows */}
<div className="divide-y divide-[#f1f5f9] max-h-[480px] overflow-y-auto">
{filteredOrdersList.length === 0 ? (
<div className="p-xl text-center text-zinc-400 font-medium">
No orders matching status filter found. Try another query or place a mock delivery item.
</div>
) : (
filteredOrdersList.map(order => (
<div
key={order.id}
onClick={() => setSelectedOrder(order)}
className={`p-md flex items-center justify-between hover:bg-zinc-50 border-l-4 transition-all cursor-pointer ${
selectedOrder?.id === order.id ? 'bg-[#faf5ff]/50 border-[#581c87]' : 'border-transparent'
}`}
>
<div className="space-y-1">
<div className="flex items-center gap-sm">
<span className="font-bold text-zinc-700">{order.customerName}</span>
<span className="text-[10px] text-zinc-400"> {order.time}</span>
</div>
<p className="text-zinc-500 truncate max-w-[24rem]">{order.address}</p>
<div className="flex gap-sm py-1 items-center">
<span className="bg-[#f1f5f9] px-1.5 py-0.5 rounded text-[9px] font-bold text-zinc-500 uppercase">{order.hub}</span>
<span className="text-[9px] text-[#581c87] font-bold">{order.itemCount ?? order.items.length} Items</span>
</div>
</div>
<div className="text-right space-y-1">
<p className="font-bold font-mono text-sm text-[#0f172a]">{order.amount.toLocaleString()}</p>
<span className={`px-2 py-0.5 rounded text-[9px] font-bold tracking-wider inline-block uppercase ${
order.status === 'DELIVERED'
? 'bg-emerald-50 text-emerald-600 border border-emerald-100'
: order.status === 'OUT_FOR_DELIVERY'
? 'bg-purple-50 text-purple-700 border border-purple-100'
: order.status === 'CONFIRMED'
? 'bg-amber-50 text-amber-600 border border-amber-100 animate-pulse'
: 'bg-zinc-100 text-zinc-650 border border-zinc-200'
}`}>
{order.status.replace(/_/g, ' ')}
</span>
</div>
</div>
))
)}
</div>
</div>
</div>
{/* Delivery Executives Fleet Section */}
<div className="bg-white border border-[#e2e8f0] p-md rounded-xl shadow-sm">
<span className="text-[10px] font-sans font-bold text-zinc-400 uppercase tracking-widest block pb-xs mb-md border-b border-[#f1f5f9]">
Coimbatore Delivery Executive Fleet status
</span>
<div className="grid grid-cols-1 md:grid-cols-2 gap-sm">
{executives.map((ex) => (
<div key={ex.id} className="p-sm border border-[#e2e8f0]/80 rounded-xl bg-[#f8fafc]/40 flex justify-between items-center">
<div className="flex items-center gap-sm">
<img
src={ex.avatar}
alt={ex.name}
referrerPolicy="no-referrer"
className="w-10 h-10 rounded-full object-cover border border-zinc-200 shrink-0"
/>
<div>
<p className="font-semibold text-zinc-800">{ex.name}</p>
<p className="text-[10px] text-zinc-400 font-medium">Zone: <strong>{ex.currentZone}</strong> Rated {ex.rating}</p>
</div>
</div>
<div className="text-right">
<span className={`px-1.5 py-0.5 rounded text-[9px] font-bold uppercase inline-block ${
ex.status === 'Active Duty' ? 'bg-sky-50 text-sky-600 border border-sky-100' : ex.status === 'Idle' ? 'bg-emerald-50 text-emerald-600 border border-emerald-100' : 'bg-zinc-100 text-zinc-400'
}`}>
{ex.status}
</span>
<p className="text-[10px] text-zinc-500 font-semibold mt-1">Completed: {ex.completedToday}</p>
</div>
</div>
))}
</div>
</div>
{selectedOrder ? (
<div className="bg-white border border-[#e2e8f0] rounded-xl p-md shadow-sm space-y-md animate-in zoom-in-95 duration-150">
<span className="text-[10px] font-sans font-bold text-zinc-400 uppercase tracking-widest block pb-xs border-b border-[#f1f5f9]">
Order Details: {selectedOrder.id}
</span>
{/* Customer summary */}
<div className="p-sm bg-[#f8fafc] rounded-lg border border-[#e2e8f0]/50 space-y-xs">
<div className="flex justify-between font-semibold">
<span>Customer Name</span>
<span className="text-zinc-700">{selectedOrder.customerName}</span>
</div>
<div className="flex justify-between font-semibold">
<span>Contact info</span>
<span className="text-zinc-600 font-mono">{selectedOrder.phone}</span>
</div>
<div>
<span className="text-[10px] text-zinc-400 font-bold uppercase block mt-1">Delivery Address</span>
<p className="text-zinc-700 mt-0.5 leading-relaxed font-medium">{selectedOrder.address}</p>
</div>
</div>
{/* Category items description list */}
<div>
<span className="text-[10px] text-zinc-400 font-bold uppercase tracking-wide block mb-sm">Ordered Grocery basket Items:</span>
<div className="divide-y divide-[#f1f5f9] bg-zinc-50/50 p-2.5 rounded-lg border border-[#e2e8f0]/40">
{selectedOrder.items.length === 0 && (
<div className="py-2 flex justify-between items-center text-xs text-zinc-500">
<span className="font-medium">{selectedOrder.itemCount ?? 0} line item(s)</span>
<span className="text-[10px] text-zinc-400">Detail lines not loaded on board view</span>
</div>
)}
{selectedOrder.items.map((item, idx) => (
<div key={idx} className="py-2 flex justify-between items-center text-xs">
<div>
<p className="font-bold text-[#0f172a]">{item.name}</p>
<p className="text-[10px] text-zinc-400">Qty: {item.quantity} x {item.price}</p>
</div>
<span className="font-bold font-mono text-zinc-700">{(item.price * Number(item.quantity))}</span>
</div>
))}
<div className="pt-2 flex justify-between items-center font-bold text-sm text-[#581c87] border-t border-dashed border-[#e2e8f0]">
<span>Grand Total Invoice</span>
<span className="font-mono">{selectedOrder.amount.toLocaleString()}</span>
</div>
</div>
</div>
{/* Interactive Status advancement controls */}
<div className="pt-xs space-y-sm">
<span className="text-[9px] font-sans font-bold text-zinc-400 uppercase tracking-widest block pb-xs border-b border-[#f1f5f9]">OPERATIONAL CONTROL</span>
{selectedOrder.status === 'PROCESSING' && (
<button
onClick={() => handleUpdateStatus('CONFIRMED')}
className="w-full bg-amber-500 hover:bg-amber-600 text-white font-bold text-[11px] py-2 rounded-xl transition duration-150 shadow-sm flex items-center justify-center gap-1 cursor-pointer border-none"
>
<Check size={14} /> Pack & Bag Order
</button>
)}
{selectedOrder.status === 'CONFIRMED' && (
<button
onClick={() => {
if (selectedOrder.assignedRider === 'Pending Assignment') {
alert('Please assign a delivery partner from the fleet roster first.');
return;
}
handleUpdateStatus('OUT_FOR_DELIVERY');
}}
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold text-[11px] py-2 rounded-xl transition duration-150 shadow-sm flex items-center justify-center gap-1 cursor-pointer border-none"
>
<Truck size={14} /> Dispatch Rider
</button>
)}
{selectedOrder.status === 'OUT_FOR_DELIVERY' && (
<button
onClick={() => handleUpdateStatus('DELIVERED')}
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-[11px] py-2 rounded-xl transition duration-150 shadow-sm flex items-center justify-center gap-1 cursor-pointer border-none"
>
<CheckCircle2 size={14} /> Verify Delivery Handover
</button>
)}
{selectedOrder.status === 'DELIVERED' && (
<div className="bg-emerald-50 border border-emerald-250 text-emerald-800 font-bold text-[10px] py-2.5 rounded-xl text-center flex items-center justify-center gap-1 select-none">
<CheckCircle2 size={13} className="text-emerald-600" /> Order Completed Successfully
</div>
)}
</div>
{/* Active Rider Assignment (only if not delivered) */}
{selectedOrder.status !== 'DELIVERED' && (
<div className="space-y-sm pt-xs">
<div className="flex justify-between items-center border-b border-[#f1f5f9] pb-xs">
<span className="text-[9px] font-sans font-bold text-zinc-400 uppercase tracking-widest">ASSIGN DELIVERY EXECUTIVE</span>
<span className="text-[9px] text-[#581c87] font-bold">Fleet Roster</span>
</div>
<div className="space-y-1.5 max-h-[140px] overflow-y-auto pr-1">
{executives.length === 0 ? (
<p className="text-[10px] text-zinc-405">No riders currently available.</p>
) : (
executives.map(ex => {
const isAssigned = selectedOrder.assignedRider === ex.name;
return (
<button
key={ex.id}
type="button"
onClick={() => handleAssignRider(ex.name)}
className={`w-full p-2 border rounded-xl flex items-center justify-between text-left transition-all cursor-pointer ${
isAssigned
? 'bg-purple-50 border-[#581c87] text-[#581c87] font-semibold'
: 'bg-[#f8fafc]/50 hover:bg-zinc-55 border-[#e2e8f0] text-zinc-700'
}`}
>
<div className="flex items-center gap-2">
<img src={ex.avatar} alt={ex.name} referrerPolicy="no-referrer" className="w-6 h-6 rounded-full object-cover border border-zinc-200" />
<div>
<p className="text-[10px] font-bold leading-tight">{ex.name}</p>
<p className="text-[9px] text-zinc-450 leading-none">{ex.currentZone} {ex.rating}</p>
</div>
</div>
<span className={`text-[8px] font-bold uppercase px-1.5 py-0.5 rounded ${
isAssigned
? 'bg-[#581c87] text-white'
: 'bg-zinc-200 text-zinc-650'
}`}>
{isAssigned ? 'Assigned' : 'Assign'}
</span>
</button>
);
})
)}
</div>
</div>
)}
{/* Simulated GPS map tracking path */}
{selectedOrder.status === 'OUT_FOR_DELIVERY' && (
<div className="space-y-xs pt-xs">
<span className="text-[9px] font-sans font-bold text-zinc-400 uppercase tracking-widest block">
LIVE GPS ROUTE TRACKER
</span>
<div className="relative overflow-hidden rounded-xl border border-zinc-200 bg-zinc-950 p-4 h-40 text-white flex flex-col justify-between font-sans shadow-inner select-none">
{/* Grid background lines */}
<div className="absolute inset-0 opacity-10 bg-[linear-gradient(to_right,#808080_1px,transparent_1px),linear-gradient(to_bottom,#808080_1px,transparent_1px)] bg-[size:12px_18px]" />
<svg className="absolute inset-0 w-full h-full" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="route-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#c084fc" />
<stop offset="100%" stopColor="#818cf8" />
</linearGradient>
</defs>
{/* Route path line */}
<path
d="M 30 110 C 60 70, 110 110, 160 40"
fill="none"
stroke="#1e293b"
strokeWidth="4"
strokeLinecap="round"
/>
<path
d="M 30 110 C 60 70, 110 110, 160 40"
fill="none"
stroke="url(#route-grad)"
strokeWidth="4"
strokeLinecap="round"
strokeDasharray="200"
strokeDashoffset="200"
style={{
animation: 'dash 6s linear infinite'
}}
/>
{/* Hub Marker */}
<circle cx="30" cy="110" r="5" fill="#c084fc" className="animate-pulse" />
<circle cx="30" cy="110" r="3" fill="#a855f7" />
{/* Destination Marker */}
<circle cx="160" cy="40" r="5" fill="#f43f5e" className="animate-ping" />
<circle cx="160" cy="40" r="3" fill="#e11d48" />
</svg>
<style dangerouslySetInnerHTML={{__html: `
@keyframes dash {
to {
stroke-dashoffset: 0;
}
}
`}} />
{/* Map overlays */}
<div className="z-10 flex justify-between items-start">
<div className="bg-zinc-900/90 backdrop-blur-md px-2 py-0.5 rounded border border-zinc-800 text-[8px] font-bold text-zinc-300">
GPS ACTIVE: IN TRANSIT
</div>
<div className="bg-zinc-900/90 backdrop-blur-md px-2 py-0.5 rounded border border-zinc-800 text-[8px] font-bold text-[#c084fc] flex items-center gap-1">
<span className="w-1 h-1 rounded-full bg-purple-500 animate-ping" />
ETA 9 MINS
</div>
</div>
<div className="z-10 bg-zinc-900/95 backdrop-blur-md p-2 rounded-lg border border-zinc-800 flex items-center justify-between">
<div>
<p className="text-[8px] text-zinc-400 font-bold uppercase tracking-wider">Executive</p>
<p className="text-[10px] font-bold text-white leading-tight">{selectedOrder.assignedRider}</p>
</div>
<div className="text-right">
<p className="text-[8px] text-zinc-400 font-bold uppercase tracking-wider">Distance</p>
<p className="text-[10px] font-bold text-[#c084fc] font-mono leading-tight">1.2 km left</p>
</div>
</div>
</div>
</div>
)}
{/* Delivery tracking visual roadmap layout */}
<div className="bg-zinc-50 border border-[#e2e8f0]/60 rounded-xl p-md">
<span className="text-[9px] font-sans font-bold text-zinc-400 uppercase tracking-widest block pb-xs mb-sm border-b border-[#f1f5f9]">
Live Dispatch Timeline Tracker
</span>
<div className="space-y-xs pt-1 relative text-[11px]">
<div className="flex gap-md items-start relative group">
<span className="text-emerald-500 mt-0.5"><CheckCircle2 size={12} /></span>
<div>
<h5 className="font-semibold text-zinc-800">Order Received ({selectedOrder.time})</h5>
<p className="text-[10px] text-zinc-400">Placed via customer app cart checkout successfully.</p>
</div>
</div>
<div className="flex gap-md items-start pt-3">
<span className={['CONFIRMED', 'OUT_FOR_DELIVERY', 'DELIVERED'].includes(selectedOrder.status) ? 'text-emerald-500 mt-0.5' : 'text-zinc-300 mt-0.5'}><CheckCircle2 size={12} /></span>
<div>
<h5 className="font-semibold text-zinc-800">Assortment Packaged & Bagged</h5>
<p className="text-[10px] text-zinc-400">Verified fresh produce items in-stock levels.</p>
</div>
</div>
<div className="flex gap-md items-start pt-3">
<span className={['OUT_FOR_DELIVERY', 'DELIVERED'].includes(selectedOrder.status) ? 'text-emerald-500 mt-0.5' : 'text-zinc-300 mt-0.5'}><CheckCircle2 size={12} /></span>
<div>
<h5 className="font-semibold text-zinc-800">Out for Delivery</h5>
<p className="text-[10px] text-zinc-400">Dispatched with executive partner on bike route.</p>
</div>
</div>
<div className="flex gap-md items-start pt-3">
<span className={selectedOrder.status === 'DELIVERED' ? 'text-emerald-500 mt-0.5' : 'text-zinc-300 mt-0.5'}><CheckCircle2 size={12} /></span>
<div>
<h5 className="font-semibold text-zinc-800">Handover Verified</h5>
<p className="text-[10px] text-zinc-400">Delivered directly to door step location.</p>
</div>
</div>
</div>
</div>
</div>
) : (
<div className="p-xl bg-white border border-[#e2e8f0] rounded-xl text-center text-zinc-400 font-medium">
Select any customer order from the feed to view its details.
</div>
)}
</div>
</div>
</div>
);
}