Files
ATLAS/frontend/src/components/video/CamaraCard.tsx
FlotillasGPS Developer 51d78bacf4 FlotillasGPS - Sistema completo de monitoreo de flotillas GPS
Sistema completo para monitoreo y gestion de flotas de vehiculos con:
- Backend FastAPI con PostgreSQL/TimescaleDB
- Frontend React con TypeScript y TailwindCSS
- App movil React Native con Expo
- Soporte para dispositivos GPS, Meshtastic y celulares
- Video streaming en vivo con MediaMTX
- Geocercas, alertas, viajes y reportes
- Autenticacion JWT y WebSockets en tiempo real

Documentacion completa y guias de usuario incluidas.
2026-01-21 08:18:00 +00:00

149 lines
4.5 KiB
TypeScript

import { Link } from 'react-router-dom'
import {
VideoCameraIcon,
SignalIcon,
PlayIcon,
StopIcon,
} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import { Camara } from '@/types'
import { StatusBadge } from '@/components/ui/Badge'
import Card from '@/components/ui/Card'
import Button from '@/components/ui/Button'
interface CamaraCardProps {
camara: Camara
onStartRecording?: () => void
onStopRecording?: () => void
onView?: () => void
showActions?: boolean
}
export default function CamaraCard({
camara,
onStartRecording,
onStopRecording,
onView,
showActions = true,
}: CamaraCardProps) {
const getEstadoConfig = () => {
switch (camara.estado) {
case 'online':
return { status: 'online' as const, label: 'En linea' }
case 'grabando':
return { status: 'online' as const, label: 'Grabando' }
case 'offline':
return { status: 'offline' as const, label: 'Sin conexion' }
case 'error':
return { status: 'error' as const, label: 'Error' }
default:
return { status: 'offline' as const, label: 'Desconocido' }
}
}
const estadoConfig = getEstadoConfig()
return (
<Card padding="md" hover>
{/* Header */}
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<div
className={clsx(
'w-10 h-10 rounded-lg flex items-center justify-center',
camara.estado === 'online' || camara.estado === 'grabando'
? 'bg-success-500/20'
: 'bg-slate-700'
)}
>
<VideoCameraIcon
className={clsx(
'w-5 h-5',
camara.estado === 'online' || camara.estado === 'grabando'
? 'text-success-400'
: 'text-slate-400'
)}
/>
</div>
<div>
<h3 className="text-sm font-semibold text-white">{camara.nombre}</h3>
<p className="text-xs text-slate-500 capitalize">{camara.posicion}</p>
</div>
</div>
<StatusBadge
status={estadoConfig.status}
label={estadoConfig.label}
size="xs"
showDot={camara.estado === 'grabando'}
/>
</div>
{/* Info */}
<div className="space-y-2 mb-3 text-sm">
{camara.vehiculo && (
<div className="flex justify-between">
<span className="text-slate-500">Vehiculo:</span>
<span className="text-slate-300">{camara.vehiculo.placa}</span>
</div>
)}
{camara.resolucion && (
<div className="flex justify-between">
<span className="text-slate-500">Resolucion:</span>
<span className="text-slate-300">{camara.resolucion}</span>
</div>
)}
{camara.fps && (
<div className="flex justify-between">
<span className="text-slate-500">FPS:</span>
<span className="text-slate-300">{camara.fps}</span>
</div>
)}
</div>
{/* Recording indicator */}
{camara.estado === 'grabando' && (
<div className="mb-3 p-2 bg-error-500/10 border border-error-500/20 rounded-lg flex items-center gap-2">
<span className="w-2 h-2 bg-error-500 rounded-full animate-pulse" />
<span className="text-xs text-error-400 font-medium">Grabando...</span>
</div>
)}
{/* Actions */}
{showActions && (
<div className="flex items-center gap-2 pt-3 border-t border-slate-700/50">
<Button
size="sm"
variant="primary"
leftIcon={<PlayIcon className="w-4 h-4" />}
onClick={onView}
disabled={camara.estado === 'offline' || camara.estado === 'error'}
>
Ver
</Button>
{camara.estado === 'grabando' ? (
<Button
size="sm"
variant="danger"
leftIcon={<StopIcon className="w-4 h-4" />}
onClick={onStopRecording}
>
Detener
</Button>
) : (
<Button
size="sm"
variant="outline"
leftIcon={<span className="w-2 h-2 bg-error-500 rounded-full" />}
onClick={onStartRecording}
disabled={camara.estado !== 'online'}
>
Grabar
</Button>
)}
</div>
)}
</Card>
)
}