import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import { Truck, Ship, Package, DollarSign, TrendingUp, AlertTriangle, Play, Factory, Activity, BarChart2, CheckCircle, Navigation, Download, Settings, RotateCcw, Zap, Globe } from 'lucide-react'; import { LineChart, Line, AreaChart, Area, BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from 'recharts'; import { MapContainer, TileLayer, Marker, Polyline, Popup } from 'react-leaflet'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import toast, { Toaster } from 'react-hot-toast'; import './App.css'; import EditorialNavigation from './components/Navigation'; import Hero from './components/Hero'; import StatsGrid from './components/StatsGrid'; import TextReveal from './components/TextReveal'; import Workflow from './components/Workflow'; import UseCaseTabs from './components/UseCaseTabs'; import ContactForm from './components/ContactForm'; import Footer from './components/Footer'; import InteractiveWavesBackground from './components/InteractiveWaves'; const API = '/api'; // ── Map Data ────────────────────────────────────────────────────── const MAP_NODES = { factories: [ { id: 'china', name: 'China Factory', lat: 22.5, lng: 114, cost: 50 }, { id: 'india', name: 'India Factory', lat: 19, lng: 72.8, cost: 45 }, { id: 'mexico', name: 'Mexico Factory', lat: 25.6, lng: -100.3, cost: 70 }, { id: 'vietnam', name: 'Vietnam Factory', lat: 10.8, lng: 106.6, cost: 42 }, { id: 'brazil', name: 'Brazil Factory', lat: -23.5, lng: -46.6, cost: 65 }, { id: 'germany', name: 'Germany Factory', lat: 48.8, lng: 9.2, cost: 90 }, ], ports: [ { id: 'shanghai', name: 'Shanghai Port', lat: 31.23, lng: 121.47, delay: 1 }, { id: 'rotterdam', name: 'Rotterdam Port', lat: 51.92, lng: 4.48, delay: 2 }, { id: 'la', name: 'LA Port', lat: 33.74, lng: -118.27, delay: 1 }, { id: 'singapore', name: 'Singapore Port', lat: 1.35, lng: 103.82, delay: 1 }, { id: 'busan', name: 'Busan Port', lat: 35.1, lng: 129.03, delay: 1 }, { id: 'dubai', name: 'Dubai Port', lat: 25.0, lng: 55.1, delay: 2 }, { id: 'santos', name: 'Santos Port', lat: -23.96, lng: -46.3, delay: 2 }, ], warehouses: [ { id: 'usa', name: 'USA Warehouse', lat: 39.83, lng: -98.58 }, { id: 'eu', name: 'EU Warehouse', lat: 50.11, lng: 8.68 }, { id: 'asia', name: 'Asia Warehouse', lat: 35.68, lng: 139.69 }, { id: 'mideast', name: 'Middle East Warehouse', lat: 25.2, lng: 55.3 }, { id: 'latam', name: 'LatAm Warehouse', lat: -23.5, lng: -46.6 }, { id: 'oceania', name: 'Oceania Warehouse', lat: -33.87, lng: 151.21 }, ], }; const ROUTES = [ // Factory → Port (gold lines) { from: [22.5, 114], to: [31.23, 121.47], color: '#b08d3a' }, { from: [22.5, 114], to: [1.35, 103.82], color: '#b08d3a' }, { from: [19, 72.8], to: [1.35, 103.82], color: '#b08d3a' }, { from: [19, 72.8], to: [25.0, 55.1], color: '#b08d3a' }, { from: [25.6, -100.3], to: [33.74, -118.27], color: '#b08d3a' }, { from: [10.8, 106.6], to: [1.35, 103.82], color: '#b08d3a' }, { from: [10.8, 106.6], to: [35.1, 129.03], color: '#b08d3a' }, { from: [-23.5, -46.6], to: [-23.96, -46.3], color: '#b08d3a' }, { from: [48.8, 9.2], to: [51.92, 4.48], color: '#b08d3a' }, // Port → Warehouse (green lines) { from: [31.23, 121.47], to: [39.83, -98.58], color: '#3d7068' }, { from: [31.23, 121.47], to: [35.68, 139.69], color: '#3d7068' }, { from: [33.74, -118.27], to: [39.83, -98.58], color: '#3d7068' }, { from: [51.92, 4.48], to: [50.11, 8.68], color: '#3d7068' }, { from: [1.35, 103.82], to: [35.68, 139.69], color: '#3d7068' }, { from: [1.35, 103.82], to: [-33.87, 151.21], color: '#3d7068' }, { from: [35.1, 129.03], to: [35.68, 139.69], color: '#3d7068' }, { from: [25.0, 55.1], to: [25.2, 55.3], color: '#3d7068' }, { from: [25.0, 55.1], to: [50.11, 8.68], color: '#3d7068' }, { from: [-23.96, -46.3], to: [-23.5, -46.6], color: '#3d7068' }, { from: [-23.96, -46.3], to: [39.83, -98.58], color: '#3d7068' }, ]; // ── Custom Marker Icons ─────────────────────────────────────────── function createIcon(type, label) { return L.divIcon({ className: '', html: `
${label}
`, iconSize: [0, 0], iconAnchor: [0, 0], }); } // ── Interactive Map Component ───────────────────────────────────── function InteractiveMap({ state }) { const inventory = state?.inventory || { usa: 0, eu: 0, asia: 0 }; const portDelays = state?.portDelays || []; return (
{/* Factory Markers */} {(state?.factoryCosts || []).map(f => { const node = MAP_NODES.factories.find(n => n.id === f.id); if (!node) return null; return ( {f.name}
Unit Cost: ${f.currentCost}
); })} {/* Port Markers */} {portDelays.map(p => { const node = MAP_NODES.ports.find(n => n.id === p.id); if (!node) return null; return ( {p.name}
Delay: {p.currentDelay}mo
Status: {p.isClosed ? '🔴 Closed' : '🟢 Open'}
); })} {/* Warehouse Markers */} {MAP_NODES.warehouses.map(w => ( {w.name}
Inventory: {inventory[w.id] ?? 0} units
))} {/* Route Lines */} {ROUTES.map((r, i) => ( ))}
); } // ── Chart Tooltip ───────────────────────────────────────────────── const ChartTooltip = ({ active, payload, label }) => { if (!active || !payload?.length) return null; return (
{label}
{payload.map((p, i) => (
{p.name}: {typeof p.value === 'number' ? p.value.toLocaleString() : p.value}
))}
); }; // ── Main App ────────────────────────────────────────────────────── function App() { const [state, setState] = useState(null); const [loading, setLoading] = useState(false); const [factory, setFactory] = useState('china'); const [port, setPort] = useState('shanghai'); const [destination, setDestination] = useState('usa'); const [quantity, setQuantity] = useState(100); const [oracleAdvice, setOracleAdvice] = useState(''); const [autoPlaying, setAutoPlaying] = useState(false); // ── Fetch state ─────────────────────────────────────────────── const fetchState = useCallback(async () => { try { const res = await fetch(`${API}/state`); if (!res.ok) throw new Error('Failed to fetch'); const data = await res.json(); setState(data); } catch (err) { console.error('Fetch error:', err); } }, []); useEffect(() => { fetchState(); }, [fetchState]); // ── Submit order (matches backend OrderRequest: factory, quantity, port, dest) ── const submitOrder = useCallback(async () => { try { const res = await fetch(`${API}/order`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ factory, port, dest: destination, quantity }), }); if (!res.ok) { const err = await res.json(); toast.error(err.detail || 'Order failed'); return; } toast.success(`Order: ${quantity} units via ${port}`); await fetchState(); } catch (err) { toast.error('Order failed'); } }, [factory, port, destination, quantity, fetchState]); // ── Advance turn ────────────────────────────────────────────── const advanceTurn = useCallback(async () => { setLoading(true); try { const res = await fetch(`${API}/advance`, { method: 'POST' }); const data = await res.json(); setState(data); toast.success(`Month ${data.turn} complete`); } catch (err) { toast.error('Advance failed'); } finally { setLoading(false); } }, []); // ── Reset ───────────────────────────────────────────────────── const resetGame = useCallback(async () => { try { await fetch(`${API}/reset`, { method: 'POST' }); await fetchState(); toast.success('Simulation reset'); } catch (err) { toast.error('Reset failed'); } }, [fetchState]); // ── Oracle ──────────────────────────────────────────────────── const fetchOracle = useCallback(async () => { try { const res = await fetch(`${API}/oracle`); const data = await res.json(); setOracleAdvice(data.advice || 'No advice available'); } catch { setOracleAdvice('Oracle unavailable'); } }, []); useEffect(() => { if (state) fetchOracle(); }, [state, fetchOracle]); // ── Auto-play ───────────────────────────────────────────────── const autoPlay = useCallback(async () => { setAutoPlaying(true); try { const res = await fetch(`${API}/autoplay`, { method: 'POST' }); const data = await res.json(); setState(data); toast.success('AI Auto-Play complete'); } catch { toast.error('Auto-play failed'); } finally { setAutoPlaying(false); } }, []); // ── Export CSV ───────────────────────────────────────────────── const exportCSV = useCallback(() => { if (!state?.history?.length) return; const headers = ['Month', 'Revenue', 'Balance', 'Profit', 'Penalties']; const rows = state.history.map(h => [h.month, h.revenue, h.balance, h.profit, h.penalties]); const csv = [headers, ...rows].map(r => r.join(',')).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.setAttribute('hidden', ''); a.setAttribute('href', url); a.setAttribute('download', 'supply_chain_report.csv'); document.body.appendChild(a); a.click(); document.body.removeChild(a); toast.success('CSV Exported'); }, [state]); // ── Decision preview ────────────────────────────────────────── const preview = useMemo(() => { if (!state) return { cost: 0, eta: 2, affordable: true, costPerUnit: 0 }; const f = (state.factoryCosts || []).find(x => x.id === factory); const p = (state.portDelays || []).find(x => x.id === port); const unitCost = f ? f.currentCost : 50; const containers = Math.ceil(quantity / 100); const cost = unitCost * quantity + containers * 3000; const eta = p ? p.currentDelay + 1 : 2; const affordable = cost <= (state.balance || 0); return { cost, eta, affordable, costPerUnit: unitCost }; }, [factory, port, quantity, state]); // ── History for charts ──────────────────────────────────────── const chartData = useMemo(() => { if (!state?.history) return []; return state.history.map(h => ({ name: `M${h.month}`, revenue: h.revenue || 0, balance: h.balance || 0, profit: h.profit || 0, })); }, [state]); // ── Derived values ──────────────────────────────────────────── const totalInventory = state ? Object.values(state.inventory || {}).reduce((a, b) => a + b, 0) : 0; const totalDemand = state ? Object.values(state.demands || {}).reduce((a, b) => a + b, 0) : 0; // ── Render ──────────────────────────────────────────────────── return ( <> {/* Editorial background grid */}
{/* Interactive Perlin noise waves — covers entire UI */} {/* Landing Page Sections */}
{/* Simulator Section */}
{!state ? (
TradeStorm
Connecting to simulation engine...
) : ( <> {/* ─── TOP BAR ─── */}
SCENARIO: {(state.scenario || 'normal').toUpperCase()} MONTH {state.turn || 1}
{/* ─── MAIN CONTENT ─── */}
{/* ─── SIDEBAR ─── */} {/* Layout divider */}
{/* ─── CENTER CONTENT ─── */}
{/* Oracle Banner */} {oracleAdvice && (
AI Oracle Advisory
{oracleAdvice}
)} {/* KPI Cards */}
Balance
${(state.balance || 0).toLocaleString()}
Benchmark
${(state.benchmark_balance || 0).toLocaleString()}
Inventory
{totalInventory}
Demand
{totalDemand}
{/* Map */} {/* Charts */}
Balance & Revenue
} />
Profit per Turn
} />
{/* Warehouse Inventory Bars */}
Warehouse Levels
{MAP_NODES.warehouses.map(w => { const inv = state.inventory?.[w.id] || 0; const demand = state.demands?.[w.id] || 0; const pct = Math.min(100, (inv / Math.max(demand, 1)) * 100); const color = pct > 80 ? '#3d7068' : pct > 40 ? '#b08d3a' : '#a04040'; return (
{w.name} (demand: {demand}) {inv} units
); })}
{/* Leaderboard */} {(state.leaderboard || []).length > 0 && (
Top Routes
{state.leaderboard.map((r, i) => (
{i + 1}. {r.route} ${r.cost} | {r.time}mo | {r.reliability}%
))}
)}
)}