import { useState, useEffect, useRef, useCallback } from 'react' import { X, Plus } from 'lucide-react' import type { PackingItem, PackingBag } from '../../types' import type { TripMember } from './usePackingListPanel' import { PopoverItem } from './PackingPopover' import { POPOVER, POPOVER_CAPTION, useDismissOnOutside } from './packingPopoverStyles' interface BagCardProps { bag: PackingBag; bagItems: PackingItem[]; totalWeight: number; pct: number; tripId: number tripMembers: TripMember[]; canEdit: boolean; onDelete: () => void onUpdate: (bagId: number, data: Record) => void onSetMembers: (bagId: number, userIds: number[]) => void; t: any; compact?: boolean } export function BagCard({ bag, bagItems, totalWeight, pct, tripId, tripMembers, canEdit, onDelete, onUpdate, onSetMembers, t, compact }: BagCardProps) { const [editingName, setEditingName] = useState(false) const [nameVal, setNameVal] = useState(bag.name) const [showUserPicker, setShowUserPicker] = useState(false) const membersRef = useRef(null) const closePicker = useCallback(() => setShowUserPicker(false), []) useDismissOnOutside(membersRef, showUserPicker, closePicker) useEffect(() => setNameVal(bag.name), [bag.name]) const saveName = () => { if (nameVal.trim() && nameVal.trim() !== bag.name) onUpdate(bag.id, { name: nameVal.trim() }) setEditingName(false) } // Limits are entered in kg — that is how airlines state them — and stored in grams. const limitToInput = (grams?: number | null) => (grams ? String(grams / 1000) : '') const [editingLimit, setEditingLimit] = useState(false) const [limitVal, setLimitVal] = useState(limitToInput(bag.weight_limit_grams)) useEffect(() => setLimitVal(limitToInput(bag.weight_limit_grams)), [bag.weight_limit_grams]) const saveLimit = () => { setEditingLimit(false) const raw = limitVal.trim().replace(',', '.') if (raw === '') { // Clearing the field removes the limit and puts the bar back on relative scaling. if (bag.weight_limit_grams != null) onUpdate(bag.id, { weight_limit_grams: null }) return } const kg = Number(raw) // Anything unparseable or negative leaves the stored limit alone rather than wiping it. if (!Number.isFinite(kg) || kg <= 0) { setLimitVal(limitToInput(bag.weight_limit_grams)); return } const grams = Math.round(kg * 1000) if (grams !== bag.weight_limit_grams) onUpdate(bag.id, { weight_limit_grams: grams }) } const memberIds = (bag.members || []).map(m => m.user_id) const toggleMember = (userId: number) => { const next = memberIds.includes(userId) ? memberIds.filter(id => id !== userId) : [...memberIds, userId] onSetMembers(bag.id, next) } const sz = compact ? { dot: 10, name: 12, weight: 11, bar: 6, count: 10, gap: 6, mb: 14, icon: 11, avatar: 18 } : { dot: 12, name: 14, weight: 13, bar: 8, count: 11, gap: 8, mb: 16, icon: 13, avatar: 22 } // Three lines, each with one job: who the bag is (name, who carries it, delete), // how full it is, and the numbers under the bar (items left, weight and limit right). return (
{editingName && canEdit ? ( setNameVal(e.target.value)} onBlur={saveName} onKeyDown={e => { if (e.key === 'Enter') saveName(); if (e.key === 'Escape') { setEditingName(false); setNameVal(bag.name) } }} style={{ flex: 1, fontSize: sz.name, fontWeight: 600, padding: '1px 4px', borderRadius: 4, border: '1px solid var(--border-primary)', outline: 'none', fontFamily: 'inherit', color: 'var(--text-primary)', background: 'transparent' }} /> ) : ( )} {/* Members */}
{(bag.members || []).map(m => { const face = m.avatar ? ( {m.username} ) : ( {m.username[0].toUpperCase()} ) if (!canEdit) return {face} return ( ) })} {canEdit && ( )} {showUserPicker && (
{t('packing.assignMembers')}
{tripMembers.map(m => ( toggleMember(m.id)} icon={m.avatar ? ( ) : ( {m.username[0].toUpperCase()} )} /> ))} {tripMembers.length === 0 &&
{t('packing.noMembers')}
}
)}
{canEdit && }
{bagItems.length} {t('admin.packingTemplates.items')} {totalWeight >= 1000 ? `${(totalWeight / 1000).toFixed(1)} kg` : `${totalWeight} g`} {editingLimit && canEdit ? ( <> / setLimitVal(e.target.value)} onBlur={saveLimit} onKeyDown={e => { if (e.key === 'Enter') saveLimit(); if (e.key === 'Escape') { setLimitVal(limitToInput(bag.weight_limit_grams)); setEditingLimit(false) } }} inputMode="decimal" aria-label={t('packing.bagLimit')} placeholder={t('packing.bagLimit')} style={{ width: 42, fontSize: sz.weight, padding: '1px 3px', borderRadius: 4, border: '1px solid var(--border-primary)', outline: 'none', fontFamily: 'inherit', color: 'var(--text-primary)', background: 'transparent' }} /> kg ) : bag.weight_limit_grams ? ( ) : canEdit ? ( ) : null}
) }