WFDASHDRIVER-APP/components/SupplierGroup.tsx
SJCoetz 2c896eede8 Add SupplierGroup and supplier view modes
Introduce a new SupplierGroup component and group collections by supplier. Add supplier color palette, per-supplier expand/collapse state, compact/detailed view modes, and controls to toggle/expand/collapse groups. Pass supplierColor and compact props to CollectionItemCard to adjust visuals, use useMemo for grouping, and auto-expand suppliers on initial load. Minor UI tweaks: adjust skeleton count/spacing, update stats/controls bar, improve toast styling, and remove some console logs.
2026-05-27 00:55:21 +02:00

210 lines
No EOL
8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { OrderItem, Supplier } from '@/lib/types';
import { cn, formatCurrency, parseCoordinates } from '@/lib/utils';
import CollectionItemCard from '@/components/CollectionItemCard';
import {
ChevronDown,
ChevronUp,
MapPin,
Navigation,
Package,
ShoppingCart,
Hammer,
Wrench,
AlertTriangle,
} from 'lucide-react';
interface SupplierColor {
border: string;
dot: string;
light: string;
text: string;
ring: string;
}
interface SupplierGroupProps {
supplier: Supplier;
items: OrderItem[];
color: SupplierColor;
isExpanded: boolean;
viewMode: 'compact' | 'detailed';
onToggle: () => void;
onCollect: (item: OrderItem) => void;
onProblem: (item: OrderItem) => void;
onNavigate: (lat: number, lng: number, name: string) => void;
}
function getItemIcon(item: OrderItem) {
if (item.item_status === 'problem' || item.problem_flag) return <AlertTriangle size={12} className="text-red-500" />;
const orderNumber = item.expand?.order?.order_number || '';
if (orderNumber.toLowerCase().includes('breakdown')) return <Hammer size={12} className="text-orange-500" />;
if (orderNumber.toLowerCase().includes('repair')) return <Wrench size={12} className="text-amber-500" />;
if (item.is_stock_item) return <Package size={12} className="text-violet-500" />;
return <ShoppingCart size={12} className="text-gray-400" />;
}
export default function SupplierGroup({
supplier,
items,
color,
isExpanded,
viewMode,
onToggle,
onCollect,
onProblem,
onNavigate,
}: SupplierGroupProps) {
const coords = parseCoordinates(supplier.coordinates);
const hasCoords = coords !== null;
const totalQty = items.reduce((sum, i) => sum + (i.order_qty || i.quantity || 0), 0);
return (
<div
className={cn(
'bg-white rounded-2xl card-shadow overflow-hidden transition-all duration-200',
`border-l-[4px] ${color.border}`
)}
>
{/* Supplier Header — always visible, tap to expand/collapse */}
<button
onClick={onToggle}
className="w-full text-left p-4 active:bg-gray-50/50 transition-colors"
>
<div className="flex items-center gap-3">
{/* Color dot */}
<div className={cn('w-9 h-9 rounded-xl flex items-center justify-center flex-shrink-0', color.light)}>
<MapPin size={16} className={color.text} />
</div>
<div className="flex-1 min-w-0">
<h2 className={cn('font-semibold text-sm truncate leading-snug', color.text)}>
{supplier.suppliername}
</h2>
{supplier.supplieraddress && (
<p className="text-[11px] text-gray-400 truncate mt-0.5">{supplier.supplieraddress}</p>
)}
</div>
{/* Right side info */}
<div className="flex items-center gap-2 flex-shrink-0">
<div className="flex flex-col items-end gap-0.5">
<span className={cn('text-[11px] font-bold', color.text)}>
{items.length} item{items.length !== 1 ? 's' : ''}
</span>
<span className="text-[10px] text-gray-400">{totalQty} total</span>
</div>
<div className="flex items-center justify-center w-7 h-7">
{isExpanded ? (
<ChevronUp size={18} className="text-gray-400" />
) : (
<ChevronDown size={18} className="text-gray-400" />
)}
</div>
</div>
</div>
{/* Compact preview line (only when collapsed in compact mode) */}
{viewMode === 'compact' && !isExpanded && (
<div className="mt-2 ml-12 flex items-center gap-1.5 flex-wrap">
{items.slice(0, 3).map((item, i) => (
<span key={item.id} className="text-[11px] text-gray-500 truncate max-w-[140px]">
{item.description || 'Item'}
{i < Math.min(items.length, 3) - 1 && ','}
</span>
))}
{items.length > 3 && (
<span className="text-[10px] text-gray-400 font-medium">
+{items.length - 3} more
</span>
)}
</div>
)}
</button>
{/* Expanded content */}
{isExpanded && (
<div className="border-t border-gray-100 animate-fade-in">
{viewMode === 'compact' ? (
/* COMPACT MODE: simple list with item name + qty */
<div className="divide-y divide-gray-50">
{items.map((item) => {
const expectedQty = item.order_qty || item.quantity || 0;
return (
<div
key={item.id}
className="flex items-center justify-between px-4 py-3 hover:bg-gray-50/50 transition-colors cursor-pointer active:bg-gray-100"
onClick={() => {
// Determine if we should open collect or problem modal
// For now, just expand - user can use the action buttons
}}
>
<div className="flex items-center gap-2.5 min-w-0 flex-1">
{getItemIcon(item)}
<span className="text-sm text-gray-800 truncate">
{item.description || 'Unknown item'}
</span>
{item.item_status === 'problem' && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-red-100 text-red-700 font-medium flex-shrink-0">
Problem
</span>
)}
</div>
<div className="flex items-center gap-3 flex-shrink-0 ml-2">
<span className="text-sm font-semibold text-gray-700">{expectedQty}×</span>
<div className="flex items-center gap-1">
<button
onClick={(e) => { e.stopPropagation(); onCollect(item); }}
className="w-8 h-8 rounded-lg bg-green-500 text-white flex items-center justify-center active:bg-green-600 transition-colors touch-manipulation"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</button>
<button
onClick={(e) => { e.stopPropagation(); onProblem(item); }}
className="w-8 h-8 rounded-lg bg-red-500 text-white flex items-center justify-center active:bg-red-600 transition-colors touch-manipulation"
>
<AlertTriangle size={14} />
</button>
</div>
</div>
</div>
);
})}
</div>
) : (
/* DETAILED MODE: full item cards */
<div className="p-3 space-y-2">
{items.map((item) => (
<CollectionItemCard
key={item.id}
item={item}
supplierColor={color}
compact
onCollect={onCollect}
onProblem={onProblem}
onNavigate={onNavigate}
/>
))}
</div>
)}
{/* Supplier footer with navigate button */}
{hasCoords && coords && (
<div className="px-4 pb-3">
<button
onClick={() => onNavigate(coords.latitude, coords.longitude, supplier.suppliername)}
className={cn(
'w-full flex items-center justify-center gap-2 py-2.5 rounded-xl text-sm font-semibold text-white transition-all touch-manipulation press-feedback',
color.dot
)}
>
<Navigation size={14} />
Navigate to {supplier.suppliername}
</button>
</div>
)}
</div>
)}
</div>
);
}