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:
FlotillasGPS Developer
2026-01-21 08:18:00 +00:00
commit 51d78bacf4
248 changed files with 50171 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
import clsx from 'clsx'
interface FuelGaugeProps {
value: number // 0-100
maxValue?: number
label?: string
size?: 'sm' | 'md' | 'lg'
showPercentage?: boolean
className?: string
}
export default function FuelGauge({
value,
maxValue = 100,
label = 'Combustible',
size = 'md',
showPercentage = true,
className,
}: FuelGaugeProps) {
const percentage = Math.min(100, Math.max(0, (value / maxValue) * 100))
// Color based on level
const getColor = () => {
if (percentage <= 20) return { bg: 'bg-error-500', text: 'text-error-400' }
if (percentage <= 40) return { bg: 'bg-warning-500', text: 'text-warning-400' }
return { bg: 'bg-success-500', text: 'text-success-400' }
}
const colors = getColor()
const sizeStyles = {
sm: { height: 'h-2', text: 'text-xs', icon: 'w-4 h-4' },
md: { height: 'h-3', text: 'text-sm', icon: 'w-5 h-5' },
lg: { height: 'h-4', text: 'text-base', icon: 'w-6 h-6' },
}
const styles = sizeStyles[size]
return (
<div className={clsx('space-y-2', className)}>
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<svg
className={clsx(styles.icon, colors.text)}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5"
/>
</svg>
<span className={clsx(styles.text, 'text-slate-400')}>{label}</span>
</div>
{showPercentage && (
<span className={clsx(styles.text, 'font-bold', colors.text)}>
{percentage.toFixed(0)}%
</span>
)}
</div>
{/* Gauge bar */}
<div className={clsx('w-full bg-slate-700 rounded-full overflow-hidden', styles.height)}>
<div
className={clsx(
'h-full rounded-full transition-all duration-500',
colors.bg
)}
style={{ width: `${percentage}%` }}
/>
</div>
{/* Markers */}
<div className="flex justify-between text-xs text-slate-600">
<span>E</span>
<span>1/4</span>
<span>1/2</span>
<span>3/4</span>
<span>F</span>
</div>
</div>
)
}
// Circular gauge variant
interface CircularGaugeProps {
value: number
maxValue?: number
label?: string
size?: number
strokeWidth?: number
className?: string
}
export function CircularGauge({
value,
maxValue = 100,
label,
size = 120,
strokeWidth = 8,
className,
}: CircularGaugeProps) {
const percentage = Math.min(100, Math.max(0, (value / maxValue) * 100))
const radius = (size - strokeWidth) / 2
const circumference = 2 * Math.PI * radius
const strokeDashoffset = circumference - (percentage / 100) * circumference
// Color based on level
const getColor = () => {
if (percentage <= 20) return '#ef4444'
if (percentage <= 40) return '#eab308'
return '#22c55e'
}
return (
<div className={clsx('relative inline-flex', className)}>
<svg width={size} height={size} className="-rotate-90">
{/* Background circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="#334155"
strokeWidth={strokeWidth}
/>
{/* Progress circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={getColor()}
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
className="transition-all duration-500"
/>
</svg>
{/* Center content */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center">
<p className="text-2xl font-bold text-white">{percentage.toFixed(0)}%</p>
{label && <p className="text-xs text-slate-500">{label}</p>}
</div>
</div>
</div>
)
}