Con marca y dashboards
This commit is contained in:
75
components/dashboard/intensidad-productividad.tsx
Normal file
75
components/dashboard/intensidad-productividad.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
'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 (bins N≥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 < 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user