Primer commit — OPSV Dashboard de siniestralidad vial
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user