import product catalogue
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ShoppingBag, Clock, CheckCircle2, XCircle, Calendar, ChevronLeft, ChevronRight, Package, MapPin, Phone, X, Loader2, Download, UserCheck, ClipboardList, ArrowLeft } from 'lucide-react';
|
||||
import { useFiestaOrderSummary, useFiestaOrders, useFiestaOrderDetails, useFiestaRiders, useFiestaAssignRider } from '../services/fiestaQueries';
|
||||
import { useFiestaOrderSummary, useFiestaOrders, useFiestaOrderDetails, useFiestaRiders, useFiestaAssignRider, useFiestaUsers } 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 {
|
||||
@@ -105,6 +105,8 @@ export default function OrdersView({ searchQuery = '', locationid, tenantId = FI
|
||||
setShowSelected(false);
|
||||
}, [fromdate, todate, status, branch, pageno, locationid]);
|
||||
|
||||
const [riderSource, setRiderSource] = useState<'own' | 'partner'>('own');
|
||||
|
||||
// Scope to the user's store when a locationid is supplied (server-side per the
|
||||
// backend's getordersummary/getorders locationid param); tenant-wide otherwise.
|
||||
const summaryQ = useFiestaOrderSummary(tenantId, fromdate, todate, locationid);
|
||||
@@ -118,20 +120,45 @@ export default function OrdersView({ searchQuery = '', locationid, tenantId = FI
|
||||
// simply won't appear — the intended guard.
|
||||
const orderPartnerId = useMemo(() => fnum(rawRows.find((r) => fnum(r.partnerid))?.partnerid), [rawRows]);
|
||||
const orderApplocationId = useMemo(() => fnum(rawRows.find((r) => fnum(r.applocationid))?.applocationid), [rawRows]);
|
||||
|
||||
const ridersQ = useFiestaRiders({
|
||||
tenantid: tenantId,
|
||||
applocationid: orderApplocationId || undefined,
|
||||
partnerid: orderPartnerId || undefined,
|
||||
// We omit partnerid here to fetch all partner riders for the location at once.
|
||||
});
|
||||
|
||||
const internalRidersQ = useFiestaUsers({
|
||||
tenantid: tenantId,
|
||||
roleid: 5, // 5 = Rider role
|
||||
pagesize: 500
|
||||
});
|
||||
|
||||
const riderOptions = useMemo(
|
||||
() =>
|
||||
(ridersQ.data ?? [])
|
||||
() => {
|
||||
const externalRiders = ridersQ.data ?? [];
|
||||
const internalRiders = internalRidersQ.data ?? [];
|
||||
const allRiders = [...externalRiders, ...internalRiders];
|
||||
|
||||
const filtered = allRiders.filter((r) => {
|
||||
const pId = fnum(r.partnerid);
|
||||
if (riderSource === 'own') {
|
||||
// Store fleet riders are internal users (they have no partner id)
|
||||
return !pId || pId === 0;
|
||||
} else {
|
||||
// Partner riders belong to a 3rd party (partnerid > 0)
|
||||
// If the order already has a specific partnerid, we only show riders from that partner.
|
||||
return pId > 0 && (!orderPartnerId || pId === orderPartnerId);
|
||||
}
|
||||
});
|
||||
|
||||
return filtered
|
||||
.map((r) => ({
|
||||
id: fnum(r.userid),
|
||||
label: `${fstr(r.firstname)} ${fstr(r.lastname)}`.trim() + (fstr(r.contactno) ? ` · ${fstr(r.contactno)}` : ''),
|
||||
}))
|
||||
.filter((o) => o.id > 0 && o.label),
|
||||
[ridersQ.data],
|
||||
.filter((o) => o.id > 0 && o.label);
|
||||
},
|
||||
[ridersQ.data, internalRidersQ.data, riderSource, orderPartnerId],
|
||||
);
|
||||
|
||||
// Branches (app-locations) present in the data — drives the branch filter so the
|
||||
@@ -342,6 +369,22 @@ export default function OrdersView({ searchQuery = '', locationid, tenantId = FI
|
||||
<span className="inline-flex items-center gap-1.5 font-extrabold text-xs" style={{ color: BRAND }}>
|
||||
<UserCheck size={15} /> {selected.size} selected
|
||||
</span>
|
||||
<div className="flex bg-white rounded-full p-0.5 ml-2 border" style={{ borderColor: edge(BRAND) }}>
|
||||
<button
|
||||
onClick={() => setRiderSource('own')}
|
||||
className={`px-3 py-1 text-[11px] font-bold rounded-full transition-colors ${riderSource === 'own' ? '' : 'text-slate-500 hover:bg-slate-50'}`}
|
||||
style={riderSource === 'own' ? { background: tint(BRAND), color: BRAND } : undefined}
|
||||
>
|
||||
Store Fleet
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setRiderSource('partner')}
|
||||
className={`px-3 py-1 text-[11px] font-bold rounded-full transition-colors ${riderSource === 'partner' ? '' : 'text-slate-500 hover:bg-slate-50'}`}
|
||||
style={riderSource === 'partner' ? { background: tint(BRAND), color: BRAND } : undefined}
|
||||
>
|
||||
Partners
|
||||
</button>
|
||||
</div>
|
||||
<select
|
||||
value={assignRiderId}
|
||||
onChange={(e) => setAssignRiderId(Number(e.target.value))}
|
||||
@@ -350,7 +393,7 @@ export default function OrdersView({ searchQuery = '', locationid, tenantId = FI
|
||||
className="rounded-full font-bold text-xs outline-none cursor-pointer disabled:opacity-50"
|
||||
style={{ padding: '7px 12px', border: `1.5px solid ${edge(BRAND)}`, background: '#fff', color: BRAND, maxWidth: 260 }}
|
||||
>
|
||||
<option value={0}>{ridersQ.isLoading ? 'Loading riders…' : riderOptions.length ? 'Select rider…' : 'No riders available'}</option>
|
||||
<option value={0}>{ridersQ.isLoading ? 'Loading riders…' : riderOptions.length ? 'Select rider…' : `No ${riderSource === 'own' ? 'store' : 'partner'} riders available`}</option>
|
||||
{riderOptions.map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
|
||||
</select>
|
||||
<button
|
||||
@@ -371,7 +414,7 @@ export default function OrdersView({ searchQuery = '', locationid, tenantId = FI
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className="bg-white border rounded-2xl overflow-hidden" style={{ borderColor: BORDER }}>
|
||||
<div className="bg-white border overflow-hidden" style={{ borderColor: BORDER }}>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full" style={{ minWidth: 960 }}>
|
||||
<thead>
|
||||
@@ -554,7 +597,7 @@ function SelectedOrdersPage({
|
||||
No orders selected. <button onClick={onClose} className="font-bold underline cursor-pointer" style={{ color: BRAND }}>Go back</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white border rounded-2xl overflow-hidden" style={{ borderColor: BORDER }}>
|
||||
<div className="bg-white border overflow-hidden" style={{ borderColor: BORDER }}>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full" style={{ minWidth: 720 }}>
|
||||
<thead><tr>{['#', 'Order', 'Pickup', 'Drop', 'Status', ''].map((h, i) => <th key={i} className="px-3 py-2.5 text-left" style={TH_STYLE}>{h}</th>)}</tr></thead>
|
||||
@@ -632,7 +675,7 @@ function OrderDetailModal({ order, onClose }: { order: Row; onClose: () => void
|
||||
return createPortal(
|
||||
<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 max-h-[90vh] flex flex-col overflow-hidden rounded-2xl animate-in zoom-in-95 duration-200" style={{ width: 'min(32rem, 92vw)', border: `1px solid ${BORDER}`, boxShadow: '0 18px 50px rgba(15,23,42,0.18)' }}>
|
||||
<div className="bg-white max-h-[90vh] flex flex-col overflow-hidden animate-in zoom-in-95 duration-200" style={{ width: 'min(32rem, 92vw)', border: `1px solid ${BORDER}`, boxShadow: '0 18px 50px rgba(15,23,42,0.18)' }}>
|
||||
<div style={{ height: 4, background: `linear-gradient(90deg, ${BRAND} 0%, ${soft(BRAND)} 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 }}><Package size={16} style={{ color: BRAND }} /> Order {fstr(order.orderid) || `#${fstr(order.orderheaderid)}`}</h4>
|
||||
|
||||
Reference in New Issue
Block a user