// frontend\src\pages\operations\milestone-cameras\MilestoneCamerasPage.tsx import { useEffect, useState } from "react"; import { MicrophoneIcon, VideoCameraIcon, } from "@heroicons/react/24/outline"; import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"; import LoadingSpinner from "../../../components/LoadingSpinner"; import Tables, { type TableColumn } from "../../../components/Tables"; import { Badge } from "../../../components/Badge"; import EmptyState from "../../../components/EmptyState"; import { useNotifications } from "../../../components/Notifications"; const API_URL = import.meta.env.VITE_API_URL ?? "http://localhost:8080"; type MilestoneCameraListItem = { id: string; name: string; displayName: string; type: "camera" | "microphone"; enabled: boolean; recordingEnabled: boolean; recordingStorageId: string; hardwareId: string; }; type StorageSimple = { id: string; name: string; displayName: string; }; type Props = { canWrite?: boolean; }; export default function MilestoneCamerasPage({ canWrite }: Props) { const { notify } = useNotifications(); const [devices, setDevices] = useState([]); const [storageMap, setStorageMap] = useState>({}); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [search, setSearch] = useState(""); useEffect(() => { void Promise.all([loadDevices(), loadStorages()]); }, []); async function loadDevices() { setIsLoading(true); setError(null); try { const response = await fetch(`${API_URL}/admin/milestone/cameras`, { credentials: "include", }); if (!response.ok) { const data = (await response.json().catch(() => null)) as { error?: string; } | null; setError(data?.error ?? "Geräte konnten nicht geladen werden"); return; } const data = (await response.json()) as { devices: MilestoneCameraListItem[]; }; setDevices(data.devices ?? []); } catch { setError("Verbindung zum Server fehlgeschlagen"); } finally { setIsLoading(false); } } async function loadStorages() { try { const response = await fetch(`${API_URL}/admin/milestone/storages`, { credentials: "include", }); if (!response.ok) return; const data = (await response.json()) as { storages?: { id: string; name: string; displayName: string }[]; }; const map: Record = {}; for (const s of data.storages ?? []) { map[s.id] = { id: s.id, name: s.name, displayName: s.displayName }; } setStorageMap(map); } catch { // silent – storage names are cosmetic } } async function handleToggle( device: MilestoneCameraListItem, field: "enabled" | "recordingEnabled", ) { const newValue = !device[field]; setDevices((current) => current.map((d) => d.id === device.id ? { ...d, [field]: newValue } : d, ), ); const collection = device.type === "camera" ? "cameras" : "microphones"; try { const response = await fetch( `${API_URL}/admin/milestone/devices/${collection}/${device.id}`, { method: "PATCH", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ [field]: newValue }), }, ); if (!response.ok) { const data = (await response.json().catch(() => null)) as { error?: string; } | null; setDevices((current) => current.map((d) => d.id === device.id ? { ...d, [field]: !newValue } : d, ), ); notify.error(data?.error ?? "Gerät konnte nicht aktualisiert werden"); } } catch { setDevices((current) => current.map((d) => d.id === device.id ? { ...d, [field]: !newValue } : d, ), ); notify.error("Verbindung fehlgeschlagen"); } } const searchLower = search.trim().toLowerCase(); const filtered = searchLower ? devices.filter( (d) => d.name.toLowerCase().includes(searchLower) || d.displayName.toLowerCase().includes(searchLower), ) : devices; const cameraCount = filtered.filter((d) => d.type === "camera").length; const micCount = filtered.filter((d) => d.type === "microphone").length; const columns: TableColumn[] = [ { key: "name", header: "Name", isPrimary: true, render: (row) => (
{row.type === "camera" ? ( ) : ( )}
{row.name}
{row.displayName && row.displayName !== row.name && (
{row.displayName}
)}
), }, { key: "enabled", header: "Status", hideOnMobile: "sm", render: (row) => canWrite ? ( ) : ( {row.enabled ? "Aktiv" : "Inaktiv"} ), }, { key: "recordingEnabled", header: "Aufnahme", hideOnMobile: "md", render: (row) => canWrite ? ( ) : ( {row.recordingEnabled ? "Aktiv" : "Inaktiv"} ), }, { key: "recordingStorageId", header: "Speicher", hideOnMobile: "lg", render: (row) => { const storage = storageMap[row.recordingStorageId]; if (storage) { return ( {storage.displayName || storage.name} ); } if (row.recordingStorageId) { return ( {row.recordingStorageId} ); } return ; }, }, ]; return (

Milestone-Geräte

{!isLoading && !error && (

{cameraCount} Kamera{cameraCount !== 1 ? "s" : ""},{" "} {micCount} Mikrofon{micCount !== 1 ? "e" : ""}

)}
setSearch(e.target.value)} className="w-full rounded-lg border border-gray-300 bg-white py-2 pl-9 pr-3 text-sm text-gray-900 placeholder-gray-400 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500" />
{isLoading && (
)} {!isLoading && error && (
{error}
)} {!isLoading && !error && filtered.length === 0 && ( )} {!isLoading && !error && filtered.length > 0 && ( row.id} variant="border" /> )}
); }