Kacemath's picture
feat: update with latest changes
47bba68
import { useState, useEffect } from 'react';
import { useGridStore } from './store/gridStore';
import { Grid } from './components/Grid/Grid';
import { GridGenerator } from './components/Controls/GridGenerator';
import { AlgorithmSelector } from './components/Controls/AlgorithmSelector';
import { PlaybackControls } from './components/Controls/PlaybackControls';
import { MetricsPanel } from './components/Stats/MetricsPanel';
import { ComparisonDashboard } from './components/Stats/ComparisonDashboard';
import { GroupInfo } from './components/Info/GroupInfo';
import { PlanResultsModal } from './components/Stats/PlanResultsModal';
import { Button } from './components/ui/button';
import {
ChevronLeft,
ChevronRight,
Play,
FileText,
Eye,
BarChart3,
Truck,
AlertCircle,
Users,
Circle,
Square,
X,
} from 'lucide-react';
type Tab = 'visualize' | 'compare' | 'info';
function App() {
const [activeTab, setActiveTab] = useState<Tab>('visualize');
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [showPlanModal, setShowPlanModal] = useState(false);
const {
grid,
planResult,
runPlan,
runSearch,
isLoading,
error,
} = useGridStore();
// Open modal when planResult is updated
useEffect(() => {
if (planResult) {
setShowPlanModal(true);
}
}, [planResult]);
const handleRunSearch = () => {
if (!grid || grid.stores.length === 0 || grid.destinations.length === 0) return;
const start = grid.stores[0].position;
const goal = grid.destinations[0].position;
runSearch(start, goal);
};
const handleRunPlan = async () => {
await runPlan();
};
return (
<div className="h-screen w-screen bg-zinc-950 text-zinc-100 flex flex-col overflow-hidden">
{/* Header */}
<header className="bg-zinc-900/80 backdrop-blur-sm border-b border-zinc-800 px-6 py-3 flex items-center justify-between shrink-0">
<div className="flex items-center gap-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-zinc-800 rounded-lg">
<Truck className="w-5 h-5 text-zinc-300" />
</div>
<div>
<h1 className="text-lg font-semibold text-zinc-100">Pathfinding Visualizer</h1>
<p className="text-xs text-zinc-500">Search Algorithm Analysis</p>
</div>
</div>
</div>
{/* Tab navigation */}
<div className="flex gap-1 p-1 bg-zinc-800/50 rounded-lg">
<Button
onClick={() => setActiveTab('visualize')}
variant={activeTab === 'visualize' ? 'secondary' : 'ghost'}
size="sm"
className="gap-2"
>
<Eye className="w-4 h-4" />
Visualize
</Button>
<Button
onClick={() => setActiveTab('compare')}
variant={activeTab === 'compare' ? 'secondary' : 'ghost'}
size="sm"
className="gap-2"
>
<BarChart3 className="w-4 h-4" />
Compare
</Button>
<Button
onClick={() => setActiveTab('info')}
variant={activeTab === 'info' ? 'secondary' : 'ghost'}
size="sm"
className="gap-2"
>
<Users className="w-4 h-4" />
Group Info
</Button>
</div>
<div className="flex items-center gap-2">
{grid && (
<>
<Button
onClick={handleRunSearch}
disabled={isLoading}
variant="primary"
size="sm"
className="gap-2"
>
<Play className="w-4 h-4" />
{isLoading ? 'Running...' : 'Run Search for S1'}
</Button>
<Button
onClick={handleRunPlan}
disabled={isLoading}
variant="outline"
size="sm"
className="gap-2"
>
<FileText className="w-4 h-4" />
Full Plan
</Button>
</>
)}
</div>
</header>
{/* Main content */}
<div className="flex-1 flex overflow-hidden">
{/* Sidebar */}
<aside className={`bg-zinc-900/50 border-r border-zinc-800 flex flex-col transition-all duration-300 ${sidebarCollapsed ? 'w-12' : 'w-80'}`}>
<button
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className="p-3 text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800/50 border-b border-zinc-800 transition-colors"
>
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
</button>
{!sidebarCollapsed && (
<div className="flex-1 overflow-y-auto p-4 space-y-4">
<GridGenerator />
<AlgorithmSelector />
<PlaybackControls />
<MetricsPanel />
</div>
)}
</aside>
{/* Main area */}
<main className="flex-1 flex flex-col overflow-hidden" style={{ backgroundColor: '#0a0a0b' }}>
{/* Error display */}
{error && (
<div className="m-4 mb-0 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-400 flex items-center gap-3 text-sm">
<AlertCircle className="w-4 h-4 shrink-0" />
<span>{error}</span>
</div>
)}
{/* Content */}
<div className="flex-1 overflow-hidden relative">
{activeTab === 'visualize' ? (
<>
<Grid />
{/* Legend overlay */}
<div className="absolute bottom-4 left-4 bg-zinc-900/95 backdrop-blur-sm border border-zinc-800 rounded-lg p-3 shadow-xl">
<p className="text-[10px] uppercase tracking-wider text-zinc-500 font-medium mb-2">Legend</p>
<div className="flex gap-4 text-xs">
<div className="flex flex-col gap-1.5">
<div className="flex items-center gap-2">
<Circle className="w-2.5 h-2.5 fill-amber-500 text-amber-500" />
<span className="text-zinc-400">Current</span>
</div>
<div className="flex items-center gap-2">
<Circle className="w-2.5 h-2.5 fill-emerald-500 text-emerald-500" />
<span className="text-zinc-400">Path</span>
</div>
<div className="flex items-center gap-2">
<Circle className="w-2.5 h-2.5 fill-sky-500 text-sky-500" />
<span className="text-zinc-400">Frontier</span>
</div>
<div className="flex items-center gap-2">
<Circle className="w-2.5 h-2.5 fill-zinc-500 text-zinc-500" />
<span className="text-zinc-400">Explored</span>
</div>
</div>
<div className="border-l border-zinc-800 pl-4 flex flex-col gap-1.5">
<div className="flex items-center gap-2">
<Square className="w-2.5 h-2.5 fill-blue-500 text-blue-500 rounded-sm" />
<span className="text-zinc-400">Store</span>
</div>
<div className="flex items-center gap-2">
<Circle className="w-2.5 h-2.5 fill-emerald-500 text-emerald-500" />
<span className="text-zinc-400">Destination</span>
</div>
<div className="flex items-center gap-2">
<Circle className="w-2.5 h-2.5 fill-purple-500 text-purple-500" />
<span className="text-zinc-400">Tunnel</span>
</div>
<div className="flex items-center gap-2">
<X className="w-2.5 h-2.5 text-red-500" />
<span className="text-zinc-400">Blocked</span>
</div>
</div>
<div className="border-l border-zinc-800 pl-4">
<p className="text-zinc-600 text-[10px] mb-1">Edge Costs</p>
<div className="flex gap-2 text-xs font-mono">
<span className="text-zinc-300">1</span>
<span className="text-zinc-400">2</span>
<span className="text-zinc-500">3</span>
<span className="text-zinc-600">4</span>
</div>
</div>
</div>
</div>
</>
) : activeTab === 'compare' ? (
<div className="h-full p-4">
<ComparisonDashboard />
</div>
) : (
<GroupInfo />
)}
</div>
</main>
</div>
{/* Status bar */}
<footer className="bg-zinc-900/50 border-t border-zinc-800 px-6 py-2 flex items-center justify-between text-xs text-zinc-600 shrink-0">
<div className="flex items-center gap-4 font-mono">
{grid && (
<>
<span>Grid {grid.width}x{grid.height}</span>
<span className="text-zinc-700">|</span>
<span>S:{grid.stores.length}</span>
<span>D:{grid.destinations.length}</span>
<span>T:{grid.tunnels.length}</span>
</>
)}
</div>
<div className="flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
<span>Connected</span>
</div>
</footer>
{/* Plan Results Modal */}
<PlanResultsModal
isOpen={showPlanModal}
onClose={() => setShowPlanModal(false)}
/>
</div>
);
}
export default App;