// ════════════════════════════════════════════════════════════════════════
// PropMystro · G2+G7 · pm-d-alerts.jsx
// ALERTS — the aggregated feed from the Apps Script Alerts page: certificate
// expiries, maintenance escalations (Awaab clock), arrears, rent-review
// windows & served notices, tax deadlines & open queries, inspection
// schedule + pending approvals — grouped Critical / This week / Upcoming.
// ACTIVITY — the portfolio-wide audit feed: activity_log + filings/payments/
// tickets, newest first, filterable. → window.PMAlertsHub, window.PMActivityLog
// ════════════════════════════════════════════════════════════════════════
(function () {
  const { useState, useEffect, useCallback } = React;
  const todayISO = () => new Date().toISOString().slice(0, 10);
  const fmt = (d) => d ? new Date(d).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }) : '—';
  const gbp = (n) => '£' + (Number(n) || 0).toLocaleString('en-GB');
  const days = (i) => Math.round((new Date(i + 'T00:00:00Z') - new Date(todayISO() + 'T00:00:00Z')) / 864e5);
  const addMonths = (i, n) => { const d = new Date(i + 'T00:00:00Z'); d.setUTCMonth(d.getUTCMonth() + n); return d.toISOString().slice(0, 10); };
  const addDays = (i, n) => { const d = new Date(i + 'T00:00:00Z'); d.setUTCDate(d.getUTCDate() + n); return d.toISOString().slice(0, 10); };
  function Spin() { return <span className="spin dark" />; }

  const CERT_LBL = { gas_safety: 'Gas Safety', eicr: 'Electrical (EICR)', epc: 'EPC', deposit_protection: 'Deposit protection', right_to_rent: 'Right to Rent', hmo_licence: 'HMO licence', insurance: 'Insurance', hmo_inspection: 'Inspection report' };

  function useAll(sb) {
    const [d, setD] = useState(null);
    const load = useCallback(async () => {
      const [p, c, tk, ty, tn, pay, ts, insp, act, nt] = await Promise.all([
        sb.from('properties').select('id,address,type,rent_pcm:rent,primary_owner_id'),
        sb.from('certificates').select('id,property_id,type,expiry_date,cert_number,created_at,file_name'),
        sb.from('tickets').select('id,property_id,title,category,priority,status,raised_at,resolved_at,cost'),
        sb.from('tenancies').select('id,property_id,lead_tenant_id,status,rent_pcm,pay_day,start_date,end_date,rent_review_anchor,last_rent_review_at'),
        sb.from('tenants').select('id,full_name'),
        sb.from('payments').select('tenancy_id,amount,date,kind,ref,created_at'),
        sb.from('tax_submissions').select('id,owner_id,tax_year,period_label,period_key,deadline,stage,queries'),
        sb.from('inspections').select('id,property_id,status,completed_at,ticket_review,inspector_name,started_at'),
        sb.from('activity_log').select('*').order('at', { ascending: false }).limit(120),
        sb.from('notices').select('id,property_id,status,effective_date,type').eq('type', 'section_13'),
      ]);
      setD({ props: p.data || [], certs: c.data || [], tickets: tk.data || [], tenancies: ty.data || [], tenants: tn.data || [], payments: pay.data || [], taxSubs: ts.data || [], insps: insp.data || [], log: act.data || [], notices: (nt && nt.data) || [] });
    }, [sb]);
    useEffect(() => { load(); }, [load]);
    return d;
  }

  // ── build the aggregated alert items ─────────────────────────────────────
  function buildAlerts(d, owners) {
    const items = [];
    const propById = Object.fromEntries(d.props.map(p => [p.id, p]));
    const tenById = Object.fromEntries(d.tenants.map(t => [t.id, t]));
    const push = (tier, icon, title, sub, view, dot) => items.push({ tier, icon, title, sub, view, dot });

    // certificates (latest per property+type)
    const latest = {};
    d.certs.forEach(c => { if (!c.expiry_date) return; const k = c.property_id + '|' + c.type; if (!latest[k] || c.expiry_date > latest[k].expiry_date) latest[k] = c; });
    Object.values(latest).forEach(c => {
      const dd = days(c.expiry_date); const p = propById[c.property_id] || {};
      if (dd < 0) push('critical', '◉', (CERT_LBL[c.type] || c.type) + ' expired', p.address + ' · ' + Math.abs(dd) + 'd overdue', 'compliance', 'crit');
      else if (dd <= 14) push('week', '◉', (CERT_LBL[c.type] || c.type) + ' expires in ' + dd + 'd', p.address + ' · ' + fmt(c.expiry_date), 'compliance', 'warn');
      else if (dd <= 60) push('upcoming', '◉', (CERT_LBL[c.type] || c.type) + ' due ' + fmt(c.expiry_date), p.address, 'compliance', 'warn');
    });

    // maintenance — escalated damp + high/critical open
    d.tickets.filter(t => t.status !== 'resolved').forEach(t => {
      const p = propById[t.property_id] || {};
      if (t.category === 'damp_mould') {
        const dl = addMonths(t.raised_at, 0); const clock = 14 + days(t.raised_at);
        push('critical', '⚒', 'Damp & mould — statutory clock', p.address + ' · ' + t.title + ' · ' + (clock >= 0 ? clock + 'd left' : Math.abs(clock) + 'd overdue'), 'maintenance', 'crit');
      } else if (t.priority === 'critical' || t.priority === 'high') {
        push('week', '⚒', 'Open ' + t.priority + ' ticket', p.address + ' · ' + t.title, 'maintenance', 'warn');
      }
    });

    // arrears (derived, same model as the ledger)
    const clampDay = (x) => Math.min(Number(x) || 1, 28);
    const psoa = (isoD, day) => { const r = new Date(isoD + 'T00:00:00Z'); const c = new Date(Date.UTC(r.getUTCFullYear(), r.getUTCMonth(), clampDay(day))); if (c < r) c.setUTCMonth(c.getUTCMonth() + 1); return c.toISOString().slice(0, 10); };
    d.tenancies.filter(t => t.status === 'active').forEach(ty => {
      const from = addMonths(todayISO(), -12);
      const lower = ty.start_date > from ? ty.start_date : from;
      let cur = psoa(lower, ty.pay_day), due = 0, g = 0;
      while (cur <= todayISO() && g++ < 400) { due += Number(ty.rent_pcm) || 0; cur = addMonths(cur, 1); }
      const paid = d.payments.filter(x => x.tenancy_id === ty.id).reduce((s, x) => s + Math.abs(Number(x.amount) || 0), 0);
      const bal = due - paid;
      if (bal > 1) {
        const lead = tenById[ty.lead_tenant_id] || {}; const p = propById[ty.property_id] || {};
        const monthsB = ty.rent_pcm ? bal / ty.rent_pcm : 0;
        push(monthsB >= 1 ? 'critical' : 'week', '£', 'Arrears · ' + gbp(bal), (lead.full_name || 'Tenant') + ' · ' + (p.address || '') + ' · ' + monthsB.toFixed(1) + ' months behind', 'ledger', monthsB >= 1 ? 'crit' : 'warn');
      }
    });

    // rent-review cycle
    d.tenancies.filter(t => t.status === 'active').forEach(ty => {
      const anchor = ty.rent_review_anchor || ty.last_rent_review_at || ty.start_date;
      if (!anchor) return;
      let ann = addMonths(anchor, 12);
      while (addMonths(ann, 6) < todayISO()) ann = addMonths(ann, 12);
      const openFrom = addMonths(ann, -3);
      const p = propById[ty.property_id] || {};
      if (todayISO() >= ann) push('week', '§', 'Rent review overdue', p.address + ' · anniversary ' + fmt(ann), 'properties', 'crit');
      else if (todayISO() >= openFrom) push('upcoming', '§', 'Rent review window open', p.address + ' · anniversary ' + fmt(ann), 'properties', 'warn');
    });

    // tax deadlines + open queries
    d.taxSubs.forEach(s => {
      if (s.stage === 'submitted') return;
      const owner = (owners || []).find(o => o.id === s.owner_id) || {};
      const openQ = (s.queries || []).filter(q => q.status === 'open').length;
      if (openQ) push('week', '▦', openQ + ' open tax quer' + (openQ === 1 ? 'y' : 'ies'), (owner.name || 'Owner') + ' · ' + (s.period_label || s.period_key) + ' ' + s.tax_year, 'tax', 'warn');
      if (s.deadline) {
        const dd = days(s.deadline);
        if (dd < 0) push('critical', '▦', (s.period_label || s.period_key) + ' overdue', (owner.name || 'Owner') + ' · deadline ' + fmt(s.deadline), 'tax', 'crit');
        else if (dd <= 30) push('week', '▦', (s.period_label || s.period_key) + ' due in ' + dd + 'd', (owner.name || 'Owner') + ' · ' + fmt(s.deadline), 'tax', 'warn');
      }
    });

    // inspections — pending approvals + overdue schedule
    d.insps.filter(i => i.status === 'completed' && i.ticket_review === 'pending').forEach(i => {
      const p = propById[i.property_id] || {};
      push('week', '⌕', 'Inspection report awaiting approval', p.address + ' · completed ' + fmt(i.completed_at), 'inspections', 'warn');
    });
    d.props.forEach(p => {
      const last = d.insps.filter(i => i.property_id === p.id && i.status === 'completed').sort((a, b) => (b.completed_at || '').localeCompare(a.completed_at || ''))[0];
      const due = last ? addMonths(last.completed_at, p.type === 'hmo' ? 3 : 6) : todayISO();
      const dd = days(due);
      if (dd < 0) push('week', '⌕', 'Inspection overdue', p.address + ' · due ' + fmt(due), 'inspections', 'warn');
      else if (dd <= 14) push('upcoming', '⌕', 'Inspection due ' + fmt(due), p.address, 'inspections', 'warn');
    });

    return items;
  }

  // ── build DATED tasks for the month calendar (each carries an ISO date) ──
  function buildCalendar(d, owners) {
    const out = [];
    const propById = Object.fromEntries(d.props.map(p => [p.id, p]));
    const tenById = Object.fromEntries(d.tenants.map(t => [t.id, t]));
    const add = (date, icon, label, where, view, tone) => { if (date) out.push({ date: date.slice(0, 10), icon, label, where, view, tone }); };

    // certificate expiries (latest per property+type)
    const latest = {};
    d.certs.forEach(c => { if (!c.expiry_date) return; const k = c.property_id + '|' + c.type; if (!latest[k] || c.expiry_date > latest[k].expiry_date) latest[k] = c; });
    Object.values(latest).forEach(c => { const p = propById[c.property_id] || {}; const dd = days(c.expiry_date);
      add(c.expiry_date, '◉', (CERT_LBL[c.type] || c.type) + ' expires', p.address, 'compliance', dd < 0 ? 'crit' : dd <= 30 ? 'warn' : 'ok'); });

    // damp & mould statutory deadline (raised + 14d)
    d.tickets.filter(t => t.status !== 'resolved' && t.category === 'damp_mould').forEach(t => {
      const p = propById[t.property_id] || {}; const dl = addDays(t.raised_at, 14);
      add(dl, '⚒', 'Damp & mould deadline', p.address + ' · ' + t.title, 'maintenance', days(dl) < 0 ? 'crit' : 'warn'); });

    // rent-review anniversaries (next) + served-notice effective dates
    d.tenancies.filter(t => t.status === 'active').forEach(ty => {
      const anchor = ty.rent_review_anchor || ty.last_rent_review_at || ty.start_date;
      if (anchor) { let ann = addMonths(anchor, 12); while (ann < todayISO()) ann = addMonths(ann, 12);
        const p = propById[ty.property_id] || {}; add(ann, '§', 'Rent review anniversary', p.address, 'properties', 'ok'); }
      if (ty.end_date) { const p = propById[ty.property_id] || {}; const lead = tenById[ty.lead_tenant_id] || {};
        add(ty.end_date, '⏏', 'Tenancy ends', (lead.full_name || 'Tenant') + ' · ' + (p.address || ''), 'properties', days(ty.end_date) < 0 ? 'none' : 'warn'); }
    });
    d.notices && d.notices.forEach(n => { if (n.status && ['served', 'acknowledged'].includes(n.status) && n.effective_date) {
      const p = propById[n.property_id] || {}; add(n.effective_date, '£', 'Rent increase effective', p.address, 'properties', 'warn'); } });

    // tax deadlines
    d.taxSubs.forEach(s => { if (s.stage === 'submitted' || !s.deadline) return;
      const owner = (owners || []).find(o => o.id === s.owner_id) || {};
      add(s.deadline, '▦', (s.period_label || s.period_key) + ' tax deadline', owner.name || 'Owner', 'tax', days(s.deadline) < 0 ? 'crit' : 'warn'); });

    // inspection schedule (next due per property) + pending approvals
    d.props.forEach(p => {
      const last = d.insps.filter(i => i.property_id === p.id && i.status === 'completed').sort((a, b) => (b.completed_at || '').localeCompare(a.completed_at || ''))[0];
      const due = last ? addMonths(last.completed_at, p.type === 'hmo' ? 3 : 6) : todayISO();
      add(due, '⌕', (p.type === 'hmo' ? 'HMO ' : '') + 'inspection due', p.address, 'inspections', days(due) < 0 ? 'warn' : 'ok'); });

    return out;
  }

  const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  const WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

  // ── month calendar ───────────────────────────────────────────────────────
  function CalendarView({ items, go }) {
    const now = new Date();
    const [cursor, setCursor] = useState({ y: now.getFullYear(), m: now.getMonth() });
    const [openDay, setOpenDay] = useState(null);
    const byDate = {};
    items.forEach(it => { (byDate[it.date] = byDate[it.date] || []).push(it); });

    const first = new Date(Date.UTC(cursor.y, cursor.m, 1));
    const startDow = (first.getUTCDay() + 6) % 7;   // Mon=0
    const daysInMonth = new Date(Date.UTC(cursor.y, cursor.m + 1, 0)).getUTCDate();
    const todayStr = todayISO();
    const cells = [];
    for (let i = 0; i < startDow; i++) cells.push(null);
    for (let dnum = 1; dnum <= daysInMonth; dnum++) {
      const iso = cursor.y + '-' + String(cursor.m + 1).padStart(2, '0') + '-' + String(dnum).padStart(2, '0');
      cells.push({ dnum, iso, items: byDate[iso] || [] });
    }
    while (cells.length % 7 !== 0) cells.push(null);
    const monthItems = items.filter(it => it.date.slice(0, 7) === cursor.y + '-' + String(cursor.m + 1).padStart(2, '0'));
    const step = (n) => setCursor(c => { let m = c.m + n, y = c.y; if (m < 0) { m = 11; y--; } if (m > 11) { m = 0; y++; } return { y, m }; });
    const toneDot = (t) => 'cal-dot cal-' + (t || 'ok');

    return <div className="card cal-card">
      <div className="cal-head">
        <div className="cal-title"><strong>{MONTHS[cursor.m]} {cursor.y}</strong><span className="pmd-mono cal-count">{monthItems.length} task{monthItems.length === 1 ? '' : 's'}</span></div>
        <div className="cal-nav">
          <button className="btn btn-ghost btn-sm" onClick={() => step(-1)}>←</button>
          <button className="btn btn-ghost btn-sm" onClick={() => setCursor({ y: now.getFullYear(), m: now.getMonth() })}>Today</button>
          <button className="btn btn-ghost btn-sm" onClick={() => step(1)}>→</button>
        </div>
      </div>
      <div className="cal-grid cal-dow">{WEEKDAYS.map(w => <div key={w} className="cal-dow-cell">{w}</div>)}</div>
      <div className="cal-grid">
        {cells.map((c, i) => c === null ? <div key={i} className="cal-cell cal-empty"></div>
          : <div key={i} className={'cal-cell' + (c.iso === todayStr ? ' cal-today' : '') + (c.items.length ? ' cal-has' : '')} onClick={() => c.items.length && setOpenDay(c)}>
            <div className="cal-dnum">{c.dnum}</div>
            <div className="cal-items">
              {c.items.slice(0, 3).map((it, j) => <div key={j} className="cal-item" title={it.label + ' · ' + (it.where || '')}><span className={toneDot(it.tone)}></span><span className="cal-item-l">{it.label}</span></div>)}
              {c.items.length > 3 && <div className="cal-more">+{c.items.length - 3} more</div>}
            </div>
          </div>)}
      </div>
      {monthItems.length === 0 && <div className="cal-empty-note">Nothing scheduled this month. Use ← → to look ahead.</div>}

      {openDay && <div className="modal-scrim" onClick={() => setOpenDay(null)}><div className="modal" style={{ maxWidth: 460 }} onClick={e => e.stopPropagation()}>
        <div className="modal-head"><div><div className="pmd-mono" style={{ fontSize: 11, color: 'var(--brand-deep)' }}>{fmt(openDay.iso).toUpperCase()}</div><h2>{openDay.items.length} task{openDay.items.length === 1 ? '' : 's'}</h2></div><button className="x" onClick={() => setOpenDay(null)}>✕</button></div>
        <div style={{ padding: '4px 10px 18px' }}>
          {openDay.items.map((it, j) => <div className="row clickable" key={j} onClick={() => { setOpenDay(null); go && go(it.view); }}>
            <span className={'hdot ' + (it.tone === 'crit' ? 'crit' : it.tone === 'warn' ? 'warn' : 'ok')}></span>
            <span className="avatar" style={{ background: 'var(--surface-2)', color: 'var(--ink-soft)', fontSize: 14 }}>{it.icon}</span>
            <div className="main"><div className="t">{it.label}</div><div className="s">{it.where}</div></div>
            <span style={{ color: 'var(--ink-faint)' }}>→</span>
          </div>)}
        </div>
      </div></div>}
    </div>;
  }

  // ── alerts page ───────────────────────────────────────────────────────────
  function AlertsHub({ sb, account, go }) {
    const d = useAll(sb);
    const [owners, setOwners] = useState([]);
    const [view, setView] = useState(() => { try { return localStorage.getItem('pm-alerts-view') || 'list'; } catch { return 'list'; } });
    const pickView = (v) => { setView(v); try { localStorage.setItem('pm-alerts-view', v); } catch (e) {} };
    useEffect(() => { sb.from('owners').select('id,name').then(({ data }) => setOwners(data || [])); }, [sb]);
    if (!d) return <div className="card card-pad"><Spin /></div>;
    const items = buildAlerts(d, owners);
    const calItems = buildCalendar(d, owners);
    const tiers = [
      { k: 'critical', label: 'Critical — act today', tone: 'crit' },
      { k: 'week', label: 'This week', tone: 'warn' },
      { k: 'upcoming', label: 'Upcoming', tone: 'ok' },
    ];
    return <React.Fragment>
      <CalStyles />
      <div className="pagehead" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 14, flexWrap: 'wrap' }}>
        <div><h1>Alerts</h1><p>{view === 'calendar' ? 'Plan ahead — every dated task across the portfolio, month by month.' : 'Everything that needs attention, across every module — worst first.'}</p></div>
        <div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
          <div className="pm-seg">
            <button className={'pm-seg-btn' + (view === 'list' ? ' on' : '')} onClick={() => pickView('list')}>List</button>
            <button className={'pm-seg-btn' + (view === 'calendar' ? ' on' : '')} onClick={() => pickView('calendar')}>Calendar</button>
          </div>
          {view === 'list' && <div className="summary" style={{ margin: 0 }}>
            <div className={'sumcard' + (items.filter(i => i.tier === 'critical').length ? ' bad' : ' ok')}><div className="n">{items.filter(i => i.tier === 'critical').length}</div><div className="l">Critical</div></div>
            <div className="sumcard warn"><div className="n">{items.filter(i => i.tier === 'week').length}</div><div className="l">This week</div></div>
          </div>}
        </div>
      </div>

      {view === 'calendar' ? <CalendarView items={calItems} go={go} />
        : <React.Fragment>
        {items.length === 0 && <div className="card"><div className="empty"><div className="ico">✓</div><h3>All clear</h3><p>Nothing needs attention right now — renewals and deadlines will appear here as they approach.</p></div></div>}
        {tiers.map(t => {
          const list = items.filter(i => i.tier === t.k);
          if (!list.length) return null;
          return <div className="card" key={t.k} style={{ marginBottom: 16 }}>
            <div className="card-head"><div><h3>{t.label}</h3><div className="sub">{list.length} item{list.length === 1 ? '' : 's'}</div></div></div>
            {list.map((it, i) => <div className="row clickable" key={i} onClick={() => go && go(it.view)}>
              <span className={'hdot ' + it.dot}></span>
              <span className="avatar" style={{ background: 'var(--surface-2)', color: 'var(--ink-soft)', fontSize: 14 }}>{it.icon}</span>
              <div className="main"><div className="t">{it.title}</div><div className="s">{it.sub}</div></div>
              <span style={{ color: 'var(--ink-faint)' }}>→</span>
            </div>)}
          </div>;
        })}
      </React.Fragment>}
    </React.Fragment>;
  }

  function CalStyles() {
    return <style>{`
    .cal-card { padding: 16px 18px; }
    .cal-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; flex-wrap: wrap; gap: 10px; }
    .cal-title { display: flex; align-items: baseline; gap: 10px; } .cal-title strong { font-size: 18px; }
    .cal-count { font-size: 11px; color: var(--ink-faint); }
    .cal-nav { display: flex; gap: 6px; }
    .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
    .cal-dow { margin-bottom: 6px; }
    .cal-dow-cell { font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: .05em; color: var(--ink-faint); text-align: center; padding: 2px 0; }
    .cal-cell { min-height: 84px; border: 1px solid var(--line-soft); border-radius: 9px; padding: 6px 7px; background: var(--surface); display: flex; flex-direction: column; gap: 4px; }
    .cal-cell.cal-empty { border: 0; background: transparent; }
    .cal-cell.cal-has { cursor: pointer; } .cal-cell.cal-has:hover { border-color: var(--ink-faint); box-shadow: var(--shadow-sm); }
    .cal-cell.cal-today { border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
    .cal-dnum { font-size: 12px; font-weight: 700; color: var(--ink-soft); }
    .cal-cell.cal-today .cal-dnum { color: var(--ink); }
    .cal-items { display: flex; flex-direction: column; gap: 3px; overflow: hidden; }
    .cal-item { display: flex; align-items: center; gap: 5px; font-size: 10.5px; line-height: 1.2; }
    .cal-item-l { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-soft); }
    .cal-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
    .cal-dot.cal-crit { background: var(--red); } .cal-dot.cal-warn { background: var(--amber); } .cal-dot.cal-ok { background: var(--ok); } .cal-dot.cal-none { background: var(--ink-faint); }
    .cal-more { font-size: 9.5px; color: var(--ink-faint); font-weight: 600; }
    .cal-empty-note { text-align: center; color: var(--ink-faint); font-size: 13px; padding: 16px 0 4px; }
    @media (max-width: 640px) { .cal-cell { min-height: 60px; } .cal-item-l { display: none; } .cal-item { gap: 0; } }
    `}</style>;
  }

  // ── activity log page ─────────────────────────────────────────────────────
  function ActivityLog({ sb, account }) {
    const d = useAll(sb);
    const [kind, setKind] = useState('all');
    if (!d) return <div className="card card-pad"><Spin /></div>;
    const propById = Object.fromEntries(d.props.map(p => [p.id, p]));
    const tenancyById = Object.fromEntries(d.tenancies.map(t => [t.id, t]));
    const events = [];
    d.log.forEach(a => events.push({ at: a.at, kind: 'admin', who: a.who, msg: a.action + (a.details && typeof a.details === 'string' ? ' · ' + a.details : '') }));
    d.certs.forEach(c => { if (c.created_at) events.push({ at: c.created_at, kind: 'compliance', who: null, msg: 'Filed ' + (CERT_LBL[c.type] || c.type) + (c.cert_number ? ' (' + c.cert_number + ')' : '') + ' · ' + ((propById[c.property_id] || {}).address || '') }); });
    d.payments.forEach(p => { const ty = tenancyById[p.tenancy_id] || {}; events.push({ at: p.created_at || p.date, kind: 'money', who: null, msg: (p.kind === 'credit' ? 'Credit ' : 'Payment ') + gbp(p.amount) + (p.ref ? ' · ' + p.ref : '') + ' · ' + ((propById[ty.property_id] || {}).address || '') }); });
    d.tickets.forEach(t => {
      events.push({ at: t.raised_at, kind: 'maintenance', who: null, msg: 'Ticket raised: ' + t.title + ' · ' + ((propById[t.property_id] || {}).address || '') });
      if (t.resolved_at) events.push({ at: t.resolved_at, kind: 'maintenance', who: null, msg: 'Ticket resolved: ' + t.title + (Number(t.cost) > 0 ? ' · ' + gbp(t.cost) : '') });
    });
    d.insps.forEach(i => { if (i.completed_at) events.push({ at: i.completed_at, kind: 'inspections', who: i.inspector_name, msg: 'Inspection completed · ' + ((propById[i.property_id] || {}).address || '') }); });
    const shown = events.filter(e => e.at && (kind === 'all' || e.kind === kind)).sort((a, b) => (b.at || '').localeCompare(a.at || '')).slice(0, 100);
    const KINDS = [['all', 'All'], ['compliance', 'Compliance'], ['money', 'Money'], ['maintenance', 'Maintenance'], ['inspections', 'Inspections'], ['admin', 'Admin']];
    return <React.Fragment>
      <div className="pagehead"><h1>Activity log</h1><p>Everything that happened across the portfolio, newest first.</p></div>
      <div className="pm-toolbar">{KINDS.map(([k, l]) => <button key={k} className={'btn btn-sm ' + (kind === k ? 'btn-primary' : 'btn-ghost')} onClick={() => setKind(k)}>{l}</button>)}</div>
      <div className="card" style={{ overflow: 'hidden' }}>
        {shown.length === 0 && <div className="empty"><div className="ico">🕑</div><h3>No activity yet</h3><p>Filing certificates, recording payments and resolving tickets will appear here.</p></div>}
        {shown.map((e, i) => <div className="row" key={i}>
          <span className="pmd-mono" style={{ fontSize: 11, color: 'var(--ink-faint)', minWidth: 120 }}>{new Date(e.at).toLocaleString('en-GB', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}</span>
          <div className="main"><div className="t" style={{ fontWeight: 500, fontSize: 13.5 }}>{e.msg}</div>{e.who && <div className="s">{e.who}</div>}</div>
          <span className="badge none">{e.kind}</span>
        </div>)}
      </div>
    </React.Fragment>;
  }

  window.PMAlertsHub = AlertsHub;
  window.PMActivityLog = ActivityLog;
})();
