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.
This commit is contained in:
148
frontend/src/components/video/CamaraCard.tsx
Normal file
148
frontend/src/components/video/CamaraCard.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user