Files
loadout/src/components/BoardingHeader.tsx
T

224 lines
8.5 KiB
TypeScript

import React from 'react';
import { Trip } from '../types';
import { sound } from '../utils/audio';
import {
Plane,
Luggage,
Volume2,
VolumeX,
BrainCircuit,
Settings as SettingsIcon,
Plus,
RotateCcw,
Sparkles
} from 'lucide-react';
interface BoardingHeaderProps {
activeTrip: Trip | null;
trips: Trip[];
onSelectTrip: (tripId: string) => void;
onNewTrip: () => void;
onOpenDebrief: () => void;
onOpenMemory: () => void;
onOpenSettings: () => void;
soundEnabled: boolean;
onToggleSound: () => void;
}
export const BoardingHeader: React.FC<BoardingHeaderProps> = ({
activeTrip,
trips,
onSelectTrip,
onNewTrip,
onOpenDebrief,
onOpenMemory,
onOpenSettings,
soundEnabled,
onToggleSound,
}) => {
const totalItems = activeTrip ? activeTrip.items.length : 0;
const packedItems = activeTrip ? activeTrip.items.filter((i) => i.packed).length : 0;
const percentPacked = totalItems > 0 ? Math.round((packedItems / totalItems) * 100) : 0;
return (
<header className="border-b-2 border-ink bg-ticket-100 shadow-card-hard">
{/* Top Telemetry Flight Ticker */}
<div className="bg-ink text-ticket-100 px-3 py-1.5 flex items-center justify-between text-xs font-mono tracking-wider border-b border-ink-light">
<div className="flex items-center gap-2">
<span className="inline-block w-2 h-2 rounded-full bg-canary animate-pulse" />
<span className="font-bold uppercase tracking-widest text-canary">LOADOUT // MANIFEST</span>
<span className="hidden sm:inline text-ink-faint">|</span>
<span className="hidden sm:inline text-ticket-200">
{activeTrip ? `REF: ${activeTrip.routeCode}` : 'DISPATCH: READY'}
</span>
</div>
<div className="flex items-center gap-3">
<button
onClick={() => {
onToggleSound();
sound.playClick();
}}
title={soundEnabled ? 'Mute sound effects' : 'Enable tactile sounds'}
className="flex items-center gap-1 hover:text-canary transition-colors"
>
{soundEnabled ? <Volume2 size={13} /> : <VolumeX size={13} />}
<span className="hidden md:inline">{soundEnabled ? 'AUDIO: ON' : 'AUDIO: OFF'}</span>
</button>
<span className="text-ink-faint">|</span>
<button
onClick={() => {
sound.playClick();
onOpenMemory();
}}
className="flex items-center gap-1 text-canary hover:underline"
title="View accumulated packing wisdom & debrief history"
>
<BrainCircuit size={13} />
<span className="font-bold">FLIGHT LOG & MEMORY</span>
</button>
<span className="text-ink-faint">|</span>
<button
onClick={() => {
sound.playClick();
onOpenSettings();
}}
className="hover:text-canary transition-colors"
title="App settings & AI dispatch"
>
<SettingsIcon size={13} />
</button>
</div>
</div>
{/* Main Boarding Pass Header Card */}
<div className="px-3 sm:px-6 py-4 flex flex-col md:flex-row md:items-center justify-between gap-4">
{/* Left: Voyage & Route Code */}
<div className="flex items-start sm:items-center gap-3">
<div className="w-12 h-12 bg-canary border-2 border-ink flex items-center justify-center shadow-card-hard-sm shrink-0">
<Plane className="text-ink transform -rotate-45" size={26} strokeWidth={2.5} />
</div>
<div>
<div className="flex items-center gap-2 flex-wrap">
<h1 className="font-display text-3xl sm:text-4xl tracking-wide uppercase leading-none text-ink">
{activeTrip ? activeTrip.title : 'NO VOYAGE SELECTED'}
</h1>
{activeTrip?.debrief && (
<span className="stamp-badge stamp-badge-packed text-[11px] py-0.5">
DEBRIEFED
</span>
)}
</div>
<div className="flex items-center gap-2 mt-1 font-mono text-xs text-ink-muted flex-wrap">
{activeTrip ? (
<>
<span className="bg-ticket-200 border border-ticket-border px-1.5 py-0.5 font-bold text-ink">
{activeTrip.routeCode}
</span>
<span>•</span>
<span>{activeTrip.destination}</span>
<span>•</span>
<span>
{activeTrip.durationDays}D / {activeTrip.durationNights}N
</span>
<span>•</span>
<span className="uppercase text-gate-orange font-bold">
{activeTrip.tripType.replace('_', ' ')}
</span>
{activeTrip.hasLaundry && (
<span className="bg-stamp-green-light text-stamp-green border border-stamp-green/30 px-1 py-0.5 text-[10px] uppercase font-bold">
LAUNDRY ACCESS
</span>
)}
</>
) : (
<span>CREATE A NEW TRAVEL MANIFEST TO BEGIN PACKING</span>
)}
</div>
</div>
</div>
{/* Center / Right: Voyage switcher and Actions */}
<div className="flex items-center gap-2 sm:gap-3 flex-wrap">
{trips.length > 0 && (
<select
value={activeTrip?.id || ''}
onChange={(e) => {
sound.playClick();
onSelectTrip(e.target.value);
}}
className="bg-white border-2 border-ink font-mono text-xs px-2.5 py-2 shadow-card-hard-sm focus:outline-none focus:ring-2 focus:ring-canary"
>
{trips.map((t) => (
<option key={t.id} value={t.id}>
{t.routeCode} — {t.title} ({t.durationDays}D)
</option>
))}
</select>
)}
<button
onClick={() => {
sound.playClick();
onNewTrip();
}}
className="flex items-center gap-1.5 bg-canary hover:bg-canary-hover text-ink font-grotesk font-bold text-xs uppercase px-3.5 py-2 border-2 border-ink shadow-card-hard-sm active:translate-x-0.5 active:translate-y-0.5 transition-all"
>
<Plus size={15} strokeWidth={3} />
<span>NEW VOYAGE</span>
</button>
{activeTrip && (
<button
onClick={() => {
sound.playStamp();
onOpenDebrief();
}}
className="flex items-center gap-1.5 bg-gate-orange hover:bg-gate-hover text-white font-grotesk font-bold text-xs uppercase px-3 py-2 border-2 border-ink shadow-card-hard-sm active:translate-x-0.5 active:translate-y-0.5 transition-all"
title="Conduct post-trip debrief to log what to exclude/include next time"
>
<RotateCcw size={14} strokeWidth={2.5} />
<span>{activeTrip.debrief ? 'EDIT DEBRIEF' : 'TRIP DEBRIEF'}</span>
</button>
)}
</div>
</div>
{/* Progress & Stowing Tally Bar */}
{activeTrip && (
<div className="bg-ticket-200 border-t border-ticket-border px-3 sm:px-6 py-2 flex flex-col sm:flex-row sm:items-center justify-between gap-2 text-xs font-mono">
<div className="flex items-center gap-3">
<span className="font-bold uppercase text-ink">
TOTAL MANIFEST LOAD:
</span>
<span className="bg-ink text-ticket-100 px-2 py-0.5 font-bold">
{packedItems} / {totalItems} STOWED
</span>
<span className="font-bold text-ink">
({percentPacked}%)
</span>
</div>
<div className="flex items-center gap-3 w-full sm:w-64">
<div className="w-full bg-ticket-50 border border-ink h-3.5 relative overflow-hidden">
<div
className={`h-full transition-all duration-300 ${
percentPacked === 100 ? 'bg-stamp-green' : 'bg-canary'
}`}
style={{ width: `${percentPacked}%` }}
/>
</div>
{percentPacked === 100 && (
<span className="stamp-badge stamp-badge-packed text-[10px] py-0 px-1 font-bold whitespace-nowrap">
ALL STOWED
</span>
)}
</div>
</div>
)}
</header>
);
};