/* passenger/app.jsx — root */ function ToastSystem() { const [toasts, setToasts] = React.useState([]); React.useEffect(() => { window.showToast = (msg, kind = "info", undo = null) => { const id = Date.now(); setToasts(prev => [...prev.slice(-4), { id, msg, kind, undo }]); setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), undo ? 10000 : 4000); }; }, []); const dismiss = id => setToasts(prev => prev.filter(t => t.id !== id)); return (
{toasts.map(t => (
{t.msg}
{t.undo && ( )}
))}
); } function PlaceholderScreen({ title, icon }) { return ( <>
{icon}
{title}
Екран у розробці — буде в наступному етапі
); } function App() { const [user, setUser] = React.useState(null); const [bootChecked, setBootChecked] = React.useState(false); const [screen, setScreen] = React.useState("overview"); const [paletteOpen, setPaletteOpen] = React.useState(false); // Перевірка існуючої сесії при mount. React.useEffect(() => { let cancelled = false; (async () => { try { const res = await window.passengerApi.auth.whoami(); if (!cancelled) { setUser({ actor: res.actor, role: res.role, capabilities: res.capabilities }); } } catch (err) { if (!cancelled) setUser(null); } finally { if (!cancelled) setBootChecked(true); } })(); return () => { cancelled = true; }; }, []); React.useEffect(() => { window.openPalette = () => setPaletteOpen(true); return () => { delete window.openPalette; }; }, []); React.useEffect(() => { const h = (e) => { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); setPaletteOpen(p => !p); } }; window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, []); const handleLogin = (u) => { setUser(u); setScreen("overview"); }; const handleLogout = async () => { try { await window.passengerApi.auth.logout(); } catch (e) { /* ignore */ } setUser(null); setScreen("overview"); }; if (!bootChecked) { return (
Перевірка сесії…
); } if (!user) return ; const caps = user.capabilities || {}; const allowedScreen = (s) => { if (s === "settings") return !!caps.settings_write; return true; }; const safeScreen = allowedScreen(screen) ? screen : "overview"; return (
{safeScreen === "overview" && } {safeScreen === "stops" && } {safeScreen === "routes" && } {safeScreen === "monitoring" && } {safeScreen === "analytics" && } {safeScreen === "history" && } {safeScreen === "settings" && caps.settings_write && }
setPaletteOpen(false)} onNav={setScreen} />
); } ReactDOM.createRoot(document.getElementById("root")).render();