// zlap-termin — app shell z prawdziwym API backend const { useState, useEffect, useMemo, useRef, useCallback } = React; const API = window.ZlapApi; const A = API.adapters; /* Topbar */ function TopBar({ state, email, onLogout, startedAt }) { return (
zlap-termin
{startedAt && ( uptime )}
{email || ""}
); } /* Sidebar */ const NAV = [ { id: "dashboard", label: "Dashboard", icon: "activity" }, { id: "check", label: "Sprawdź", icon: "search" }, { id: "auto", label: "Auto-task", icon: "zap" }, { id: "reservations", label: "Rezerwacje", icon: "check" }, { id: "slots", label: "Terminy", icon: "clock" }, { id: "logs", label: "Logi", icon: "terminal" }, { id: "settings", label: "Ustawienia", icon: "settings" }, ]; function Sidebar({ active, onNav, collapsed, setCollapsed }) { return ( <> ); } /* Toast host */ function useToasts() { const [toasts, setToasts] = useState([]); const push = useCallback((msg, tone = "ok") => { const id = Math.random().toString(36).slice(2); setToasts(t => [...t, { id, msg, tone }]); setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 5000); }, []); const node = (
{toasts.map(t => (
{t.msg}
))}
); return [push, node]; } /* ============================ APP ============================ */ function App() { const [session, setSession] = useState(null); // {email, csrf_token} albo null const [loading, setLoading] = useState(true); const [route, setRoute] = useState("dashboard"); const [status, setStatus] = useState(null); const [navCollapsed, setNavCollapsed] = useState(false); const [toast, toastNode] = useToasts(); // Bootstrap: sprawdź session useEffect(() => { API.session() .then(s => setSession(s)) .catch(() => setSession(null)) .finally(() => setLoading(false)); }, []); // Polling statusu (co 3s gdy zalogowany) useEffect(() => { if (!session) return; let cancelled = false; const tick = async () => { try { const s = await API.status(); if (!cancelled) setStatus(s); } catch (e) { if (e.status === 401 && !cancelled) setSession(null); } }; tick(); const iv = setInterval(tick, 3000); return () => { cancelled = true; clearInterval(iv); }; }, [session]); const handleLogin = async (email, password) => { try { const res = await API.login(email, password); setSession(res); toast("Zalogowano", "ok"); } catch (e) { toast(`Błąd logowania: ${e.message || e}`, "err"); throw e; } }; const handleLogout = async () => { try { await API.logout(); } catch (_) {} setSession(null); toast("Wylogowano", "info"); }; if (loading) { return (
Ładowanie…
); } if (!session) { return ; } const botState = status?.state || "offline"; const startedAt = status?.started_at ? new Date(status.started_at) : null; const view = () => { switch (route) { case "dashboard": return ; case "check": return ; case "auto": return ; case "reservations": return ; case "slots": return ; case "logs": return ; case "settings": return ; default: return null; } }; return (
{view()}
{toastNode}
); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render();