// Florio Analytics — Live tab (real-time, big-screen / TV view). // Ported from js/pages/live.js. Subscribes to the analytics_counters Firestore // docs ("today" + "global") via FlorioAPI.subscribeToCounter and counts total // plants once via fetchTotalPlants. Numbers update live; subscriptions are // cleaned up on unmount. Range prop is ignored (this page is not date-range based). (function () { const I = window.HyperIcons; const { useState, useEffect, useRef } = React; // formatNumber from the data layer (lazy — FlorioAPI may not exist at definition time). function fmt(n) { if (n == null) return '--'; return (window.FlorioAPI && window.FlorioAPI.formatNumber) ? window.FlorioAPI.formatNumber(n) : String(n); } // Card definitions mirror the old page. `counter` selects which live snapshot // (or "manual" one-shot fetch) supplies the value; `key` is the field on the doc. const CARDS = [ { key: 'waterings', counter: 'today', label: 'Waterings Today', accent: '#2ACF56', icon: 'droplet' }, { key: 'totalWaterings', counter: 'global', label: 'Waterings All Time', accent: 'var(--app-accent)', icon: 'droplet' }, { key: 'totalPlants', counter: 'manual', label: 'Total Plants', accent: '#2ACF56', icon: 'leaf' }, { key: 'totalUsers', counter: 'global', label: 'Total Users', accent: 'var(--app-accent)', icon: 'users' }, ]; function Live({ range }) { // values keyed by card.key; null until first snapshot/fetch arrives. const [values, setValues] = useState({ waterings: null, totalWaterings: null, totalPlants: null, totalUsers: null }); const [tvMode, setTvMode] = useState(false); const [ready, setReady] = useState(false); useEffect(() => { let alive = true; const unsubs = []; (async () => { await window.FlorioReady; if (!alive) return; const API = window.FlorioAPI; setReady(true); const applyCounter = (counterName) => (data) => { if (!alive) return; setValues((prev) => { const next = Object.assign({}, prev); for (const c of CARDS) { if (c.counter === counterName) next[c.key] = data[c.key] ?? 0; } return next; }); }; try { unsubs.push(API.subscribeToCounter('global', applyCounter('global'))); } catch (e) { console.error('[Live] subscribe global failed:', e); } try { unsubs.push(API.subscribeToCounter('today', applyCounter('today'))); } catch (e) { console.error('[Live] subscribe today failed:', e); } // Total plants — one-shot count of every user's plants subcollection. API.fetchTotalPlants() .then((total) => { if (alive) setValues((prev) => Object.assign({}, prev, { totalPlants: total })); }) .catch((e) => console.error('[Live] fetchTotalPlants failed:', e)); })(); return () => { alive = false; for (const u of unsubs) { try { if (typeof u === 'function') u(); } catch (e) { /* noop */ } } }; }, []); if (!ready) { return (
); } const valueSize = tvMode ? 'clamp(56px, 11vw, 168px)' : 'clamp(40px, 6vw, 84px)'; const labelSize = tvMode ? 'clamp(16px, 1.8vw, 28px)' : 16; return (
{/* Header + TV-mode toggle */}
{I.monitor ? I.monitor({ size: 20 }) : null}
Live activity
Real-time counters — updates as events arrive.
{/* Counter grid — big numbers */}
{CARDS.map((c) => { const v = values[c.key]; return (
{I[c.icon] ? I[c.icon]({ size: tvMode ? 28 : 20 }) : null} {fmt(v)} {c.label}
); })}
); } (window.FlorioTabs = window.FlorioTabs || {}).live = Live; })();