import { useEffect, useRef, useState } from 'react'
import { api, streamArrivals } from '../api.js'
import { ago, Loading, Problem } from './Sites.jsx'
import Shot from './Shot.jsx'
// Who just walked in, pushed as it happens.
//
// The list is capped rather than unbounded: this screen is left open on a wall
// display for days, and an ever-growing array of arrivals — each holding an
// image URL — is a tab that eventually falls over. Older arrivals are in the
// reports; this view is about the last few minutes.
const KEEP = 60
export default function Live() {
const [arrivals, setArrivals] = useState([])
const [status, setStatus] = useState('connecting')
const [error, setError] = useState(null)
const seen = useRef(new Set())
useEffect(() => {
const ac = new AbortController()
let stop = () => {}
;(async () => {
try {
// One poll for the recent window, so the screen is never blank while
// waiting for the next person to walk through a door.
const page = await api.arrivals({ limit: 30 })
const rows = page.arrivals || []
rows.forEach(a => seen.current.add(a.visit_id))
setArrivals(rows.slice().reverse())
setStatus('live')
stop = streamArrivals({
cursor: page.cursor,
signal: ac.signal,
onPage: (p) => {
const fresh = (p.arrivals || []).filter(a => !seen.current.has(a.visit_id))
if (!fresh.length) return
fresh.forEach(a => seen.current.add(a.visit_id))
setStatus('live')
// Newest first on screen; the feed delivers oldest first.
setArrivals(prev => [...fresh.reverse(), ...prev].slice(0, KEEP))
},
onError: () => setStatus('reconnecting'),
})
} catch (err) {
setError(err)
}
})()
return () => { ac.abort(); stop() }
}, [])
if (error) return
{status === 'live' ? 'Connected' : 'Reconnecting…'}
{' · '}last {KEEP} arrivals
Live
Arrivals appear here the moment a camera recognises someone.