// 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();