import React, { useCallback, useEffect, useRef, useState } from 'react'; import { scannerApi, type ScannerAlarm } from '../../lib/api'; import { useNavigation } from '../../lib/navigation'; import { Modal } from '../ui/Modal'; import { PrimaryButton, SecondaryButton } from '../ui/form'; import { useToast } from '../Toast'; import { AlarmBell, SOURCE_LABELS, formatAge, playAlarmSound } from './scannerUi'; /* * Alarm des Scanners: Treffer mit Alarm-Schlagwort melden sich mit Ton und * Pop-up - egal, welche Ansicht offen ist (deshalb hängt der Wächter in App, * nicht im Scanner-Dashboard; beim perinco-Scanner hörte man den Ton nur dort). * * Abgefragt wird jede Minute. Der Scanner läuft stündlich, ein Fund meldet * sich also spätestens eine Minute nach dem Lauf. Quittieren gilt für alle * Benutzer (serverseitig `alarm_ack`). */ const POLL_MS = 60_000; export const AlarmWatcher: React.FC = () => { const { toast } = useToast(); const { setView } = useNavigation(); const [alarms, setAlarms] = useState([]); /** Welche Alarme schon geklingelt haben - neue klingeln erneut. */ const rung = useRef(new Set()); const check = useCallback(async () => { try { const { alarms: list } = await scannerApi.alarms(); setAlarms(list); if (list.some((alarm) => !rung.current.has(alarm.id))) { list.forEach((alarm) => rung.current.add(alarm.id)); playAlarmSound(); } } catch { /* kein Netz oder Sitzung abgelaufen - beim nächsten Mal */ } }, []); useEffect(() => { void check(); const timer = setInterval(() => void check(), POLL_MS); return () => clearInterval(timer); }, [check]); if (alarms.length === 0) return null; const acknowledge = async (goToScanner = false) => { const ids = alarms.map((alarm) => alarm.id); setAlarms([]); try { await scannerApi.ackAlarms(ids); } catch (err) { toast(err instanceof Error ? err.message : 'Quittieren fehlgeschlagen.', 'error'); } if (goToScanner) setView('Scanner'); }; return ( void acknowledge()} title={ {alarms.length === 1 ? 'Alarm-Treffer im Scanner' : `${alarms.length} Alarm-Treffer im Scanner`} } footer={ <> void acknowledge()}> Gesehen void acknowledge(true)}> Zum Scanner } >
    {alarms.map((alarm) => (
  • {alarm.title} ↗

    {[alarm.company, alarm.location, SOURCE_LABELS[alarm.source]].filter(Boolean).join(' · ')} ·{' '} {formatAge(alarm.published_at)}

    Agent {alarm.agent_name} · gefunden:{' '} {alarm.hits.map((hit) => ( {hit} ))}

  • ))}
); };