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.
156 lines
4.2 KiB
TypeScript
156 lines
4.2 KiB
TypeScript
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>
|
|
)
|
|
}
|