// Haikara Tracking — App-Shell: Auth-Gate, State-Store, Routing, Actions. // Lädt Sendungen aus /api/tracking, hält den lokalen State und reicht // { data, actions, route, setRoute } an die Screens (nav/dashboard/detail/add). const { useState: useS, useEffect: useE, useCallback: useC, useRef: useRf } = React; const { Icon: AppIcon } = window.HaikaraDesignSystem_df3594; const STORE_KEY = 'haikara-tracking-route'; function LoginGate() { return (

Haikara Tracking

Dein Sendungs-Cockpit. Melde dich an, um deine Pakete zu verfolgen.

); } function App() { const [route, setRouteRaw] = useS(() => { try { const s = JSON.parse(localStorage.getItem(STORE_KEY)); if (s && s.page) return s; } catch (e) {} return { page: 'dashboard', filter: 'all' }; }); const [dark, setDark] = useS(true); const [user, setUser] = useS(null); const [loading, setLoading] = useS(true); const [shipments, setShipments] = useS([]); const [toast, setToast] = useS(null); const toastTimer = useRf(null); const setRoute = useC((r) => setRouteRaw(r), []); useE(() => { document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light'); }, [dark]); useE(() => { try { localStorage.setItem(STORE_KEY, JSON.stringify(route)); } catch (e) {} }, [route]); const flash = useC((msg, kind) => { setToast({ msg, kind: kind || 'info' }); clearTimeout(toastTimer.current); toastTimer.current = setTimeout(() => setToast(null), 2600); }, []); const reload = useC(async () => { try { const list = await window.trackingApi.loadShipments(route.page === 'archive' ? true : false); setShipments(list); } catch (e) { console.error('tracking load:', e); } }, [route.page]); // Auth + Initial-Load useE(() => { let alive = true; (async () => { const u = (window.hkAuth && window.hkAuth.getCurrentUser) ? await window.hkAuth.getCurrentUser().catch(() => null) : null; if (!alive) return; setUser(u || null); if (!u) { setLoading(false); return; } await reload(); if (alive) setLoading(false); })(); return () => { alive = false; }; }, []); // Beim Wechsel aktive ⇄ Archiv neu laden. useE(() => { if (user) reload(); }, [route.page === 'archive']); // ── Actions ───────────────────────────────────────────────────────────────── const actions = { reload, flash, getDetail: (id) => window.trackingApi.getShipment(id), add: async (payload) => { const res = await window.trackingApi.addShipment(payload); if (res.shipment) { await reload(); flash('Sendung hinzugefügt.', 'success'); return res; } if (res.status === 409) flash('Diese Sendung verfolgst du bereits.', 'warn'); else flash(res.error || 'Konnte nicht hinzufügen.', 'danger'); return res; }, refresh: async (id) => { const res = await window.trackingApi.refreshShipment(id); if (res.shipment) { if (res.shipment.provider === 'manual') flash('Manuelle Sendung — Status pflegst du selbst.', 'info'); else flash(res.newCheckpoints ? `${res.newCheckpoints} neue Checkpoints.` : 'Keine Änderung.', 'success'); await reload(); return res.shipment; } flash(res.error || 'Aktualisierung fehlgeschlagen.', 'danger'); return null; }, addEvent: async (id, ev) => { const s = await window.trackingApi.addEvent(id, ev); if (s) { await reload(); flash('Checkpoint hinzugefügt.', 'success'); } else flash('Konnte Checkpoint nicht speichern.', 'danger'); return s; }, archive: async (id, archived) => { const s = await window.trackingApi.updateShipment(id, { archived: archived !== false }); if (s) { await reload(); flash(archived !== false ? 'Archiviert.' : 'Wiederhergestellt.', 'success'); } return s; }, setStatus: async (id, status) => { const s = await window.trackingApi.updateShipment(id, { status }); if (s) await reload(); return s; }, remove: async (id) => { const ok = await window.trackingApi.removeShipment(id); if (ok) { await reload(); flash('Gelöscht.', 'success'); } else flash('Löschen fehlgeschlagen (fehlt dir die Berechtigung?).', 'danger'); return ok; }, }; if (loading) { return
// lädt…
; } if (!user) return ; // Activity synthetisieren: jüngster bekannter Stand je Sendung (das Backend hat // (noch) keinen app-weiten Event-Feed — v1 zeigt eine Zeile pro Sendung). const activity = shipments .map((s) => ({ id: 'act-' + s.id, pkg: s.id, carrier: s.carrier, label: window.trkStatusMeta(s.status).label, loc: s.loc, t: s.updated, status: s.status, title: s.title })) .sort((a, b) => new Date(b.t || 0) - new Date(a.t || 0)); const data = { user, packages: shipments, activity }; let screen; if (route.page === 'detail') screen = ; else if (route.page === 'add') screen = ; else if (route.page === 'activity') screen = ; else screen = ; return (
{screen} {toast && (
{toast.msg}
)}
); } ReactDOM.createRoot(document.getElementById('root')).render();