Files
loadout/src/components/BagOverview.tsx
T

244 lines
8.9 KiB
TypeScript

import React, { useState } from 'react';
import { BagType, PackingItem, LuggageConfig, CustomBag } from '../types';
import { sound } from '../utils/audio';
import {
Backpack,
Briefcase,
Package,
UserCheck,
Layers,
CheckCircle2,
Settings2,
Camera,
Compass,
Tag,
Check,
Luggage
} from 'lucide-react';
interface BagOverviewProps {
items: PackingItem[];
selectedBag: BagType | 'all';
onSelectBag: (bag: BagType | 'all') => void;
luggageConfig: LuggageConfig;
activeTripBags: CustomBag[];
customBags: CustomBag[];
onToggleTripBag: (bagId: BagType) => void;
onEditBags: () => void;
}
function getBagIcon(iconKey?: string) {
switch (iconKey) {
case 'backpack':
return <Backpack size={20} strokeWidth={2.2} />;
case 'briefcase':
return <Briefcase size={20} strokeWidth={2.2} />;
case 'package':
return <Package size={20} strokeWidth={2.2} />;
case 'user':
return <UserCheck size={20} strokeWidth={2.2} />;
case 'camera':
return <Camera size={20} strokeWidth={2.2} />;
case 'compass':
return <Compass size={20} strokeWidth={2.2} />;
default:
return <Tag size={20} strokeWidth={2.2} />;
}
}
export const BagOverview: React.FC<BagOverviewProps> = ({
items,
selectedBag,
onSelectBag,
activeTripBags,
customBags,
onToggleTripBag,
onEditBags,
}) => {
const [showTripBagPicker, setShowTripBagPicker] = useState(false);
const visibleBags = activeTripBags;
return (
<div className="mb-6">
<div className="flex items-center justify-between mb-2 flex-wrap gap-2">
<div className="flex items-center gap-1.5 sm:gap-2 flex-wrap">
<span className="font-mono text-xs uppercase font-bold text-ink tracking-wider">
TRIP BAGS ({visibleBags.length}/{customBags.length})
</span>
<button
type="button"
onClick={() => {
sound.playClick();
setShowTripBagPicker(!showTripBagPicker);
}}
className={`font-mono text-[11px] font-bold px-2 py-0.5 border border-ink transition-colors flex items-center gap-1 shadow-xs ${
showTripBagPicker
? 'bg-ink text-canary'
: 'bg-canary/80 hover:bg-canary text-ink'
}`}
title="Select which bags are going on this specific trip"
>
<Luggage size={12} />
<span>SELECT TRIP BAGS</span>
</button>
<button
type="button"
onClick={() => {
sound.playClick();
onEditBags();
}}
className="font-mono text-[11px] font-bold px-2 py-0.5 border border-ink bg-white hover:bg-ticket-200 text-ink transition-colors flex items-center gap-1 shadow-xs"
title="Add, rename, or reconfigure your global bag pool"
>
<Settings2 size={12} />
<span>EDIT BAG POOL</span>
</button>
</div>
<button
type="button"
onClick={() => {
sound.playClick();
onSelectBag('all');
}}
className={`font-mono text-xs font-bold px-2.5 py-1 border border-ink transition-all flex items-center gap-1.5 ${
selectedBag === 'all'
? 'bg-ink text-ticket-100 shadow-card-hard-sm'
: 'bg-ticket-100 text-ink hover:bg-ticket-200'
}`}
>
<Layers size={13} />
<span>VIEW ALL ({items.length})</span>
</button>
</div>
{/* Inline Trip Bag Loadout Switcher */}
{showTripBagPicker && (
<div className="mb-3 p-3 bg-ticket-100 border-2 border-ink shadow-card-hard-sm animate-in fade-in duration-100">
<div className="flex items-center justify-between mb-2">
<span className="font-mono text-[11px] font-bold uppercase text-ink">
WHICH BAGS ARE GOING ON THIS TRIP? (UNCHECKED BAGS AUTOMATICALLY MOVE ITEMS TO YOUR MAIN BAG)
</span>
</div>
<div className="flex flex-wrap gap-2">
{customBags.map((bag) => {
const isBringing = visibleBags.some((b) => b.id === bag.id);
return (
<button
type="button"
key={bag.id}
onClick={() => {
sound.playClick();
onToggleTripBag(bag.id);
}}
className={`px-2.5 py-1.5 border-2 font-mono text-xs font-bold flex items-center gap-2 transition-all ${
isBringing
? 'bg-canary-light border-ink text-ink shadow-card-hard-sm'
: 'bg-white/60 border-ink/40 text-ink/50 hover:bg-white hover:text-ink'
}`}
>
<span
className={`w-4 h-4 border border-ink flex items-center justify-center ${
isBringing ? 'bg-ink text-canary' : 'bg-white'
}`}
>
{isBringing && <Check size={11} strokeWidth={3} />}
</span>
<span>{bag.label}</span>
<span className="text-[10px] bg-ink text-ticket-100 px-1 py-0.2">
{bag.tagCode}
</span>
</button>
);
})}
</div>
</div>
)}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2 sm:gap-3">
{visibleBags.map((b) => {
const bagItems = items.filter((i) => i.bag === b.id);
const bagPacked = bagItems.filter((i) => i.packed).length;
const bagTotal = bagItems.length;
const pct = bagTotal > 0 ? Math.round((bagPacked / bagTotal) * 100) : 0;
const isSelected = selectedBag === b.id;
const isFull = bagTotal > 0 && bagPacked === bagTotal;
return (
<button
type="button"
key={b.id}
onClick={() => {
sound.playClick();
onSelectBag(isSelected ? 'all' : b.id);
}}
className={`relative text-left p-2.5 sm:p-3.5 border-2 border-ink transition-all flex flex-col justify-between ${
isSelected
? 'bg-canary-light shadow-card-hard border-ink translate-x-0.5 translate-y-0.5'
: 'bg-ticket-50 hover:bg-ticket-100 shadow-card-hard-sm'
}`}
>
{/* Perforation holes on card edges */}
<div className="ticket-notch-left hidden sm:block" />
<div className="ticket-notch-right hidden sm:block" />
<div>
{/* Top Barcode Header */}
<div className="flex items-center justify-between border-b border-ink/20 pb-1.5 sm:pb-2 mb-1.5 sm:mb-2 font-mono text-[10px] gap-1">
<span className="bg-ink text-ticket-100 px-1 py-0.5 font-bold uppercase shrink-0">
{b.tagCode}
</span>
<span className="text-ink-muted tracking-tight truncate">
{b.sub}
</span>
</div>
{/* Title & Icon */}
<div className="flex items-center justify-between gap-1.5 mb-1">
<div className="flex items-center gap-1.5 sm:gap-2 text-ink min-w-0">
<span className="text-ink shrink-0 hidden sm:inline">{getBagIcon(b.iconKey)}</span>
<span className="font-display text-base sm:text-xl tracking-wide uppercase leading-tight truncate">
{b.label}
</span>
</div>
{isFull && (
<CheckCircle2 size={16} className="text-stamp-green shrink-0" />
)}
</div>
{/* TSA / Bag Advisory Tip */}
{b.tsaTip && (
<p className="font-sans text-[11px] text-ink-muted leading-tight mb-3 line-clamp-2 hidden sm:block">
{b.tsaTip}
</p>
)}
</div>
{/* Meter and Stowed Count */}
<div>
<div className="flex items-center justify-between font-mono text-xs font-bold mb-1">
<span className="text-ink">
{bagPacked}/{bagTotal} ITEMS
</span>
<span className={isFull ? 'text-stamp-green' : 'text-ink-muted'}>
{pct}%
</span>
</div>
<div className="w-full bg-ticket-200 border border-ink h-2.5 overflow-hidden">
<div
className={`h-full transition-all duration-200 ${
isFull ? 'bg-stamp-green' : 'bg-gate-orange'
}`}
style={{ width: `${pct}%` }}
/>
</div>
</div>
</button>
);
})}
</div>
</div>
);
};