Etapa completada (local) — widgets de Mirada Empresa

This commit is contained in:
markosbenitez
2026-06-26 11:29:16 -03:00
parent df9b168577
commit 56443a739f
24 changed files with 905 additions and 808 deletions

View File

@@ -0,0 +1,126 @@
'use client'
import { useEffect, useState } from 'react'
import {
isSuppressed,
type RiesgoRotacionData,
type AusentismoData,
type PresentismoData,
type CarreraCongeladaData,
type SuppressedPayload,
type DashboardFilters,
} from '@/lib/dashboard-types'
import { queries } from '@/lib/dashboard-queries'
import { WidgetShell } from './widget-shell'
type Riesgo = RiesgoRotacionData | SuppressedPayload
type Ausentismo = AusentismoData | SuppressedPayload
type Presentismo = PresentismoData | SuppressedPayload
type Carrera = CarreraCongeladaData | SuppressedPayload
interface Senal {
label: string
pct: number | null
color: 'rose' | 'amber'
}
// Combina E2 (riesgo) + E3 (ausentismo) + E4 (presentismo) — núcleo — y
// E5 (carrera congelada) — [SEC] — en una sola card de 4 barras, igual al
// mockup ("Señales de impacto laboral"). Cada señal se suprime POR FILA
// (n<5 individual) sin tumbar las demás; la card entera solo se suprime
// si las 4 vienen suprimidas a la vez.
export function SenalesImpactoBar({ surveyId, filters }: { surveyId: string; filters: DashboardFilters }) {
const [riesgo, setRiesgo] = useState<Riesgo | null>(null)
const [ausentismo, setAusentismo] = useState<Ausentismo | null>(null)
const [presentismo, setPresentismo] = useState<Presentismo | null>(null)
const [carrera, setCarrera] = useState<Carrera | null>(null)
const [error, setError] = useState(false)
useEffect(() => {
setRiesgo(null); setAusentismo(null); setPresentismo(null); setCarrera(null); setError(false)
Promise.all([
queries.meRiesgoRotacion(surveyId, filters),
queries.meAusentismo(surveyId, filters),
queries.mePresentismo(surveyId, filters),
queries.meCarreraCongelada(surveyId, filters),
])
.then(([r, a, p, c]) => {
setRiesgo(r as Riesgo)
setAusentismo(a as Ausentismo)
setPresentismo(p as Presentismo)
setCarrera(c as Carrera)
})
.catch(() => setError(true))
}, [surveyId, JSON.stringify(filters)])
const loading = riesgo === null || ausentismo === null || presentismo === null || carrera === null
const allSuppressed =
!loading &&
isSuppressed(riesgo) && isSuppressed(ausentismo) && isSuppressed(presentismo) && isSuppressed(carrera)
const status = error ? 'error' : loading ? 'loading' : allSuppressed ? 'suppressed' : 'ok'
const rows: Senal[] = !loading
? [
{
label: 'Presentismo',
pct: !isSuppressed(presentismo) ? presentismo.pct_presentismo : null,
color: 'amber',
},
{
label: 'Ausentismo frecuente',
pct: !isSuppressed(ausentismo) ? ausentismo.pct_ausentismo_recurrente : null,
color: 'amber',
},
{
label: 'Riesgo de salida',
pct: !isSuppressed(riesgo) ? riesgo.pct_riesgo : null,
color: 'rose',
},
{
label: 'Carrera congelada',
pct: !isSuppressed(carrera) ? carrera.pct_carrera_congelada : null,
color: 'rose',
},
]
: []
const repN = !loading && !isSuppressed(riesgo) ? riesgo.n_cuidadores : undefined
return (
<WidgetShell
title="Señales de impacto laboral"
subtitle="7.2 · 7.3 · 7.6 · 7.5 — sobre la población cuidadora"
status={status}
n={repN}
colSpan={6}
>
{!loading && (
<ul className="db-bar-list" aria-label="Señales de impacto laboral">
{rows.map((row) => (
<li key={row.label} className="db-bar-item">
<span className="db-bar-label">{row.label}</span>
{row.pct !== null ? (
<>
<div className="db-bar-track">
<div
className="db-bar-fill"
style={{ width: `${row.pct}%`, background: row.color === 'rose' ? '#f06b6b' : '#f0b429' }}
/>
</div>
<span className="db-bar-pct">{row.pct}%</span>
</>
) : (
<>
<div className="db-bar-track" />
<span className="db-bar-pct" style={{ color: '#6b7280' }}>
n&lt;5
</span>
</>
)}
</li>
))}
</ul>
)}
</WidgetShell>
)
}