import product catalogue
This commit is contained in:
@@ -131,6 +131,7 @@ export default function DispatchView({ locationid, tenantId = FIESTA_TENANT_ID,
|
||||
setFocusedId(null);
|
||||
}, [viewMode]);
|
||||
|
||||
const [customerStoreFilter, setCustomerStoreFilter] = useState<string>('all');
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [tripSort, setTripSort] = useState<'planned' | 'time'>('planned');
|
||||
const [animateNonce, setAnimateNonce] = useState(0);
|
||||
@@ -167,6 +168,7 @@ export default function DispatchView({ locationid, tenantId = FIESTA_TENANT_ID,
|
||||
|
||||
if (viewMode === 'stores' && locationsQ.data) {
|
||||
for (const loc of locationsQ.data) {
|
||||
if (locationid && fnum(loc.locationid) !== locationid) continue;
|
||||
const id = String(fnum(loc.locationid)).toLowerCase();
|
||||
const name = fstr(loc.locationname) || `Store ${id}`;
|
||||
map.set(id, { id, name, color: colorFor(id), orders: [], delivered: 0, totalKm: 0, profit: 0, riders: new Set(), suburbs: new Map(), statusCounts: {} });
|
||||
@@ -175,6 +177,10 @@ export default function DispatchView({ locationid, tenantId = FIESTA_TENANT_ID,
|
||||
|
||||
if (viewMode === 'customers' && customersQ.data) {
|
||||
for (const cust of customersQ.data) {
|
||||
if (customerStoreFilter !== 'all') {
|
||||
const locId = String(fnum(cust.locationid));
|
||||
if (locId !== customerStoreFilter) continue;
|
||||
}
|
||||
const id = String(fnum(cust.customerid) || fstr(cust.contactno)).toLowerCase();
|
||||
const name = fstr(cust.customername) || fstr(cust.name) || `Customer ${id}`;
|
||||
map.set(id, { id, name, color: colorFor(id), orders: [], delivered: 0, totalKm: 0, profit: 0, riders: new Set(), suburbs: new Map(), statusCounts: {}, raw: cust });
|
||||
@@ -221,6 +227,12 @@ export default function DispatchView({ locationid, tenantId = FIESTA_TENANT_ID,
|
||||
return Array.from(map.values()).sort((a, b) => b.orders.length - a.orders.length || a.name.localeCompare(b.name));
|
||||
}, [rows, viewMode, locationsQ.data, customersQ.data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewMode === 'stores' && locationid && groups.length === 1 && !focusedId) {
|
||||
setFocusedId(groups[0].id);
|
||||
}
|
||||
}, [viewMode, locationid, groups, focusedId]);
|
||||
|
||||
const focused = groups.find((g) => g.id === focusedId) ?? null;
|
||||
const groupedByRider = viewMode !== 'riders';
|
||||
|
||||
@@ -361,7 +373,7 @@ export default function DispatchView({ locationid, tenantId = FIESTA_TENANT_ID,
|
||||
groupedByRider={groupedByRider}
|
||||
tripSort={tripSort}
|
||||
setTripSort={setTripSort}
|
||||
onBack={() => setFocusedId(null)}
|
||||
onBack={(locationid && viewMode === 'stores' && groups.length === 1) ? undefined : () => setFocusedId(null)}
|
||||
fmtTime={fmtTime}
|
||||
riderLogs={riderLogsQ.data}
|
||||
riderLogsLoading={riderLogsQ.isLoading}
|
||||
@@ -370,8 +382,31 @@ export default function DispatchView({ locationid, tenantId = FIESTA_TENANT_ID,
|
||||
<div className="ph">{viewMode === 'customers' ? 'No customers found' : 'No deliveries for this day'}</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="ph">
|
||||
{viewMode === 'riders' ? 'Riders' : viewMode === 'customers' ? 'Customers' : viewMode === 'stores' ? 'Stores' : 'Zones'} ({groups.length})
|
||||
<div className="ph relative flex items-center justify-between w-full h-8">
|
||||
<span>{viewMode === 'riders' ? 'Riders' : viewMode === 'customers' ? 'Customers' : viewMode === 'stores' ? 'Stores' : 'Zones'} ({groups.length})</span>
|
||||
{viewMode === 'customers' && !locationid && locationsQ.data && (
|
||||
<div className="absolute right-0 flex items-center group/filter cursor-pointer">
|
||||
<select
|
||||
className="appearance-none w-36 text-[11px] font-extrabold tracking-wide rounded-md pl-3 pr-8 py-1.5 bg-white text-slate-800 shadow-sm ring-1 ring-slate-900/5 hover:bg-slate-50 outline-none focus:ring-2 focus:ring-[#662582]/30 transition-all duration-300 cursor-pointer relative z-0 truncate"
|
||||
style={{ textOverflow: 'ellipsis' }}
|
||||
value={customerStoreFilter}
|
||||
onChange={(e) => {
|
||||
setCustomerStoreFilter(e.target.value);
|
||||
setFocusedId(null);
|
||||
}}
|
||||
>
|
||||
<option value="all">All Stores</option>
|
||||
{locationsQ.data.map(loc => (
|
||||
<option key={fnum(loc.locationid)} value={String(fnum(loc.locationid))}>
|
||||
{fstr(loc.locationname) || `Store ${fnum(loc.locationid)}`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute right-2 text-slate-400 pointer-events-none group-hover/filter:text-[#662582] transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{groups.map((g) => {
|
||||
const isSelected = focusedId === g.id;
|
||||
@@ -580,16 +615,18 @@ function FocusedDetail({
|
||||
groupedByRider: boolean;
|
||||
tripSort: 'planned' | 'time';
|
||||
setTripSort: (v: 'planned' | 'time') => void;
|
||||
onBack: () => void;
|
||||
onBack?: () => void;
|
||||
fmtTime: (raw: unknown) => string;
|
||||
riderLogs?: Row[];
|
||||
riderLogsLoading?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<button className="sbt" onClick={onBack} style={{ marginBottom: 12 }}>
|
||||
<span className="sbt-icon"><ChevronLeft size={15} /></span> Back to list
|
||||
</button>
|
||||
{onBack && (
|
||||
<button className="sbt" onClick={onBack} style={{ marginBottom: 12 }}>
|
||||
<span className="sbt-icon"><ChevronLeft size={15} /></span> Back to list
|
||||
</button>
|
||||
)}
|
||||
|
||||
{riderLogs && riderLogs.length > 0 && (
|
||||
<RiderTelemetryPanel
|
||||
@@ -599,6 +636,16 @@ function FocusedDetail({
|
||||
/>
|
||||
)}
|
||||
|
||||
{tripBlocks.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center p-8 text-center h-48 border-2 border-dashed border-slate-200 rounded-xl mt-4 bg-slate-50/50">
|
||||
<div className="w-12 h-12 rounded-full bg-slate-100 flex items-center justify-center text-slate-400 mb-3">
|
||||
<Package size={20} />
|
||||
</div>
|
||||
<p className="font-bold text-slate-700 text-sm">No orders to display</p>
|
||||
<p className="text-xs text-slate-500 mt-1">There are no deliveries matching this selection for the current date.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tripBlocks.map((blk, bi) => (
|
||||
<div className="trip-block" key={bi}>
|
||||
<div className="trip-header" style={{ background: `${blk.color}12`, borderColor: `${blk.color}40` }}>
|
||||
|
||||
Reference in New Issue
Block a user