/* History.jsx — Історія змін, live API. */ const { useState: useStateH, useEffect: useEffectH } = React; const KIND_LABELS = { publish: "Публікація", edit: "Редагування", create: "Створення", pause: "Призупинення", resume: "Відновлення", delete: "Видалення", restore: "Відновлення", ack: "Підтвердження", rollback:"Відкат", settings:"Налаштування", other: "Інше", }; const KIND_ICONS = { publish: "✅", edit: "✏️", create: "➕", pause: "⏸", resume: "▶", delete: "🗑", restore: "↩", ack: "✓", rollback: "↩", settings: "⚙", other: "•", }; const KIND_COLORS = { publish: { bg: "var(--ok-bg)", color: "var(--ok)", border: "var(--ok-border)" }, create: { bg: "var(--ok-bg)", color: "var(--ok)", border: "var(--ok-border)" }, edit: { bg: "var(--accent-soft)", color: "var(--accent)", border: "#c7d2fe" }, pause: { bg: "var(--warn-bg)", color: "var(--warn)", border: "var(--warn-border)" }, resume: { bg: "var(--ok-bg)", color: "var(--ok)", border: "var(--ok-border)" }, delete: { bg: "var(--bad-bg)", color: "var(--bad)", border: "var(--bad-border)" }, restore: { bg: "var(--ok-bg)", color: "var(--ok)", border: "var(--ok-border)" }, ack: { bg: "var(--surface-3)", color: "var(--text-2)", border: "var(--border)" }, rollback: { bg: "var(--warn-bg)", color: "var(--warn)", border: "var(--warn-border)" }, settings: { bg: "var(--surface-3)", color: "var(--text-2)", border: "var(--border)" }, other: { bg: "var(--surface-3)", color: "var(--text-2)", border: "var(--border)" }, }; function formatEventText(item) { const target = item.target_id ? `#${item.target_id}` : ""; switch (item.action) { case "passenger.stops.create": return `Створено зупинку ${target}`; case "passenger.stops.update": return `Оновлено зупинку ${target}`; case "passenger.stops.delete": return `Видалено зупинку ${target}`; case "passenger.stops.restore": return `Відновлено зупинку ${target}`; case "passenger.stops.publish": return `Опубліковано зупинку ${target}`; case "passenger.stops.bulk_delete":return `Bulk-видалено ${(item.details && item.details.deleted) || ""} зупинок`; case "passenger.routes.create": return `Створено маршрут ${target}`; case "passenger.routes.update": return `Оновлено маршрут ${target}`; case "passenger.routes.delete": return `Видалено маршрут ${target}`; case "passenger.routes.restore": return `Відновлено маршрут ${target}`; case "passenger.routes.pause": return `Призупинено маршрут ${target}`; case "passenger.routes.resume": return `Відновлено маршрут ${target}`; case "passenger.routes.publish": return `Опубліковано маршрут ${target}`; case "passenger.settings.update": return `Оновлено налаштування`; default: return item.action; } } function formatDetail(item) { if (!item.details) return null; if (item.action === "passenger.stops.bulk_delete" && Array.isArray(item.details.ids)) { return `IDs: ${item.details.ids.join(", ")}`; } if (item.details.changed) { return Array.isArray(item.details.changed) ? `Поля: ${item.details.changed.join(", ")}` : `Поля: ${Object.keys(item.details.changed).join(", ")}`; } if (item.details.reason) return `Причина: ${item.details.reason}`; if (item.details.code && item.details.name) return `${item.details.code} · ${item.details.name}`; if (item.details.name) return item.details.name; return JSON.stringify(item.details); } function dateBucket(ts) { if (!ts) return "—"; const d = new Date(ts); const now = new Date(); const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); const eventStart = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); const diffDays = Math.round((todayStart - eventStart) / 86400000); if (diffDays === 0) return "Сьогодні"; if (diffDays === 1) return "Вчора"; return d.toLocaleDateString("uk-UA", { day: "numeric", month: "long" }); } function formatTime(ts) { if (!ts) return ""; return new Date(ts).toLocaleTimeString("uk-UA", { hour: "2-digit", minute: "2-digit" }); } function History() { const [items, setItems] = useStateH([]); const [loading, setLoading] = useStateH(true); const [error, setError] = useStateH(null); const [kindFilter, setKindFilter] = useStateH("all"); const [search, setSearch] = useStateH(""); const reload = async () => { setLoading(true); try { const res = await window.passengerApi.history.list({ kind: kindFilter, search, limit: 200 }); setItems(res.items || []); setError(null); } catch (e) { setError(e); window.showToast && window.showToast("Не вдалося завантажити історію: " + (e.message || ""), "bad"); } finally { setLoading(false); } }; useEffectH(() => { reload(); /* eslint-disable-next-line */ }, [kindFilter, search]); window.passengerApi.usePolling(reload, 60000, [kindFilter, search]); // Group by date. const groups = items.reduce((acc, item) => { const d = dateBucket(item.ts); (acc[d] = acc[d] || []).push(item); return acc; }, {}); return ( <>
{error && (
Помилка завантаження.
)}
🔍 setSearch(e.target.value)} placeholder="Пошук по подіях…" style={{ paddingLeft: 32 }} />
{["publish", "edit", "pause", "delete", "settings"].map(k => ( ))}
{loading ? (
Завантаження…
) : items.length === 0 ? (
🕓
{search || kindFilter !== "all" ? "Нічого не знайдено" : "Поки немає подій"}
{search || kindFilter !== "all" ? "Спробуйте інший фільтр" : "Тут з'являться дії після створення/редагування зупинок і маршрутів"}
{(search || kindFilter !== "all") && ( )}
) : ( Object.entries(groups).map(([date, group]) => (
{date}
{group.map((item, i) => { const c = KIND_COLORS[item.kind] || KIND_COLORS.other; const icon = KIND_ICONS[item.kind] || "•"; return (
{icon}
{formatEventText(item)} {KIND_LABELS[item.kind] || item.kind}
{formatDetail(item) || "—"}
{item.actor || "anonymous"}
{formatTime(item.ts)}
); })}
)) )}
); } Object.assign(window, { History });