Files
motor-de-encuestas/components/dashboard/intensidad-productividad.tsx

76 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState } from 'react'
import { isSuppressed, type IntensidadData } 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 } from '@/lib/dashboard-utils'
// Hexbin implementado como CSS Grid con celdas coloreadas por densidad.
// NO es scatter de individuos — cada celda es un agregado con N≥5.
const BIENESTAR_BINS = ['1','2','3','4','5']
export function IntensidadProductividad({ surveyId, filters }: { surveyId: string; filters: DashboardFilters }) {
const [data, setData] = useState<IntensidadData | null>(null)
const [sup, setSup] = useState(false)
useEffect(() => {
setData(null); setSup(false)
queries.intensidad(surveyId, filters)
.then(d => { if (isSuppressed(d)) setSup(true); else setData(d as IntensidadData) })
.catch(() => {})
}, [surveyId, JSON.stringify(filters)])
const status = !data && !sup ? 'loading' : sup ? 'suppressed' : 'ok'
const maxN = data ? Math.max(...data.data.map(c => c.n ?? 0), 1) : 1
const cellMap = new Map((data?.data ?? []).map(c => [`${c.horas}|${c.bienestar}`, c]))
function cellColor(n: number | null): string {
if (n === null) return '#1e2d42'
const t = n / maxN
return `rgba(67, 187, 200, ${0.1 + t * 0.9})`
}
return (
<WidgetShell title="Intensidad de cuidado vs. Bienestar" subtitle="2.2 × 4.3 · cruce agregado intensidad de cuidado × bienestar (bins con k≥5)" status={status} n={data?.n_total} colSpan={6}>
{data && (
<div className="db-hexbin-wrapper">
<p className="db-hexbin-note">
Cada celda = respondentes agregados. Celdas sin color: N &lt; 5 (suprimido).
</p>
<div className="db-heatmap-scroll">
<div className="db-heatmap-grid"
style={{ gridTemplateColumns: `120px repeat(${BIENESTAR_BINS.length}, 1fr)` }}>
<div role="columnheader" />
{BIENESTAR_BINS.map(b => (
<div key={b} className="db-heatmap-header" role="columnheader">Bienestar {b}</div>
))}
{HORAS_ORDER.map(horas => (
<>
<div key={`r-${horas}`} className="db-heatmap-rowlabel" role="rowheader">{horas}</div>
{BIENESTAR_BINS.map(bien => {
const cell = cellMap.get(`${horas}|${bien}`)
const n = cell?.n ?? null
const suppressed = cell?.suppressed ?? true
return (
<div key={`${horas}|${bien}`}
className="db-heatmap-cell"
style={{ background: cellColor(n) }}
role="cell"
title={suppressed ? 'Datos insuficientes' : `n = ${n}`}
aria-label={suppressed ? 'suprimido' : `n=${n}`}>
{!suppressed && n !== null && <span>{n}</span>}
</div>
)
})}
</>
))}
</div>
</div>
</div>
)}
</WidgetShell>
)
}