diff --git a/src/App.tsx b/src/App.tsx index 7ac9dec..97da899 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,8 @@ import { AppSettings, TripDebrief, CustomBag, - CustomArchetype + CustomArchetype, + getActiveTripBags } from './types'; import { StorageClient } from './utils/storage'; import { sound } from './utils/audio'; @@ -111,6 +112,76 @@ export function App() { }, []); const activeTrip = trips.find((t) => t.id === activeTripId) || null; + const activeTripBags: CustomBag[] = activeTrip + ? getActiveTripBags(activeTrip, settings.customBags) + : settings.customBags; + + // Keep quick-add bag dropdown pointed at a bag that is actually on this trip + useEffect(() => { + if (activeTripBags.length > 0 && !activeTripBags.some((b) => b.id === newItemBag)) { + const preferred = + activeTripBags.find((b) => b.id === 'checked') || + activeTripBags.find((b) => b.id === 'carry_on') || + activeTripBags[0]; + if (preferred) setNewItemBag(preferred.id); + } + }, [activeTripId, activeTrip?.activeBags, settings.customBags]); + + const handleToggleTripBag = async (bagId: BagType) => { + if (!activeTrip) return; + const currentIds = activeTripBags.map((b) => b.id); + const isCurrentlyActive = currentIds.includes(bagId); + + if (isCurrentlyActive && currentIds.length <= 1) { + showToast('At least one bag must remain active on the trip!'); + return; + } + + const nextIds = isCurrentlyActive + ? currentIds.filter((id) => id !== bagId) + : [...currentIds, bagId]; + + const fallbackBag: BagType = nextIds.includes('checked') + ? 'checked' + : nextIds.includes('carry_on') + ? 'carry_on' + : nextIds.find((id) => id !== 'worn') || nextIds[0]; + + let movedCount = 0; + const updatedItems = activeTrip.items.map((item) => { + if (!nextIds.includes(item.bag)) { + movedCount++; + return { ...item, bag: fallbackBag }; + } + return item; + }); + + const derivedConfig = nextIds.includes('checked') + ? 'checked_bag' + : nextIds.includes('carry_on') + ? 'carry_on_only' + : 'backpack_only'; + + const updatedTrip: Trip = { + ...activeTrip, + activeBags: nextIds, + luggageConfig: derivedConfig, + items: updatedItems, + }; + + if (selectedBag === bagId && isCurrentlyActive) { + setSelectedBag('all'); + } + + await StorageClient.saveTrip(updatedTrip); + setTrips((prev) => prev.map((t) => (t.id === activeTrip.id ? updatedTrip : t))); + + if (movedCount > 0) { + const targetLabel = + settings.customBags.find((b) => b.id === fallbackBag)?.label || fallbackBag; + showToast(`Moved ${movedCount} item(s) into ${targetLabel}`); + } + }; const handleSelectTrip = (id: string) => { setActiveTripId(id); @@ -235,7 +306,7 @@ export function App() { text += `Destination: ${activeTrip.destination} • ${activeTrip.durationDays}D / ${activeTrip.durationNights}N • Archetype: ${activeTrip.tripType.toUpperCase()}\n`; text += `==========================================================\n\n`; - for (const bag of settings.customBags) { + for (const bag of activeTripBags) { const bagItems = activeTrip.items.filter((i) => i.bag === bag.id); if (bagItems.length > 0) { text += `[${bag.tagCode}] ${bag.label} (${bag.sub}):\n`; @@ -352,7 +423,9 @@ export function App() { selectedBag={selectedBag} onSelectBag={setSelectedBag} luggageConfig={activeTrip.luggageConfig} + activeTripBags={activeTripBags} customBags={settings.customBags} + onToggleTripBag={handleToggleTripBag} onEditBags={() => { setSettingsTab('bags'); setIsSettingsOpen(true); @@ -393,7 +466,7 @@ export function App() { onChange={(e) => setNewItemBag(e.target.value)} className="bg-white border-2 border-ink px-2 py-2 font-mono text-xs font-bold shadow-xs cursor-pointer" > - {settings.customBags.map((b) => ( + {activeTripBags.map((b) => ( @@ -613,7 +686,7 @@ export function App() { key={item.id} item={item} compact={compactView} - availableBags={settings.customBags} + availableBags={activeTripBags} onTogglePacked={handleTogglePacked} onUpdateQuantity={handleUpdateQuantity} onUpdateBag={handleUpdateBag} @@ -664,6 +737,11 @@ export function App() { onClose={() => setIsTripModalOpen(false)} onSaveTrip={handleSaveTrip} memory={memory} + customBags={settings.customBags} + onEditBags={() => { + setSettingsTab('bags'); + setIsSettingsOpen(true); + }} customArchetypes={settings.customArchetypes} onEditArchetypes={() => { setSettingsTab('archetypes'); diff --git a/src/components/BagOverview.tsx b/src/components/BagOverview.tsx index 93994f4..0254eb7 100644 --- a/src/components/BagOverview.tsx +++ b/src/components/BagOverview.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { BagType, PackingItem, LuggageConfig, CustomBag } from '../types'; import { sound } from '../utils/audio'; import { @@ -11,7 +11,9 @@ import { Settings2, Camera, Compass, - Tag + Tag, + Check, + Luggage } from 'lucide-react'; interface BagOverviewProps { @@ -19,7 +21,9 @@ interface BagOverviewProps { selectedBag: BagType | 'all'; onSelectBag: (bag: BagType | 'all') => void; luggageConfig: LuggageConfig; + activeTripBags: CustomBag[]; customBags: CustomBag[]; + onToggleTripBag: (bagId: BagType) => void; onEditBags: () => void; } @@ -46,35 +50,48 @@ export const BagOverview: React.FC = ({ items, selectedBag, onSelectBag, - luggageConfig, + activeTripBags, customBags, + onToggleTripBag, onEditBags, }) => { - // Filter bags based on luggage mode if it's a default bag - const visibleBags = customBags.filter((b) => { - if (b.id === 'carry_on' && luggageConfig === 'backpack_only') return false; - if (b.id === 'checked' && luggageConfig !== 'checked_bag') return false; - return true; - }); + const [showTripBagPicker, setShowTripBagPicker] = useState(false); + const visibleBags = activeTripBags; return (
-
+
- LUGGAGE ALLOCATION // "WHAT GOES IN WHAT BAG" + TRIP BAGS ({visibleBags.length}/{customBags.length}) +
@@ -91,10 +108,53 @@ export const BagOverview: React.FC = ({ }`} > - VIEW ALL BAGS ({items.length}) + VIEW ALL ({items.length})
+ {/* Inline Trip Bag Loadout Switcher */} + {showTripBagPicker && ( +
+
+ + WHICH BAGS ARE GOING ON THIS TRIP? (UNCHECKED BAGS AUTOMATICALLY MOVE ITEMS TO YOUR MAIN BAG) + +
+
+ {customBags.map((bag) => { + const isBringing = visibleBags.some((b) => b.id === bag.id); + return ( + + ); + })} +
+
+ )} +
{visibleBags.map((b) => { const bagItems = items.filter((i) => i.bag === b.id); diff --git a/src/components/TripModal.tsx b/src/components/TripModal.tsx index a0e51e0..fb11a87 100644 --- a/src/components/TripModal.tsx +++ b/src/components/TripModal.tsx @@ -1,5 +1,17 @@ import React, { useState, useEffect } from 'react'; -import { TripType, Climate, LuggageConfig, Trip, PackMemory, CustomArchetype, PackingItem } from '../types'; +import { + TripType, + Climate, + LuggageConfig, + Trip, + PackMemory, + CustomArchetype, + CustomBag, + BagType, + PackingItem, + getRecommendedBagIdsForTrip, + getActiveTripBags, +} from '../types'; import { generateAirportCode, generatePackingList } from '../utils/generator'; import { sound } from '../utils/audio'; import { @@ -22,6 +34,8 @@ interface TripModalProps { onClose: () => void; onSaveTrip: (trip: Trip) => void; memory: PackMemory; + customBags: CustomBag[]; + onEditBags: () => void; customArchetypes: CustomArchetype[]; onEditArchetypes: () => void; trips: Trip[]; @@ -35,12 +49,6 @@ const CLIMATES: { id: Climate; label: string; icon: string }[] = [ { id: 'rainy', label: 'Rainy / Variable', icon: '🌧️' }, ]; -const LUGGAGE_MODES: { id: LuggageConfig; label: string; desc: string }[] = [ - { id: 'carry_on_only', label: 'Carry-On Only', desc: 'Personal item + overhead roller (TSA 3-1-1 liquids rule enforced)' }, - { id: 'checked_bag', label: 'Checked Bag Included', desc: 'Cargo hold: full liquids allowed, trekking poles, heavy winter boots' }, - { id: 'backpack_only', label: 'One Backpack Only', desc: 'Minimalist 25-35L pack for ultra-mobile travel' }, -]; - const ACTIVITIES = [ { id: 'formal', label: 'Formal / Fine Dining', icon: '👔' }, { id: 'swimming', label: 'Swimming / Water', icon: '🏊' }, @@ -55,6 +63,8 @@ export const TripModal: React.FC = ({ onClose, onSaveTrip, memory, + customBags, + onEditBags, customArchetypes, onEditArchetypes, trips, @@ -73,12 +83,25 @@ export const TripModal: React.FC = ({ const [destination, setDestination] = useState(''); const [title, setTitle] = useState(''); const [tripType, setTripType] = useState(customArchetypes[0]?.id || 'international_urban'); - const [durationDays, setDurationDays] = useState(7); + const [durationDays, setDurationDays] = useState(4); const [climate, setClimate] = useState('mild'); - const [luggageConfig, setLuggageConfig] = useState('carry_on_only'); + const [selectedBags, setSelectedBags] = useState(() => + getRecommendedBagIdsForTrip(4, customArchetypes[0]?.id || 'international_urban', customBags) + ); + const [bagsManuallyCustomized, setBagsManuallyCustomized] = useState(false); const [hasLaundry, setHasLaundry] = useState(true); const [selectedActivities, setSelectedActivities] = useState(['remote_work']); + // Automatically recommend bags when duration or archetype changes (unless manually overridden) + const applyDurationAndArchetype = (newDays: number, newArchetype: TripType, forceAuto = false) => { + setDurationDays(newDays); + setTripType(newArchetype); + if (!bagsManuallyCustomized || forceAuto) { + setSelectedBags(getRecommendedBagIdsForTrip(newDays, newArchetype, customBags)); + if (forceAuto) setBagsManuallyCustomized(false); + } + }; + // Handle pre-populating when cloning from a trip useEffect(() => { if (initialCloneTripId) { @@ -91,7 +114,8 @@ export const TripModal: React.FC = ({ setTripType(src.tripType); setDurationDays(src.durationDays); setClimate(src.climate); - setLuggageConfig(src.luggageConfig); + setSelectedBags(getActiveTripBags(src, customBags).map((b) => b.id)); + setBagsManuallyCustomized(false); setHasLaundry(src.hasLaundry); setSelectedActivities(src.activities || []); } @@ -108,12 +132,43 @@ export const TripModal: React.FC = ({ setTripType(src.tripType); setDurationDays(src.durationDays); setClimate(src.climate); - setLuggageConfig(src.luggageConfig); + setSelectedBags(getActiveTripBags(src, customBags).map((b) => b.id)); + setBagsManuallyCustomized(false); setHasLaundry(src.hasLaundry); setSelectedActivities(src.activities || []); } }; + const toggleBagForTrip = (bagId: BagType) => { + sound.playClick(); + setBagsManuallyCustomized(true); + setSelectedBags((prev) => { + if (prev.includes(bagId)) { + // Don't allow deselecting the very last bag + if (prev.length <= 1) return prev; + return prev.filter((id) => id !== bagId); + } + return [...prev, bagId]; + }); + }; + + const applyBagPreset = (preset: 'short_carryon' | 'long_checked' | 'both' | 'minimal') => { + sound.playClick(); + setBagsManuallyCustomized(true); + const hasBag = (id: string) => customBags.some((b) => b.id === id); + const next: BagType[] = []; + if (hasBag('personal_item')) next.push('personal_item'); + if (preset === 'short_carryon' && hasBag('carry_on')) next.push('carry_on'); + if (preset === 'long_checked' && hasBag('checked')) next.push('checked'); + if (preset === 'both') { + if (hasBag('carry_on')) next.push('carry_on'); + if (hasBag('checked')) next.push('checked'); + } + if (hasBag('worn')) next.push('worn'); + if (next.length === 0 && customBags[0]) next.push(customBags[0].id); + setSelectedBags(next); + }; + const destinationAirportCode = generateAirportCode(destination || 'DEST'); const routeCode = `DEP ➔ ${destinationAirportCode}`; @@ -133,15 +188,33 @@ export const TripModal: React.FC = ({ const tripTitle = title.trim() || `${destination.trim() || 'Voyage'} Expedition`; const tripId = `trip_${Date.now()}`; + const activeBags: BagType[] = + selectedBags.length > 0 + ? selectedBags + : getRecommendedBagIdsForTrip(durationDays, tripType, customBags); + + const derivedLuggageConfig: LuggageConfig = activeBags.includes('checked') + ? 'checked_bag' + : activeBags.includes('carry_on') + ? 'carry_on_only' + : 'backpack_only'; + + const mainBag: BagType = activeBags.includes('checked') + ? 'checked' + : activeBags.includes('carry_on') + ? 'carry_on' + : activeBags.find((b) => b !== 'worn') || activeBags[0] || 'personal_item'; + let items: PackingItem[] = []; if (creationMode === 'clone') { const sourceTrip = trips.find((t) => t.id === selectedCloneId); if (sourceTrip) { - // Clone items with reset packed checkboxes and new IDs + // Clone items and automatically re-route any item whose bag isn't coming on this trip! items = sourceTrip.items.map((item) => ({ ...item, id: `item_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`, + bag: activeBags.includes(item.bag) ? item.bag : mainBag, packed: false, })); @@ -151,7 +224,7 @@ export const TripModal: React.FC = ({ // Remove deadweight items items = items.filter((item) => !excludedLower.includes(item.name.toLowerCase())); - // Inject wishlist items + // Inject wishlist items into the trip's main luggage bag for (const wishlistName of sourceTrip.debrief.includedItemNames) { if (!items.some((i) => i.name.toLowerCase() === wishlistName.toLowerCase())) { items.push({ @@ -159,7 +232,7 @@ export const TripModal: React.FC = ({ name: wishlistName, category: 'gear', quantity: 1, - bag: 'carry_on', + bag: mainBag, packed: false, source: 'learned', learnedReason: `Included from past debrief of ${sourceTrip.title}`, @@ -179,7 +252,8 @@ export const TripModal: React.FC = ({ durationDays, durationNights: Math.max(1, durationDays - 1), climate, - luggageConfig, + luggageConfig: derivedLuggageConfig, + activeBags, hasLaundry, activities: selectedActivities, memory, @@ -195,7 +269,8 @@ export const TripModal: React.FC = ({ durationDays, durationNights: Math.max(1, durationDays - 1), climate, - luggageConfig, + luggageConfig: derivedLuggageConfig, + activeBags, hasLaundry, activities: selectedActivities, items, @@ -384,7 +459,7 @@ export const TripModal: React.FC = ({ key={t.id} onClick={() => { sound.playClick(); - setTripType(t.id); + applyDurationAndArchetype(durationDays, t.id); }} className={`text-left p-2.5 border-2 border-ink transition-all flex flex-col ${ tripType === t.id @@ -419,17 +494,17 @@ export const TripModal: React.FC = ({ min="1" max="30" value={durationDays} - onChange={(e) => setDurationDays(parseInt(e.target.value))} + onChange={(e) => applyDurationAndArchetype(parseInt(e.target.value), tripType)} className="w-full accent-gate-orange cursor-pointer" /> -
- {[3, 5, 7, 10, 14].map((d) => ( +
+ {[2, 4, 5, 7, 10, 14].map((d) => (
- {/* Luggage Mode */} -
- -
- {LUGGAGE_MODES.map((m) => ( + {/* Trip-Specific Bag Selection ("Which Bags Go on This Trip") */} +
+
+
+ + + {!bagsManuallyCustomized + ? durationDays <= 2 + ? `Auto-matched for ${durationDays}D overnight trip: Personal Item only` + : durationDays <= 5 + ? `Auto-matched for ${durationDays}D shorter trip: Carry-On (no Checked Bag)` + : `Auto-matched for ${durationDays}D longer trip: Checked Bag (no Carry-On)` + : 'Custom bag selection for this voyage'} + +
+ +
+ {bagsManuallyCustomized && ( + + )} - ))} +
+
+ + {/* Quick Presets */} +
+ + + + +
+ + {/* Individual Bag Toggle Cards */} +
+ {customBags.map((bag) => { + const isSelected = selectedBags.includes(bag.id); + return ( + + ); + })}
diff --git a/src/types/index.ts b/src/types/index.ts index 17b2dc3..63f9154 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -42,6 +42,7 @@ export interface Trip { durationNights: number; climate: Climate; luggageConfig: LuggageConfig; + activeBags?: BagType[]; // Specific bag IDs brought on this trip hasLaundry: boolean; activities: string[]; // 'formal', 'swimming', 'hiking', 'gym', 'remote_work', 'photography' items: PackingItem[]; @@ -49,6 +50,80 @@ export interface Trip { debrief?: TripDebrief; } +/** + * Recommends which bags go on a trip based on trip duration and archetype: + * - 1-2 days: Personal Item + Worn (unless business/alpine) + * - 3-5 days (shorter trips): Personal Item + Carry-On + Worn (no checked bag) + * - 6+ days (longer trips): Personal Item + Checked Bag + Worn + */ +export function getRecommendedBagIdsForTrip( + durationDays: number, + tripType: TripType, + allBags: CustomBag[] +): BagType[] { + const hasBag = (id: string) => allBags.some((b) => b.id === id); + const selected: BagType[] = []; + + if (hasBag('personal_item')) selected.push('personal_item'); + + if (durationDays <= 2 && tripType !== 'business' && tripType !== 'alpine_cold') { + // Ultra-short 1-2 day trip: Personal item only (+ worn) + } else if (durationDays <= 5 && tripType !== 'alpine_cold') { + // Shorter trip (3-5 days): Carry-On bag, no Checked bag + if (hasBag('carry_on')) selected.push('carry_on'); + } else { + // Longer trip (6+ days) or Alpine gear trip: Checked bag instead of Carry-On by default + if (hasBag('checked')) { + selected.push('checked'); + } else if (hasBag('carry_on')) { + selected.push('carry_on'); + } + } + + if (hasBag('worn')) selected.push('worn'); + + // Fallback if user deleted all default bags and only has custom ones + if (selected.length === 0 && allBags.length > 0) { + selected.push(allBags[0].id); + } + + return selected; +} + +/** + * Resolves the active CustomBag objects for a given trip. + */ +export function getActiveTripBags(trip: Trip, allBags: CustomBag[]): CustomBag[] { + if (trip.activeBags && trip.activeBags.length > 0) { + const filtered = allBags.filter((b) => trip.activeBags!.includes(b.id)); + if (filtered.length > 0) return filtered; + } + + // Fallback for legacy trips created before activeBags was stored + const legacyIds = new Set(); + if (trip.luggageConfig === 'backpack_only') { + legacyIds.add('personal_item'); + legacyIds.add('worn'); + } else if (trip.luggageConfig === 'carry_on_only') { + legacyIds.add('personal_item'); + legacyIds.add('carry_on'); + legacyIds.add('worn'); + } else if (trip.luggageConfig === 'checked_bag') { + legacyIds.add('personal_item'); + // If legacy trip has no items in carry_on, only include checked; otherwise include any bag with items + legacyIds.add('checked'); + legacyIds.add('worn'); + } + + // Also ensure any bag that currently holds items in this trip is visible + for (const item of trip.items || []) { + legacyIds.add(item.bag); + } + + const matched = allBags.filter((b) => legacyIds.has(b.id)); + return matched.length > 0 ? matched : allBags; +} + export interface MemoryExclusion { itemName: string; tripType: TripType; diff --git a/src/utils/generator.ts b/src/utils/generator.ts index bf6bf64..b9092a4 100644 --- a/src/utils/generator.ts +++ b/src/utils/generator.ts @@ -8,6 +8,7 @@ interface GenerationParams { durationNights: number; climate: Climate; luggageConfig: LuggageConfig; + activeBags?: BagType[]; hasLaundry: boolean; activities: string[]; memory?: PackMemory; @@ -56,11 +57,69 @@ export function generatePackingList(params: GenerationParams): PackingItem[] { durationDays, climate, luggageConfig, + activeBags, hasLaundry, activities, memory, } = params; + // Determine which bags are actually going on this trip + const tripBags: BagType[] = + activeBags && activeBags.length > 0 + ? activeBags + : luggageConfig === 'backpack_only' + ? ['personal_item', 'worn'] + : luggageConfig === 'checked_bag' + ? ['personal_item', 'checked', 'worn'] + : ['personal_item', 'carry_on', 'worn']; + + const activeSet = new Set(tripBags); + const hasChecked = activeSet.has('checked'); + const hasCarryOn = activeSet.has('carry_on'); + const hasPersonal = activeSet.has('personal_item'); + const hasWorn = activeSet.has('worn'); + + // Find any custom bag IDs the user selected for this trip + const customBagIds = tripBags.filter( + (id) => !['personal_item', 'carry_on', 'checked', 'worn'].includes(id) + ); + + // Fallback cabin/personal bag + const cabinBag: BagType = hasPersonal + ? 'personal_item' + : hasCarryOn + ? 'carry_on' + : customBagIds[0] || tripBags[0] || 'personal_item'; + + // Primary main luggage bag (Carry-On for shorter trips, Checked Bag for longer trips) + const mainLuggageBag: BagType = hasChecked + ? 'checked' + : hasCarryOn + ? 'carry_on' + : customBagIds[0] || cabinBag; + + // Secondary luggage bag (if user brings BOTH Carry-On and Checked on an extended trip) + const secondaryLuggageBag: BagType = + hasChecked && hasCarryOn ? 'carry_on' : mainLuggageBag; + + // Resolve any requested bag to a valid active bag on this trip + const resolveBag = (requested: BagType): BagType => { + if (requested === 'worn') { + return hasWorn ? 'worn' : mainLuggageBag; + } + if (requested === 'personal_item') { + return cabinBag; + } + if (requested === 'checked') { + return hasChecked ? 'checked' : mainLuggageBag; + } + if (requested === 'carry_on') { + // Route standard clothing/gear to mainLuggageBag (Checked on long trips, Carry-On on short trips) + return mainLuggageBag; + } + return activeSet.has(requested) ? requested : mainLuggageBag; + }; + const items: PackingItem[] = []; const addItem = ( @@ -93,7 +152,7 @@ export function generatePackingList(params: GenerationParams): PackingItem[] { name, category, quantity, - bag: luggageConfig === 'backpack_only' && bag === 'checked' ? 'carry_on' : bag, + bag: resolveBag(bag), packed: false, essential, notes, @@ -178,7 +237,7 @@ export function generatePackingList(params: GenerationParams): PackingItem[] { addItem('Casual shorts', 'clothing', Math.max(2, Math.min(durationDays, 4)), 'carry_on'); addItem('UV protection sunglasses', 'gear', 1, 'personal_item', true); addItem('Wide-brim sun hat or baseball cap', 'clothing', 1, 'personal_item'); - addItem('Reef-safe sunscreen SPF 50', 'toiletries', 1, luggageConfig === 'checked_bag' ? 'checked' : 'carry_on', true, luggageConfig === 'checked_bag' ? 'Full size in checked' : 'TSA 3.4oz limit'); + addItem('Reef-safe sunscreen SPF 50', 'toiletries', 1, hasChecked ? 'checked' : 'carry_on', true, hasChecked ? 'Full size in checked' : 'TSA 3.4oz limit'); } if (climate === 'rainy') { @@ -197,7 +256,7 @@ export function generatePackingList(params: GenerationParams): PackingItem[] { } if (activities.includes('formal') || tripType === 'business') { - addItem('Tailored blazer / suit jacket', 'clothing', 1, 'carry_on', true); + addItem('Tailored blazer / suit jacket', 'clothing', 1, secondaryLuggageBag, true); addItem('Dress button-down shirts', 'clothing', Math.min(durationDays, 3), 'carry_on', true); addItem('Dress belt', 'clothing', 1, 'carry_on'); addItem('Dress shoes / oxfords / heels', 'clothing', 1, 'carry_on', true); @@ -213,7 +272,7 @@ export function generatePackingList(params: GenerationParams): PackingItem[] { addItem('Headlamp with extra batteries', 'gear', 1, 'personal_item', true); addItem('Blister treatment & moleskin tape', 'wellness', 1, 'personal_item', true); addItem('Insect repellent spray / wipes', 'toiletries', 1, 'carry_on'); - if (luggageConfig === 'checked_bag') { + if (hasChecked) { addItem('Collapsible trekking poles', 'gear', 1, 'checked', false, 'TSA banned in carry-on!'); addItem('Pocket knife / multi-tool', 'gear', 1, 'checked', false, 'TSA strictly prohibited in cabin'); } @@ -226,12 +285,12 @@ export function generatePackingList(params: GenerationParams): PackingItem[] { } // 7. TOILETRIES (TSA 3-1-1 liquids rule enforced if carry-on) - const toiletryBag: BagType = luggageConfig === 'checked_bag' ? 'checked' : 'carry_on'; - const liquidNote = luggageConfig === 'checked_bag' ? 'Full size allowed' : 'Must be <= 3.4oz (100ml) in 1-quart transparent pouch'; + const toiletryBag: BagType = hasChecked ? 'checked' : 'carry_on'; + const liquidNote = hasChecked ? 'Full size allowed in Checked Bag' : 'Must be <= 3.4oz (100ml) in 1-quart transparent pouch'; - addItem('Toothbrush & travel toothpaste', 'toiletries', 1, 'carry_on', true, liquidNote); - addItem('Dental floss', 'toiletries', 1, 'carry_on'); - addItem('Deodorant', 'toiletries', 1, 'carry_on', true); + addItem('Toothbrush & travel toothpaste', 'toiletries', 1, secondaryLuggageBag, true, liquidNote); + addItem('Dental floss', 'toiletries', 1, secondaryLuggageBag); + addItem('Deodorant', 'toiletries', 1, secondaryLuggageBag, true); addItem('Face wash & moisturizer', 'toiletries', 1, toiletryBag, false, liquidNote); addItem('Shampoo & conditioner bar or travel bottles', 'toiletries', 1, toiletryBag, false, liquidNote); addItem('Razor & shave cream', 'toiletries', 1, toiletryBag);