From 71b70e78eed5b35687d088d54715515fc3102826 Mon Sep 17 00:00:00 2001 From: EidanThen Date: Thu, 3 Sep 2026 11:17:52 -0400 Subject: [PATCH] feat: Nuevos filtros para dashboard de carnet --- index.html | 2 +- src/screens/DashboardCarnet.jsx | 133 ++++++++++++++++++++++++-------- src/screens/DashboardENPS.jsx | 30 +++---- 3 files changed, 117 insertions(+), 48 deletions(-) diff --git a/index.html b/index.html index b070d22..06b2209 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + dashboard-proyectos diff --git a/src/screens/DashboardCarnet.jsx b/src/screens/DashboardCarnet.jsx index 91fd1f9..cabb141 100644 --- a/src/screens/DashboardCarnet.jsx +++ b/src/screens/DashboardCarnet.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { supabaseData } from '../services/supabaseClient'; import { BarChart, Bar, Cell, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, CartesianGrid } from 'recharts'; import NavBar from '../components/NavBar'; @@ -9,17 +9,29 @@ export default function DashboardCarnet() { const [rawData, setRawData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [clientes, setClientes] = useState([]); - const [filtroCliente, setFiltroCliente] = useState(''); + + const [filtroDepartamento, setFiltroDepartamento] = useState(''); + const [filtroPosicion, setFiltroPosicion] = useState(''); + const [filtroPais, setFiltroPais] = useState(''); + const [filtroDivision, setFiltroDivision] = useState(''); useEffect(() => { fetchData(); }, []); - useEffect(() => { - const uniqueClientes = [...new Set(rawData.map(item => item.cliente_proyecto).filter(Boolean))]; - setClientes(uniqueClientes); - }, [rawData]); + // Helper para obtener valores únicos ordenados de una columna (obtiene todos los valores para la lista del filtro) + const opcionesUnicas = (campo) => [ + ...new Set( + rawData + .map(item => (item?.[campo] ?? '').toString().trim()) + .filter(v => v !== '') + ), + ].sort((a, b) => a.localeCompare(b, 'es')); + + const departamentos = useMemo(() => opcionesUnicas('departamento'), [rawData]); + const posiciones = useMemo(() => opcionesUnicas('posicion'), [rawData]); + const paises = useMemo(() => opcionesUnicas('pais'), [rawData]); + const divisiones = useMemo(() => opcionesUnicas('division'), [rawData]); const fetchData = async () => { setLoading(true); @@ -46,21 +58,34 @@ export default function DashboardCarnet() { setLoading(false); }; - const datosFiltrados = filtroCliente - ? rawData.filter(item => item.cliente_proyecto === filtroCliente) - : rawData; + const datosFiltrados = rawData.filter(item => { + const matchDepartamento = !filtroDepartamento || (item?.departamento ?? '').toString().trim() === filtroDepartamento; + const matchPosicion = !filtroPosicion || (item?.posicion ?? '').toString().trim() === filtroPosicion; + const matchPais = !filtroPais || (item?.pais ?? '').toString().trim() === filtroPais; + const matchDivision = !filtroDivision || (item?.division ?? '').toString().trim() === filtroDivision; + return matchDepartamento && matchPosicion && matchPais && matchDivision; + }); const totalCarnets = datosFiltrados.length; - const conteoPorCliente = rawData.reduce((acc, curr) => { - const cliente = curr.cliente_proyecto || 'Sin cliente'; - acc[cliente] = (acc[cliente] || 0) + 1; + const limpiarFiltros = () => { + setFiltroDepartamento(''); + setFiltroPosicion(''); + setFiltroPais(''); + setFiltroDivision(''); + }; + + const hayFiltrosActivos = filtroDepartamento || filtroPosicion || filtroPais || filtroDivision; + + const conteoPorDivision = datosFiltrados.reduce((acc, curr) => { + const division = (curr.division || 'Sin división').toString().trim() || 'Sin división'; + acc[division] = (acc[division] || 0) + 1; return acc; }, {}); - const chartDataCliente = Object.keys(conteoPorCliente).map((cliente, index) => ({ - name: cliente, - value: conteoPorCliente[cliente], + const chartDataDivision = Object.keys(conteoPorDivision).map((division, index) => ({ + name: division, + value: conteoPorDivision[division], fill: CHART_PALETTE[index % CHART_PALETTE.length] })); @@ -102,34 +127,74 @@ export default function DashboardCarnet() {

Dashboard de Carnets

-

Conteo de carnets creados por cliente / proyecto

+

Conteo de carnets creados por división

{/* NAVEGACIÓN GLM */} - {/* FILTRO GLM */} -
+ {/* FILTROS GLM */} +
- + +
+
+ + +
+
+ + +
+
+ +
@@ -141,18 +206,18 @@ export default function DashboardCarnet() {

{totalCarnets}

-

Clientes / Proyectos

-

{clientes.length}

-

distintos en el histórico

+

Divisiones

+

{divisiones.length}

+

distintas en el histórico

{/* GRÁFICA GLM */}
-

Carnets por Cliente / Proyecto

+

Carnets por División

- + - {chartDataCliente.map((entry, index) => ( + {chartDataDivision.map((entry, index) => ( ))} diff --git a/src/screens/DashboardENPS.jsx b/src/screens/DashboardENPS.jsx index 2c981a1..7b98b39 100644 --- a/src/screens/DashboardENPS.jsx +++ b/src/screens/DashboardENPS.jsx @@ -4,9 +4,14 @@ import NavBar from '../components/NavBar'; function GaugeChart({ value }) { const clamped = Math.max(-100, Math.min(100, value || 0)); - const angle = (clamped + 100) / 200 * 180 - 90; - const rad = angle * Math.PI / 180; - const cx = 90, cy = 120, r = 90; + + const cx = 120, cy = 100, r = 85; + const bandSize = 12; + const innerR = r - bandSize; + + const toAngle = (v) => 180 + ((v + 100) / 200) * 180; + const needleAngle = toAngle(clamped); + const rad = needleAngle * Math.PI / 180; const nx = cx + r * Math.cos(rad); const ny = cy + r * Math.sin(rad); @@ -47,22 +52,21 @@ function GaugeChart({ value }) { return `M ${x1} ${y1} A ${outerR} ${outerR} 0 ${large} 1 ${x2} ${y2} L ${x3} ${y3} A ${innerR} ${innerR} 0 ${large} 0 ${x4} ${y4} Z`; } - const bandSize = 12; - const innerR = r - bandSize; const totalSegments = 40; return (
- + {categories.map((cat, i) => { - const startAngle = (cat.from + 100) / 200 * 180 - 90; - const endAngle = (cat.to + 100) / 200 * 180 - 90; - const segStart = Math.floor((startAngle + 90) / (180 / totalSegments)); - const segEnd = Math.ceil((endAngle + 90) / (180 / totalSegments)); + const startAngle = toAngle(cat.from); + const endAngle = toAngle(cat.to); + const segSize = 180 / totalSegments; + const segStart = Math.floor(startAngle / segSize); + const segEnd = Math.ceil(endAngle / segSize); const segments = []; for (let s = segStart; s <= segEnd; s++) { - const a1 = Math.max(startAngle, -90 + s * (180 / totalSegments)); - const a2 = Math.min(endAngle, -90 + (s + 1) * (180 / totalSegments)); + const a1 = Math.max(startAngle, s * segSize); + const a2 = Math.min(endAngle, (s + 1) * segSize); if (a2 > a1) { segments.push(arcPath(a1, a2, r, innerR)); } @@ -75,7 +79,7 @@ function GaugeChart({ value }) { -
+
{clamped} eNPS

{getLabel(clamped)}