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:
155
frontend/src/components/charts/FuelGauge.tsx
Normal file
155
frontend/src/components/charts/FuelGauge.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user