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:
136
frontend/src/components/mapa/VehiculoPopup.tsx
Normal file
136
frontend/src/components/mapa/VehiculoPopup.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { format } from 'date-fns'
|
||||
import { es } from 'date-fns/locale'
|
||||
import {
|
||||
TruckIcon,
|
||||
MapPinIcon,
|
||||
ClockIcon,
|
||||
BoltIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
UserIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import clsx from 'clsx'
|
||||
import { Vehiculo } from '@/types'
|
||||
import { StatusBadge } from '@/components/ui/Badge'
|
||||
|
||||
interface VehiculoPopupProps {
|
||||
vehiculo: Vehiculo
|
||||
}
|
||||
|
||||
export default function VehiculoPopup({ vehiculo }: VehiculoPopupProps) {
|
||||
const { ubicacion, conductor } = vehiculo
|
||||
|
||||
// Format timestamp
|
||||
const lastUpdate = ubicacion?.timestamp
|
||||
? format(new Date(ubicacion.timestamp), "d MMM, HH:mm", { locale: es })
|
||||
: 'Sin datos'
|
||||
|
||||
// Movement status
|
||||
const getMovimientoStatus = () => {
|
||||
switch (vehiculo.movimiento) {
|
||||
case 'movimiento':
|
||||
return { label: 'En movimiento', status: 'online' as const }
|
||||
case 'detenido':
|
||||
return { label: 'Detenido', status: 'warning' as const }
|
||||
case 'ralenti':
|
||||
return { label: 'Ralenti', status: 'warning' as const }
|
||||
case 'sin_senal':
|
||||
return { label: 'Sin senal', status: 'offline' as const }
|
||||
default:
|
||||
return { label: 'Desconocido', status: 'offline' as const }
|
||||
}
|
||||
}
|
||||
|
||||
const movStatus = getMovimientoStatus()
|
||||
|
||||
return (
|
||||
<div className="p-1">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between gap-3 mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={clsx(
|
||||
'w-10 h-10 rounded-lg flex items-center justify-center',
|
||||
vehiculo.movimiento === 'movimiento'
|
||||
? 'bg-success-500/20'
|
||||
: vehiculo.movimiento === 'detenido'
|
||||
? 'bg-warning-500/20'
|
||||
: 'bg-slate-700'
|
||||
)}
|
||||
>
|
||||
<TruckIcon
|
||||
className={clsx(
|
||||
'w-5 h-5',
|
||||
vehiculo.movimiento === 'movimiento'
|
||||
? 'text-success-400'
|
||||
: vehiculo.movimiento === 'detenido'
|
||||
? 'text-warning-400'
|
||||
: 'text-slate-400'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white">{vehiculo.nombre}</h3>
|
||||
<p className="text-xs text-slate-500">{vehiculo.placa}</p>
|
||||
</div>
|
||||
</div>
|
||||
<StatusBadge status={movStatus.status} label={movStatus.label} size="xs" />
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 gap-2 mb-3">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<BoltIcon className="w-4 h-4 text-slate-500" />
|
||||
<span className="text-slate-400">Velocidad:</span>
|
||||
<span className="text-white font-medium">{vehiculo.velocidad || 0} km/h</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<ArrowTrendingUpIcon className="w-4 h-4 text-slate-500" />
|
||||
<span className="text-slate-400">Rumbo:</span>
|
||||
<span className="text-white font-medium">{vehiculo.rumbo || 0}°</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Location */}
|
||||
{ubicacion && (
|
||||
<div className="flex items-start gap-2 mb-3 p-2 bg-slate-800/50 rounded-lg">
|
||||
<MapPinIcon className="w-4 h-4 text-slate-500 mt-0.5 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs text-slate-300 truncate">
|
||||
{ubicacion.lat.toFixed(6)}, {ubicacion.lng.toFixed(6)}
|
||||
</p>
|
||||
<div className="flex items-center gap-1 mt-1">
|
||||
<ClockIcon className="w-3 h-3 text-slate-500" />
|
||||
<span className="text-xs text-slate-500">{lastUpdate}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Conductor */}
|
||||
{conductor && (
|
||||
<div className="flex items-center gap-2 mb-3 text-xs">
|
||||
<UserIcon className="w-4 h-4 text-slate-500" />
|
||||
<span className="text-slate-400">Conductor:</span>
|
||||
<span className="text-white">{conductor.nombre} {conductor.apellido}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-slate-700">
|
||||
<Link
|
||||
to={`/vehiculos/${vehiculo.id}`}
|
||||
className="flex-1 px-3 py-1.5 text-xs font-medium text-center text-white bg-accent-500 hover:bg-accent-600 rounded-lg transition-colors"
|
||||
>
|
||||
Ver detalles
|
||||
</Link>
|
||||
<Link
|
||||
to={`/viajes?vehiculo=${vehiculo.id}`}
|
||||
className="flex-1 px-3 py-1.5 text-xs font-medium text-center text-slate-300 hover:text-white bg-slate-700 hover:bg-slate-600 rounded-lg transition-colors"
|
||||
>
|
||||
Ver viajes
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user