Primer commit — OPSV Dashboard de siniestralidad vial

This commit is contained in:
2026-04-29 13:39:09 -03:00
commit ca7b159657
67 changed files with 12246 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts'
import { useChartTheme } from '../../hooks/useChartTheme'
const COLORES = {
Urbana: '#252C61',
Rural: '#E8881A',
'Sin datos': '#94A3B8',
}
const RADIAN = Math.PI / 180
function normalizarZona(valor) {
const texto = String(valor ?? '').trim().toLowerCase()
if (!texto) return 'Sin datos'
if (texto.includes('urb')) return 'Urbana'
if (texto.includes('rur')) return 'Rural'
return 'Sin datos'
}
export default function ZonaOcurrencia({ siniestros }) {
const { tooltipBg, tooltipBorder, tooltipLabel, tickColor } = useChartTheme()
const conteo = { Urbana: 0, Rural: 0, 'Sin datos': 0 }
siniestros.forEach((s) => {
const zona = normalizarZona(s.zona_ocurrencia)
conteo[zona] += 1
})
const data = Object.entries(conteo)
.map(([name, value]) => ({ name, value }))
.filter((item) => item.value > 0)
const total = data.reduce((acc, d) => acc + d.value, 0)
const renderLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent }) => {
if (percent < 0.04) return null
const r = innerRadius + (outerRadius - innerRadius) * 0.5
const x = cx + r * Math.cos(-midAngle * RADIAN)
const y = cy + r * Math.sin(-midAngle * RADIAN)
return (
<text
x={x}
y={y}
fill="white"
textAnchor="middle"
dominantBaseline="central"
style={{ fontSize: '13px', fontWeight: 700 }}
>
{`${(percent * 100).toFixed(1)}%`}
</text>
)
}
return (
<div className="rounded-[28px] border border-opsv-border bg-opsv-surface p-6 shadow-sm">
<ResponsiveContainer width="100%" height={260}>
<PieChart>
<Pie
data={data}
cx="50%"
cy="50%"
outerRadius={100}
innerRadius={45}
dataKey="value"
labelLine={false}
label={renderLabel}
>
{data.map((entry, i) => (
<Cell key={i} fill={COLORES[entry.name] || '#8E44AD'} />
))}
</Pie>
<Tooltip
contentStyle={{
background: tooltipBg,
border: `1px solid ${tooltipBorder}`,
borderRadius: 16,
boxShadow: '0 10px 30px rgba(0,0,0,0.08)',
}}
labelStyle={{ color: tooltipLabel, fontWeight: 700 }}
itemStyle={{ color: tickColor }}
formatter={(val, name) => [
`${val} (${total ? ((val / total) * 100).toFixed(1) : 0}%)`,
name,
]}
/>
<Legend
iconType="circle"
iconSize={10}
formatter={(value) => (
<span style={{ fontSize: 12, color: tickColor }}>{value}</span>
)}
/>
</PieChart>
</ResponsiveContainer>
</div>
)
}