// LoginView, SettingsView, SlotsView, LogsView, ReservationsView // Wszystkie fetchują dane przez window.ZlapApi const { useState: uS, useEffect: uE, useMemo: uM, useCallback: uC } = React; const A2 = window.ZlapApi.adapters; /* ============================ LOGIN ============================ */ function LoginView({ onLogin }) { const [email, setEmail] = uS(""); const [password, setPassword] = uS(""); const [busy, setBusy] = uS(false); const [err, setErr] = uS(""); const submit = async (e) => { e.preventDefault(); setBusy(true); setErr(""); try { await onLogin(email, password); } catch (ex) { setErr(ex.message || "Błąd logowania"); } finally { setBusy(false); } }; return (
zlap-termin
panel administracyjny
{err && (
{err}
)}
); } window.LoginView = LoginView; /* ============================ SETTINGS ============================ */ function Section({ title, children, right }) { return (
{title}
{right}
{children}
); } function Field({ label, hint, children }) { return ( ); } function SettingsView({ toast }) { const [data, setData] = uS(null); const [saving, setSaving] = uS(false); const [testing, setTesting] = uS(""); uE(() => { window.ZlapApi.getSettings() .then(s => setData(A2.settings(s))) .catch(e => toast(`Błąd wczytywania ustawień: ${e.message}`, "err")); }, []); if (!data) return
Ładowanie…
; const set = (k, v) => setData(d => ({ ...d, [k]: v })); const save = async () => { setSaving(true); try { const fresh = await window.ZlapApi.saveSettings(A2.settingsToApi(data)); setData(A2.settings(fresh)); toast("Zapisano ustawienia", "ok"); } catch (e) { toast(`Błąd zapisu: ${e.message}`, "err"); } finally { setSaving(false); } }; const runTest = async (kind) => { setTesting(kind); try { const fn = { infocar: "testInfocar", email: "testEmail", telegram: "testTelegram" }[kind]; const r = await window.ZlapApi[fn](); toast(`Test ${kind}: ${r.ok ? "OK" : "BŁĄD"} — ${r.message}`, r.ok ? "ok" : "err"); } catch (e) { toast(`Test ${kind}: ${e.message}`, "err"); } finally { setTesting(""); } }; return (
Ustawienia
Sekrety są maskowane ••••••••. Zostaw puste żeby nie nadpisywać.
set("candidateFirstName", e.target.value)} /> set("candidateLastName", e.target.value)} /> set("candidatePesel", e.target.value)} /> set("candidatePkk", e.target.value)} /> set("candidateEmail", e.target.value)} /> set("candidatePhone", e.target.value)} />
runTest("infocar")}> {testing==="infocar" ? "Testuję…" : "Test"} } >
set("infocarEmail", e.target.value)} /> set("infocarPassword", e.target.value)} />
runTest("email")}> {testing==="email" ? "Testuję…" : "Test"} } >
set("smtpHost", e.target.value)} /> set("smtpPort", e.target.value)} /> set("smtpUser", e.target.value)} /> set("smtpPassword", e.target.value)} /> set("smtpFrom", e.target.value)} /> set("smtpTo", e.target.value)} />
runTest("telegram")}> {testing==="telegram" ? "Testuję…" : "Test"} } >
set("tgBotToken", e.target.value)} /> set("tgChatId", e.target.value)} />
); } window.SettingsView = SettingsView; /* ============================ SLOTS ============================ */ function SlotsView() { const [slots, setSlots] = uS([]); const [loading, setLoading] = uS(true); uE(() => { window.ZlapApi.getSlots({ limit: 200 }) .then(rows => setSlots(rows.map(A2.slot))) .catch(() => {}) .finally(() => setLoading(false)); }, []); const [q, setQ] = uS(""); const [wordF, setWordF] = uS("all"); const [catF, setCatF] = uS("all"); const filtered = uM(() => { let arr = slots.slice(); if (wordF !== "all") arr = arr.filter(s => s.word === wordF); if (catF !== "all") arr = arr.filter(s => s.cat === catF); if (q) { const qq = q.toLowerCase(); arr = arr.filter(s => (`${s.slotDate} ${s.slotTime} ${s.word} ${s.cat}`).toLowerCase().includes(qq)); } return arr; }, [slots, q, wordF, catF]); const uniqWords = ["all", ...Array.from(new Set(slots.map(s => s.word)))]; const uniqCats = ["all", ...Array.from(new Set(slots.map(s => s.cat)))]; return (
Terminy (historia znalezień)
setQ(e.target.value)} /> {filtered.length} / {slots.length}
{loading && } {!loading && filtered.length === 0 && } {filtered.map(s => ( ))}
ZnalezionyDataGodzinaWORDKat.Typ
Ładowanie…
Brak wyników
{s.slotDate} {s.slotTime} {s.word} {s.cat} {s.examType}
); } window.SlotsView = SlotsView; /* ============================ LOGS ============================ */ function LogsView() { const [events, setEvents] = uS([]); const [levels, setLevels] = uS(new Set(["INFO", "WARN", "ERROR"])); uE(() => { let cancelled = false; let sinceIso = null; const load = async () => { try { const rows = await window.ZlapApi.getEvents({ limit: 500 }); if (!cancelled) { setEvents(rows.map(A2.event)); if (rows.length) sinceIso = rows[0].at; } } catch (e) {} }; load(); const iv = setInterval(async () => { if (!sinceIso) return load(); try { const rows = await window.ZlapApi.getEvents({ since: sinceIso, limit: 200 }); if (!cancelled && rows.length) { const adapted = rows.map(A2.event); sinceIso = rows[0].at; setEvents(prev => [...adapted, ...prev].slice(0, 1000)); } } catch (e) {} }, 3000); return () => { cancelled = true; clearInterval(iv); }; }, []); const toggle = (lvl) => { setLevels(s => { const ns = new Set(s); if (ns.has(lvl)) ns.delete(lvl); else ns.add(lvl); return ns; }); }; const filtered = events.filter(e => levels.has(e.level)); return (
Logi
{["INFO", "WARN", "ERROR"].map(l => ( ))}
{filtered.length === 0 && } {filtered.map(e => ( ))}
PoziomCzasWiadomość
Brak zdarzeń
{e.msg}
); } window.LogsView = LogsView; /* ============================ RESERVATIONS ============================ */ function ReservationsView({ toast }) { const [items, setItems] = uS([]); const [loading, setLoading] = uS(true); uE(() => { const load = () => window.ZlapApi.listReservations(100).then(setItems).catch(()=>{}).finally(()=>setLoading(false)); load(); const iv = setInterval(load, 5000); return () => clearInterval(iv); }, []); const copy = async (url) => { try { await navigator.clipboard.writeText(url); toast("Link skopiowany", "ok"); } catch (_) {} }; return (
Rezerwacje
{loading && } {!loading && items.length === 0 && } {items.map(r => ( ))}
StanTrybData/GodzWORDKat.TypPłatnośćKiedy
Ładowanie…
Brak rezerwacji
{r.status} {r.trigger} {r.slot_date} {r.slot_time} {r.word_name} {r.category} {r.exam_type} {r.payment_url ?
Otwórz
: }
); } window.ReservationsView = ReservationsView;