71 lines
2.8 KiB
TypeScript
71 lines
2.8 KiB
TypeScript
'use client'
|
||
import { useEffect, useState } from 'react'
|
||
import { isSuppressed, type HeatmapData } from '@/lib/dashboard-types'
|
||
import { queries } from '@/lib/dashboard-queries'
|
||
import type { DashboardFilters } from '@/lib/dashboard-types'
|
||
import { WidgetShell } from './widget-shell'
|
||
import { HORAS_ORDER, AUSENCIAS_ORDER } from '@/lib/dashboard-utils'
|
||
|
||
const SHORT_AUSENCIAS = ['Nunca','Pocas (1-3)','Varias (4-10)','Con freq.']
|
||
|
||
export function HeatmapWidget({ surveyId, filters }: { surveyId: string; filters: DashboardFilters }) {
|
||
const [data, setData] = useState<HeatmapData | null>(null)
|
||
const [sup, setSup] = useState(false)
|
||
|
||
useEffect(() => {
|
||
setData(null); setSup(false)
|
||
queries.heatmap(surveyId, filters)
|
||
.then(d => { if (isSuppressed(d)) setSup(true); else setData(d as HeatmapData) })
|
||
.catch(() => {})
|
||
}, [surveyId, JSON.stringify(filters)])
|
||
|
||
const status = !data && !sup ? 'loading' : sup ? 'suppressed' : 'ok'
|
||
|
||
function cellColor(n: number | null): string {
|
||
if (n === null) return '#1e2d42'
|
||
const max = Math.max(...(data?.data.map(c => c.n ?? 0) ?? [1]), 1)
|
||
const t = n / max
|
||
return `rgba(67, 187, 200, ${0.15 + t * 0.85})`
|
||
}
|
||
|
||
const cellMap = new Map((data?.data ?? []).map(c => [`${c.horas}|${c.ausencias}`, c]))
|
||
|
||
return (
|
||
<WidgetShell title="🔥 Horas de cuidado × Ausencias laborales" status={status} n={data?.n_total} colSpan={6}>
|
||
{data && (
|
||
<div className="db-heatmap-scroll" role="table" aria-label="Heatmap de intensidad">
|
||
<div className="db-heatmap-grid"
|
||
style={{ gridTemplateColumns: `120px repeat(${AUSENCIAS_ORDER.length}, 1fr)` }}>
|
||
{/* Header row */}
|
||
<div role="columnheader" />
|
||
{SHORT_AUSENCIAS.map(h => (
|
||
<div key={h} className="db-heatmap-header" role="columnheader">{h}</div>
|
||
))}
|
||
{/* Data rows */}
|
||
{HORAS_ORDER.map(horas => (
|
||
<>
|
||
<div key={`row-${horas}`} className="db-heatmap-rowlabel" role="rowheader">{horas}</div>
|
||
{AUSENCIAS_ORDER.map(aus => {
|
||
const cell = cellMap.get(`${horas}|${aus}`)
|
||
const n = cell?.n ?? null
|
||
const sup2 = cell?.suppressed ?? (!cell)
|
||
return (
|
||
<div key={`${horas}|${aus}`}
|
||
className="db-heatmap-cell"
|
||
style={{ background: cellColor(n) }}
|
||
role="cell"
|
||
aria-label={`${horas} / ${aus}: ${sup2 ? 'suprimido' : `n=${n}`}`}
|
||
title={sup2 ? 'Datos insuficientes' : `n = ${n}`}>
|
||
{!sup2 && n !== null && <span>{n}</span>}
|
||
</div>
|
||
)
|
||
})}
|
||
</>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</WidgetShell>
|
||
)
|
||
}
|