CUB-196: CameraCard component with live SSE status display #3

Merged
overseer merged 8 commits from agent/hermes/CUB-196-cameracard into dev 2026-05-21 10:26:56 -04:00
Showing only changes of commit ad813cd206 - Show all commits
+54 -4
View File
@@ -1,6 +1,19 @@
import { Camera } from 'lucide-react' import { Camera, Radio } from 'lucide-react'
import { useSSE } from './hooks/useSSE'
import { useCameraStore } from './store/useCameraStore'
import { CameraCard } from './components'
function App() { function App() {
// Connect to SSE endpoint — auto-updates the camera store
useSSE()
// Subscribe to the camera store for reactivity.
// getCameras / getOnlineCount / getRecordingCount pull from live state.
const { getCameras, getOnlineCount, getRecordingCount } = useCameraStore()
const cameras = getCameras()
const onlineCount = getOnlineCount()
const recordingCount = getRecordingCount()
return ( return (
<div className="min-h-screen bg-rig-dark-900"> <div className="min-h-screen bg-rig-dark-900">
{/* Header */} {/* Header */}
@@ -14,21 +27,58 @@ function App() {
<span className="ml-2 rounded-full bg-rig-accent/10 px-2.5 py-0.5 text-xs font-medium text-rig-accent"> <span className="ml-2 rounded-full bg-rig-accent/10 px-2.5 py-0.5 text-xs font-medium text-rig-accent">
Dashboard Dashboard
</span> </span>
{/* Stats badges */}
<div className="ml-auto flex items-center gap-4">
{/* Online count */}
<span
className="inline-flex items-center gap-1.5 rounded-full bg-rig-dark-700/60 px-3 py-1 text-xs font-medium text-rig-dark-200"
title="Cameras online"
>
<span className="h-2 w-2 rounded-full bg-rig-success" />
{onlineCount} online
</span>
{/* Recording count */}
<span
className="inline-flex items-center gap-1.5 rounded-full bg-rig-dark-700/60 px-3 py-1 text-xs font-medium text-rig-dark-200"
title="Cameras recording"
>
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-rig-danger opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-rig-danger" />
</span>
{recordingCount} recording
</span>
</div>
</div> </div>
</div> </div>
</header> </header>
{/* Main Content */} {/* Main Content */}
<main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"> <main className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
{cameras.length === 0 ? (
/* Empty state */
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-rig-dark-600 bg-rig-dark-800/30 py-24 text-center"> <div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-rig-dark-600 bg-rig-dark-800/30 py-24 text-center">
<Camera className="mb-4 h-12 w-12 text-rig-dark-500" /> <span className="relative mb-4 inline-flex">
<Radio className="h-12 w-12 animate-pulse text-rig-accent" />
</span>
<h2 className="text-lg font-semibold text-rig-dark-200"> <h2 className="text-lg font-semibold text-rig-dark-200">
Dashboard Coming Soon Waiting for cameras&hellip;
</h2> </h2>
<p className="mt-2 max-w-sm text-sm text-rig-dark-400"> <p className="mt-2 max-w-sm text-sm text-rig-dark-400">
Camera monitoring and remote control interface under construction. Connect cameras to your RemoteRig server and they will appear here
automatically.
</p> </p>
</div> </div>
) : (
/* Camera grid */
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{cameras.map((camera) => (
<CameraCard key={camera.camera_id} camera={camera} />
))}
</div>
)}
</main> </main>
{/* Footer */} {/* Footer */}