/* global React */ // ──────────────────────────────────────────────────────────── // SETTINGS — уведомления, пороги магнитуды, тема, источники // Phase 2: пороги/пауза летят в бот через /api/v1/config/*. // Тема — фронтенд-only (localStorage). // ──────────────────────────────────────────────────────────── window.SettingsScreen = ({ T, dark, onBack, onToggleTheme, onOpenAbout, onOpenSafety, homeVariant, onChangeHomeVariant }) => { const [magRu, setMagRu] = React.useState(null); const [magWorld, setMagWorld] = React.useState(null); const [magDigest, setMagDigest] = React.useState(null); const [paused, setPaused] = React.useState(false); const [pausedUntil, setPausedUntil] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [toast, setToast] = React.useState(null); const [pauseMinutes, setPauseMinutes] = React.useState(60); // Load current snapshot from API on mount React.useEffect(() => { let cancelled = false; (async () => { try { const s = await window.API.getStatus(); if (cancelled) return; setMagRu(s.mag_region); setMagWorld(s.mag_global); // mag_digest may be absent on older backends — fall back to mag_region. setMagDigest(typeof s.mag_digest === "number" ? s.mag_digest : s.mag_region); setPaused(!!s.paused); setPausedUntil(s.paused_until || null); } catch (e) { if (!cancelled) setError(e.message || String(e)); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, []); // Debounced threshold push (fires 400ms after last change) const pushThreshold = React.useMemo(() => { let timer = null; return (target, value) => { if (timer) clearTimeout(timer); timer = setTimeout(async () => { try { await window.API.setThreshold(target, value); const human = target === "region" ? "регион" : target === "global" ? "мир" : "дайджест"; showToast(`✓ M ${human} → ${value.toFixed(1)}`); } catch (e) { showToast(`❌ ${e.message}`); } }, 400); }; }, []); function showToast(msg) { setToast(msg); setTimeout(() => setToast(null), 2500); } async function togglePause() { const minutes = paused ? 0 : pauseMinutes; try { const r = await window.API.setPause(minutes); setPaused(!!r.paused); setPausedUntil(r.paused_until || null); showToast(paused ? "✓ Алерты возобновлены" : `✓ Пауза на ${minutes} мин`); } catch (e) { showToast(`❌ ${e.message}`); } } if (loading) { return (
ЗАГРУЗКА…
); } if (error) { return (
⚠️ Не удалось связаться с ботом:
{error}
Возможные причины: API не запущен на сервере, либо ты открыл Mini App не из своего бота. Изменить пороги можно через команду /set в чате с ботом.
); } return (
{/* Emergency reference at the very top — first thing users see when they open Settings. Below that come the regular config blocks. */}
{ setMagRu(v); pushThreshold("region", v); }} hint={`Оповещать при M ≥ ${magRu.toFixed(1)} (в реальном времени)`} /> { setMagWorld(v); pushThreshold("global", v); }} hint={`Оповещать при M ≥ ${magWorld.toFixed(1)} (в реальном времени)`} /> { setMagDigest(v); pushThreshold("digest", v); // Update Mini App feed filter immediately so the Лента/Сводка // screens reflect the new threshold without re-opening the app. if (window.DATA && window.DATA.setUserMagFilter) { window.DATA.setUserMagFilter(v); } }} hint={`Сводка 08:00/20:00 МСК и Лента — события M ≥ ${(magDigest ?? magRu).toFixed(1)}`} />
{paused ? "Алерты на паузе" : "Алерты включены"}
{paused && pausedUntil ? `до ${formatPausedUntil(pausedUntil)}` : ""}
{!paused && (
Длительность паузы: {pauseMinutes} мин
{[15, 30, 60, 120, 240].map(m => ( ))}
)}
{toast && (
{toast}
)}
); }; // ──────────────────────────────────────────── const Section = ({ T, title, children }) => (
{title}
{children}
); const Slider = ({ T, label, value, min, max, step, onChange, hint }) => (
{label}
M ≥ {value.toFixed(1)}
onChange(parseFloat(e.target.value))} style={{ width: "100%", marginTop: 10, accentColor: T.accent }} />
M {min.toFixed(1)} {hint} M {max.toFixed(1)}
); const DataSource = ({ T, name, desc }) => (
{name}
{desc}
ONLINE
); // 3-button picker for the home variant. Stores label + 1-line description. const HomeVariantPicker = ({ T, value, onChange }) => { const variants = [ { id: "A", label: "Приборный", desc: "Hero событие + сейсмограмма" }, { id: "B", label: "Карта-первая", desc: "Live-карта сверху, лента снизу" }, { id: "C", label: "Минимализм", desc: "Чистая хронология по дням" }, ]; return (
{variants.map((v) => { const active = v.id === value; return ( ); })}
{variants.find((v) => v.id === value)?.desc || ""}
); }; function formatPausedUntil(iso) { try { const d = new Date(iso); return new Intl.DateTimeFormat("ru-RU", { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: "Europe/Moscow", }).format(d) + " МСК"; } catch (e) { return iso; } }