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.
This commit is contained in:
parent
db10eb95c7
commit
2c896eede8
3 changed files with 337 additions and 71 deletions
185
app/page.tsx
185
app/page.tsx
|
|
@ -1,12 +1,13 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { OrderItem } from '@/lib/types';
|
||||
import { useAuth } from '@/components/AuthProvider';
|
||||
import BottomNav from '@/components/BottomNav';
|
||||
import LocationTracker from '@/components/LocationTracker';
|
||||
import CollectionItemCard from '@/components/CollectionItemCard';
|
||||
import SupplierGroup from '@/components/SupplierGroup';
|
||||
import CollectModal from '@/components/CollectModal';
|
||||
import ProblemModal from '@/components/ProblemModal';
|
||||
import {
|
||||
|
|
@ -26,9 +27,23 @@ import {
|
|||
Search,
|
||||
X,
|
||||
Package,
|
||||
ListFilter,
|
||||
LayoutList,
|
||||
Columns,
|
||||
} from 'lucide-react';
|
||||
|
||||
const SUPPLIER_COLORS = [
|
||||
{ border: 'border-l-orange-500', dot: 'bg-orange-500', light: 'bg-orange-50', text: 'text-orange-700', ring: 'ring-orange-500/30' },
|
||||
{ border: 'border-l-blue-500', dot: 'bg-blue-500', light: 'bg-blue-50', text: 'text-blue-700', ring: 'ring-blue-500/30' },
|
||||
{ border: 'border-l-emerald-500', dot: 'bg-emerald-500', light: 'bg-emerald-50', text: 'text-emerald-700', ring: 'ring-emerald-500/30' },
|
||||
{ border: 'border-l-violet-500', dot: 'bg-violet-500', light: 'bg-violet-50', text: 'text-violet-700', ring: 'ring-violet-500/30' },
|
||||
{ border: 'border-l-rose-500', dot: 'bg-rose-500', light: 'bg-rose-50', text: 'text-rose-700', ring: 'ring-rose-500/30' },
|
||||
{ border: 'border-l-cyan-500', dot: 'bg-cyan-500', light: 'bg-cyan-50', text: 'text-cyan-700', ring: 'ring-cyan-500/30' },
|
||||
{ border: 'border-l-amber-500', dot: 'bg-amber-500', light: 'bg-amber-50', text: 'text-amber-700', ring: 'ring-amber-500/30' },
|
||||
{ border: 'border-l-pink-500', dot: 'bg-pink-500', light: 'bg-pink-50', text: 'text-pink-700', ring: 'ring-pink-500/30' },
|
||||
{ border: 'border-l-teal-500', dot: 'bg-teal-500', light: 'bg-teal-50', text: 'text-teal-700', ring: 'ring-teal-500/30' },
|
||||
{ border: 'border-l-indigo-500', dot: 'bg-indigo-500', light: 'bg-indigo-50', text: 'text-indigo-700', ring: 'ring-indigo-500/30' },
|
||||
];
|
||||
|
||||
export default function CollectionsPage() {
|
||||
const router = useRouter();
|
||||
const { isLoggedIn, user, isLoading: authLoading } = useAuth();
|
||||
|
|
@ -42,6 +57,10 @@ export default function CollectionsPage() {
|
|||
const [showCollectModal, setShowCollectModal] = useState(false);
|
||||
const [showProblemModal, setShowProblemModal] = useState(false);
|
||||
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'warning' } | null>(null);
|
||||
const [viewMode, setViewMode] = useState<'compact' | 'detailed'>('compact');
|
||||
|
||||
// Track which supplier groups are expanded
|
||||
const [expandedSuppliers, setExpandedSuppliers] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading && !isLoggedIn) router.push('/login/');
|
||||
|
|
@ -51,11 +70,16 @@ export default function CollectionsPage() {
|
|||
if (!isAuthenticated()) return;
|
||||
if (showLoading) setIsLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const data = await getAssignedCollections();
|
||||
setItems(data);
|
||||
setFilteredItems(data);
|
||||
// Auto-expand all suppliers on fresh load
|
||||
const allIds = new Set<string>();
|
||||
data.forEach(i => {
|
||||
if (i.expand?.supplier?.id) allIds.add(i.expand.supplier.id);
|
||||
});
|
||||
setExpandedSuppliers(allIds);
|
||||
} catch (err) {
|
||||
console.error('Failed to load collections:', err);
|
||||
setError('Failed to load collections. Pull down to retry.');
|
||||
|
|
@ -69,7 +93,6 @@ export default function CollectionsPage() {
|
|||
|
||||
useEffect(() => {
|
||||
let filtered = items;
|
||||
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
filtered = filtered.filter((i) => {
|
||||
|
|
@ -83,7 +106,6 @@ export default function CollectionsPage() {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
setFilteredItems(filtered);
|
||||
}, [items, searchQuery]);
|
||||
|
||||
|
|
@ -108,7 +130,6 @@ export default function CollectionsPage() {
|
|||
setShowCollectModal(false);
|
||||
showToast('Collection completed successfully');
|
||||
} catch (err) {
|
||||
console.error('Failed to collect:', err);
|
||||
showToast('Failed to update collection', 'error');
|
||||
}
|
||||
};
|
||||
|
|
@ -120,7 +141,6 @@ export default function CollectionsPage() {
|
|||
setShowProblemModal(false);
|
||||
showToast('Problem reported successfully', 'warning');
|
||||
} catch (err) {
|
||||
console.error('Failed to report problem:', err);
|
||||
showToast('Failed to report problem', 'error');
|
||||
}
|
||||
};
|
||||
|
|
@ -133,21 +153,40 @@ export default function CollectionsPage() {
|
|||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
// Group by supplier
|
||||
const groupedBySupplier = filteredItems.reduce((acc, item) => {
|
||||
const supplier = item.expand?.supplier;
|
||||
if (!supplier) return acc;
|
||||
const key = supplier.id;
|
||||
if (!acc[key]) {
|
||||
acc[key] = { supplier, items: [] };
|
||||
}
|
||||
acc[key].items.push(item);
|
||||
return acc;
|
||||
}, {} as Record<string, { supplier: NonNullable<OrderItem['expand']>['supplier']; items: OrderItem[] }>);
|
||||
const toggleSupplier = (supplierId: string) => {
|
||||
setExpandedSuppliers(prev => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(supplierId)) next.delete(supplierId);
|
||||
else next.add(supplierId);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const groupedArray = Object.values(groupedBySupplier).sort((a, b) =>
|
||||
(a.supplier?.suppliername || '').localeCompare(b.supplier?.suppliername || '')
|
||||
);
|
||||
const expandAll = () => {
|
||||
const allIds = new Set<string>();
|
||||
filteredItems.forEach(i => {
|
||||
if (i.expand?.supplier?.id) allIds.add(i.expand.supplier.id);
|
||||
});
|
||||
setExpandedSuppliers(allIds);
|
||||
};
|
||||
|
||||
const collapseAll = () => setExpandedSuppliers(new Set());
|
||||
|
||||
// Group by supplier
|
||||
const groupedArray = useMemo(() => {
|
||||
const grouped = filteredItems.reduce((acc, item) => {
|
||||
const supplier = item.expand?.supplier;
|
||||
if (!supplier) return acc;
|
||||
const key = supplier.id;
|
||||
if (!acc[key]) acc[key] = { supplier, items: [] };
|
||||
acc[key].items.push(item);
|
||||
return acc;
|
||||
}, {} as Record<string, { supplier: NonNullable<OrderItem['expand']>['supplier']; items: OrderItem[] }>);
|
||||
|
||||
return Object.values(grouped).sort((a, b) =>
|
||||
(a.supplier?.suppliername || '').localeCompare(b.supplier?.suppliername || '')
|
||||
);
|
||||
}, [filteredItems]);
|
||||
|
||||
if (authLoading) {
|
||||
return (
|
||||
|
|
@ -196,7 +235,7 @@ export default function CollectionsPage() {
|
|||
</div>
|
||||
|
||||
{/* Search bar */}
|
||||
<div className="px-4 pb-3">
|
||||
<div className="px-4 pb-2">
|
||||
<div className="relative">
|
||||
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-white/50" />
|
||||
<input
|
||||
|
|
@ -217,18 +256,42 @@ export default function CollectionsPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats pill */}
|
||||
<div className="px-4 pb-2 flex items-center gap-2">
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-white/15 text-[11px] font-medium">
|
||||
<Package size={12} />
|
||||
{items.length} item{items.length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
{groupedArray.length > 0 && (
|
||||
{/* Stats + Controls bar */}
|
||||
<div className="px-4 pb-2 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-white/15 text-[11px] font-medium">
|
||||
<MapPin size={12} />
|
||||
{groupedArray.length} stop{groupedArray.length !== 1 ? 's' : ''}
|
||||
<Package size={12} />
|
||||
{items.length} item{items.length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
{groupedArray.length > 0 && (
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-white/15 text-[11px] font-medium">
|
||||
<MapPin size={12} />
|
||||
{groupedArray.length} stop{groupedArray.length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* View toggle */}
|
||||
<div className="flex items-center gap-1 bg-white/10 rounded-lg p-0.5">
|
||||
<button
|
||||
onClick={() => setViewMode('compact')}
|
||||
className={cn(
|
||||
'px-2.5 py-1 rounded-md text-[11px] font-medium transition-all',
|
||||
viewMode === 'compact' ? 'bg-white text-orange-600 shadow-sm' : 'text-white/70'
|
||||
)}
|
||||
>
|
||||
Compact
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setViewMode('detailed'); expandAll(); }}
|
||||
className={cn(
|
||||
'px-2.5 py-1 rounded-md text-[11px] font-medium transition-all',
|
||||
viewMode === 'detailed' ? 'bg-white text-orange-600 shadow-sm' : 'text-white/70'
|
||||
)}
|
||||
>
|
||||
Detailed
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -236,8 +299,8 @@ export default function CollectionsPage() {
|
|||
<main className="p-4 custom-scrollbar">
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<div key={i} className="bg-white rounded-2xl p-4 card-shadow stagger-1">
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<div key={i} className="bg-white rounded-2xl p-4 card-shadow">
|
||||
<div className="skeleton h-4 w-3/4 rounded-lg mb-3" />
|
||||
<div className="skeleton h-3 w-1/2 rounded-lg mb-2" />
|
||||
<div className="skeleton h-3 w-2/3 rounded-lg" />
|
||||
|
|
@ -270,39 +333,26 @@ export default function CollectionsPage() {
|
|||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{groupedArray.map(({ supplier, items: supplierItems }, groupIdx) => (
|
||||
<div key={supplier!.id} className="animate-fade-in" style={{ animationDelay: `${groupIdx * 0.05}s` }}>
|
||||
{/* Supplier header */}
|
||||
<div className="flex items-center gap-2.5 mb-3 px-1">
|
||||
<div className="w-8 h-8 bg-orange-100 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<MapPin size={14} className="text-orange-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="font-semibold text-sm text-gray-900 truncate">{supplier!.suppliername}</h2>
|
||||
{supplier!.supplieraddress && (
|
||||
<p className="text-[11px] text-gray-400 truncate">{supplier!.supplieraddress}</p>
|
||||
)}
|
||||
</div>
|
||||
<span className="bg-orange-500 text-white text-[10px] px-2 py-0.5 rounded-full font-bold">
|
||||
{supplierItems.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{groupedArray.map(({ supplier, items: supplierItems }, groupIdx) => {
|
||||
const color = SUPPLIER_COLORS[groupIdx % SUPPLIER_COLORS.length];
|
||||
const isExpanded = expandedSuppliers.has(supplier!.id);
|
||||
|
||||
{/* Items */}
|
||||
<div className="space-y-2.5">
|
||||
{supplierItems.map((item) => (
|
||||
<CollectionItemCard
|
||||
key={item.id}
|
||||
item={item}
|
||||
onCollect={(i) => { setSelectedItem(i); setShowCollectModal(true); }}
|
||||
onProblem={(i) => { setSelectedItem(i); setShowProblemModal(true); }}
|
||||
onNavigate={handleNavigate}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
return (
|
||||
<SupplierGroup
|
||||
key={supplier!.id}
|
||||
supplier={supplier!}
|
||||
items={supplierItems}
|
||||
color={color}
|
||||
isExpanded={isExpanded}
|
||||
viewMode={viewMode}
|
||||
onToggle={() => toggleSupplier(supplier!.id)}
|
||||
onCollect={(item) => { setSelectedItem(item); setShowCollectModal(true); }}
|
||||
onProblem={(item) => { setSelectedItem(item); setShowProblemModal(true); }}
|
||||
onNavigate={handleNavigate}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
|
@ -314,7 +364,6 @@ export default function CollectionsPage() {
|
|||
onClose={() => { setShowCollectModal(false); setSelectedItem(null); }}
|
||||
onConfirm={handleCollect}
|
||||
/>
|
||||
|
||||
<ProblemModal
|
||||
item={selectedItem}
|
||||
isOpen={showProblemModal}
|
||||
|
|
@ -326,7 +375,7 @@ export default function CollectionsPage() {
|
|||
{toast && (
|
||||
<div className="fixed top-4 left-4 right-4 z-[70] animate-slide-down safe-top">
|
||||
<div className={cn(
|
||||
'px-4 py-3 rounded-2xl shadow-lg text-sm font-medium text-white text-center press-feedback-sm',
|
||||
'px-4 py-3 rounded-2xl shadow-lg text-sm font-medium text-white text-center',
|
||||
toast.type === 'success' && 'bg-green-500',
|
||||
toast.type === 'error' && 'bg-red-500',
|
||||
toast.type === 'warning' && 'bg-amber-500'
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import {
|
|||
|
||||
interface CollectionItemProps {
|
||||
item: OrderItem;
|
||||
supplierColor?: { border: string; dot: string; light: string; text: string; ring: string };
|
||||
compact?: boolean;
|
||||
onCollect: (item: OrderItem) => void;
|
||||
onProblem: (item: OrderItem) => void;
|
||||
onNavigate: (lat: number, lng: number, name: string) => void;
|
||||
|
|
@ -85,6 +87,8 @@ function getItemType(item: OrderItem): {
|
|||
|
||||
export default function CollectionItemCard({
|
||||
item,
|
||||
supplierColor,
|
||||
compact,
|
||||
onCollect,
|
||||
onProblem,
|
||||
onNavigate,
|
||||
|
|
@ -97,13 +101,16 @@ export default function CollectionItemCard({
|
|||
const hasCoords = coords !== null;
|
||||
const description = item.description || 'Unknown item';
|
||||
const expectedQty = item.order_qty || item.quantity || 0;
|
||||
const colorAccent = supplierColor?.border || 'border-l-orange-400';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'bg-white rounded-2xl card-shadow card-press overflow-hidden',
|
||||
(item.item_status === 'problem' || item.problem_flag) && 'ring-l-4 ring-red-400',
|
||||
item.item_status === 'assigned' && 'border-l-[3px] border-orange-400'
|
||||
compact
|
||||
? 'bg-gray-50 rounded-xl overflow-hidden'
|
||||
: 'bg-white rounded-2xl card-shadow card-press overflow-hidden',
|
||||
(item.item_status === 'problem' || item.problem_flag) && 'ring-1 ring-red-400',
|
||||
item.item_status === 'assigned' && !compact && `border-l-[3px] ${colorAccent}`
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
|
|
|
|||
210
components/SupplierGroup.tsx
Normal file
210
components/SupplierGroup.tsx
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
'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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue