DataScope26/WEB
0
1import { useDashboardMetrics } from '../hooks/useDashboardMetrics.jsx';2import BarGraph from './BarGraph.jsx';3import NumberStat from './NumberStat.jsx';4import PieChartGraph from './PieChartGraph.jsx';5import ListWithPercentages from './ListWithPercentages.jsx';6import InfoIcon from './InfoIcon.jsx';7import { useAuth } from "../context/AuthContext.jsx"8import "./Dashboard.css";9import { useState, useRef, useEffect } from 'react';10import { useNavigate, useLocation } from "react-router-dom";11import ButtonPrimary from './ButtonPrimary.jsx';12 13const isTechnicalArea = (areaName) => {14 const normalizedName = String(areaName ?? '')15 .trim()16 .toLocaleLowerCase()17 .normalize('NFD')18 .replace(/[\u0300-\u036f]/g, '');19 20 return normalizedName === 'general' || normalizedName === 'sin area';21};22 23function Dashboard() {24 const { user, selectedCompany, setSelectedCompany } = useAuth()25 const navigate = useNavigate();26 const location = useLocation();27 const [selectedArea, setSelectedArea] = useState('general');28 const activeCompany = location.state?.company ?? selectedCompany ?? (user?.companies?.[0] ?? null);29 const [timePeriod, setTimePeriod] = useState('cuatrimestre');30 const { metrics, loading, isRefreshing, error } = useDashboardMetrics(selectedArea, activeCompany, timePeriod);31 32 33 const [isOpen, setIsOpen] = useState(false);34 const [isAreaOpen, setIsAreaOpen] = useState(false);35 const [showAllAdoption, setShowAllAdoption] = useState(false);36 const dropdownRef = useRef(null);37 const areaDropdownRef = useRef(null);38 const ADOPTION_PREVIEW_COUNT = 1; 39 40 const options = [41 { value: 'dia', label: 'Día' },42 { value: 'semana', label: 'Semana' },43 { value: 'mes', label: 'Mes' },44 { value: 'cuatrimestre', label: 'Cuatrimestre' },45 { value: 'año', label: 'Año' }46 ];47 48 const handleFilterChange = (period) => {49 console.log('Period seleccionado:', period);50 setTimePeriod(period);51 setIsOpen(false);52 };53 54 const handleAreaChange = (area) => {55 setSelectedArea(area);56 setIsAreaOpen(false);57 setShowAllAdoption(false); // al cambiar de filtro, siempre arrancamos colapsado58 };59 60 useEffect(() => {61 const handleClickOutside = (event) => {62 if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {63 setIsOpen(false);64 }65 if (areaDropdownRef.current && !areaDropdownRef.current.contains(event.target)) {66 setIsAreaOpen(false);67 }68 };69 70 document.addEventListener('mousedown', handleClickOutside);71 return () => document.removeEventListener('mousedown', handleClickOutside);72 }, []);73 74 useEffect(() => {75 if (location.state?.company) {76 setSelectedCompany(location.state.company);77 }78 }, [location.state?.company, setSelectedCompany]);79 80 const selectedLabel = options.find(opt => opt.value === timePeriod)?.label;81 82 const availableAreas = (metrics?.availableAreas || []).filter(83 (area) => !isTechnicalArea(area.name)84 );85 const areaOptions = [{ id: 'general', name: 'General' }, ...availableAreas];86 const selectedAreaLabel = selectedArea === 'general' ? 'General' : areaOptions.find(a => String(a.id) === String(selectedArea))?.name;87 88 const fullAdoptionRate = (metrics?.adoptionRate || []).filter(89 (area) => !isTechnicalArea(area.area)90 );91 const isGeneralFilter = selectedArea === 'general';92 const adoptionRateToShow = (isGeneralFilter && !showAllAdoption)93 ? fullAdoptionRate.slice(0, ADOPTION_PREVIEW_COUNT)94 : fullAdoptionRate;95 const canExpandAdoption = isGeneralFilter && fullAdoptionRate.length > ADOPTION_PREVIEW_COUNT;96 97 const hasNoAreas = availableAreas.length === 0;98 99 const areaHasNoEmployees = !isGeneralFilter && (100 !metrics?.adoptionRate ||101 metrics.adoptionRate.length === 0 ||102 metrics.adoptionRate[0]?.area_total_users === 0103 );104 105 const handleSetupNavigation = (step) => {106 const companyId = activeCompany?.id ?? user?.companies?.[0]?.id;107 if (!companyId) return;108 109 const params = new URLSearchParams({ steps: step });110 111 if (step === 'invite' && selectedArea && selectedArea !== 'general') {112 params.set('areaId', String(selectedArea));113 }114 115 navigate(`/company-setup/${companyId}?${params.toString()}`);116 };117 118 if (!user) {119 return <h1 className="loading-dashboard">Loading...</h1>;120 }121 122 // If the user is logged but has no companies, show a helpful message123 if (user && !activeCompany) {124 return (125 <div className='loading-dashboard-container'>126 <h1 className="loading-dashboard">No tienes ninguna compañia asociada.</h1>127 128 <ButtonPrimary129 className="create-companies-btn"130 onClick={() => navigate('/create-company')}131 style={{ marginTop: '16px' }}132 >133 Crear mi primera empresa134 </ButtonPrimary>135 </div>136 );137 }138 139 return (140 <>141 {loading ? (142 <div className='loading-dashboard-container'>143 <h1 className="loading-dashboard">Loading...</h1>144 </div>145 ) : error ? (146 !activeCompany ?147 <div className='loading-dashboard-container'>148 <h1 className="loading-dashboard">No tienes ninguna compañia asociada.</h1>149 150 <ButtonPrimary151 className="my-companies__create-btn"152 onClick={() => navigate('/create-company')}153 style={{ marginTop: '16px' }}154 >155 Crear mi primera empresa156 </ButtonPrimary>157 </div>158 : error.status == 403 ?159 <div className='loading-dashboard-container'>160 <h1 className="loading-dashboard">No eres administrador de esta compañia.</h1>161 </div>162 :163 <div className='loading-dashboard-container'>164 <h1 className="loading-dashboard">Ocurrió un error</h1>165 </div>166 167 ) : (168 <div className="dashboard-root">169 {/* Header */}170 <div className='header-name-company'>171 <h1 className="dashboard-company-name">{activeCompany?.name || 'Nombre de la empresa'}</h1>172 </div>173 174 {/* Métricas generales */}175 <section className="dashboard-section">176 <h2 className="dashboard-section-title dashboard-section--spaced">Metricas generales</h2>177 <div className="dashboard-stats-row">178 179 <NumberStat180 title="TOTAL PROMPTS"181 metric={metrics?.estimatedUsageTime?.total_messages ?? 0}182 subtitle="Procesados en tiempo real"183 infoTitle="Total de Prompts"184 infoDescription="La cantidad total de Prompts realizados en la empresa. Un prompt es una instrucción, pregunta o texto que se le escribe a un sistema de IA (como ChatGPT o Gemini) para que realice una tarea u obtenga un resultado específico"185 accent186 />187 {/**188 <NumberStat189 title="PRECISIÓN GLOBAL"190 metric="0%"191 subtitle="Calibración optimizada"192 infoTitle="Precisión global"193 infoDescription="Work In Progress - not yet measured"194 />195 */}196 </div>197 </section>198 199 {/* Área */}200 <section className="dashboard-section">201 <div className="dashboard-section-header">202 <h2 className="dashboard-section-title">Area</h2>203 204 <div className="dashboard-header-filters">205 <div className="custom-dropdown" ref={areaDropdownRef}>206 <ButtonPrimary207 className="dropdown-button"208 onClick={() => setIsAreaOpen(!isAreaOpen)}209 >210 <span className="dropdown-label">{selectedAreaLabel}</span>211 <span className={`dropdown-arrow ${isAreaOpen ? 'open' : ''}`}>▼</span>212 </ButtonPrimary>213 214 {isAreaOpen && (215 <div className="dropdown-menu">216 {areaOptions.map(area => (217 <ButtonPrimary218 key={area.id}219 type="button"220 className={`dropdown-option ${String(selectedArea) === String(area.id) ? 'selected' : ''}`}221 onClick={() => handleAreaChange(area.id)}222 >223 {area.name}224 </ButtonPrimary>225 ))}226 </div>227 )}228 </div>229 230 <div className="custom-dropdown" ref={dropdownRef}>231 <ButtonPrimary232 className="dropdown-button"233 onClick={() => setIsOpen(!isOpen)}234 >235 <span className="dropdown-label">{selectedLabel}</span>236 <span className={`dropdown-arrow ${isOpen ? 'open' : ''}`}>▼</span>237 </ButtonPrimary>238 239 {isOpen && (240 <div className="dropdown-menu">241 {options.map(option => (242 <ButtonPrimary243 key={option.value}244 type="button"245 className={`dropdown-option ${timePeriod === option.value ? 'selected' : ''}`}246 onClick={() => handleFilterChange(option.value)}247 >248 {option.label}249 </ButtonPrimary>250 ))}251 </div>252 )}253 </div>254 </div>255 </div>256 257 {hasNoAreas ? (258 <div className="dashboard-no-employees">259 <p className="dashboard-no-employees__text">Todavía no creaste ningún área.</p>260 <ButtonPrimary261 type="button"262 className="dashboard-no-employees__btn"263 onClick={() => handleSetupNavigation('area')}264 style={{ border: 'none' }}265 >266 Crear área267 </ButtonPrimary>268 </div>269 ) : areaHasNoEmployees ? (270 <div className="dashboard-no-employees">271 <p className="dashboard-no-employees__text">Esta area no tiene empleados asignados.</p>272 <ButtonPrimary273 type="button"274 className="dashboard-no-employees__btn"275 onClick={() => handleSetupNavigation('invite')}276 style={{ border: 'none' }}277 >278 Invitar empleados279 </ButtonPrimary>280 </div>281 ) : (282 <div className="dashboard-area-grid">283 284 {/* Calidad de prompts: foco principal (full width) */}285 <div className="dashboard-card dashboard-card--full">286 {metrics.promptQuality?.average_quality != null ? (287 <div className="quality-score">288 <div className="quality-score__header">289 <span className="bar-graph-title">CALIDAD DE PROMPTS</span>290 <InfoIcon description="La estadística se obtiene promediando la calidad evaluada en cada sesión. Una sesión empieza con el primer mensaje y se extiende mientras el usuario siga enviando mensajes: cada nuevo mensaje reinicia un contador de 10 minutos. La sesión solo termina cuando pasa ese tiempo sin actividad. Al cierre, se asigna una puntuación por sesión y se promedian todas." title="Calidad" />291 </div>292 <div className="quality-number">293 {metrics.promptQuality.average_quality} <span className="quality-max">/100</span>294 </div>295 <div className="quality-bar-track">296 <div className="quality-bar-fill" style={{ width: (metrics.promptQuality.average_quality + "%") }} />297 </div>298 <span className="quality-level">NIVEL: {(metrics.promptQuality.average_quality < 30 ? "BASICO" : metrics.promptQuality.average_quality < 70 ? "MEDIO" : "EXPERTO")}</span>299 </div>300 ) : (301 <div className="dashboard-no-data">302 <span className="bar-graph-title">CALIDAD DE PROMPTS</span>303 <p className="dashboard-no-data__text">No hay datos disponibles</p>304 </div>305 )}306 </div>307 308 <div className="dashboard-card">309 {adoptionRateToShow?.length > 0 ? (310 <>311 <BarGraph312 metrics={adoptionRateToShow}313 numberedAxisValueName="active_users"314 numberedAxisTotalName="area_total_users"315 title={isGeneralFilter ? "ADOPCIÓN DE IA POR ÁREA" : "ADOPCIÓN DE IA"}316 namedAxis="area"317 infoTitle="Adopcion de IA por Área"318 infoDescription="Representa el porcentaje de uso de plataformas de IA en cada área de la empresa. Un usuario activo es aquel que mando al menos un mensaje a la IA en el tiempo especificado."319 />320 {canExpandAdoption && (321 <ButtonPrimary322 type="button"323 className="adoption-toggle-btn"324 onClick={() => setShowAllAdoption(!showAllAdoption)}325 >326 {showAllAdoption ? "Ver menos" : "Ver mas..."}327 </ButtonPrimary>328 )}329 </>330 ) : (331 <div className="dashboard-no-data">332 <span className="bar-graph-title">{isGeneralFilter ? "ADOPCIÓN DE IA POR ÁREA" : "ADOPCIÓN DE IA"}</span>333 <p className="dashboard-no-data__text">No hay datos disponibles</p>334 </div>335 )}336 </div>337 338 <div className="dashboard-card dashboard-card-pie">339 {metrics.platformUsage?.length > 0 ? (340 <PieChartGraph341 title="USO DE IA"342 objectList={metrics.platformUsage}343 label="platform"344 percentage="percentage"345 infoTitle="Distribución de Plataformas"346 infoDescription="Muestra el porcentaje de uso de cada plataforma de IA. Se calcula tomando el total de prompts procesados en cada plataforma dividido por el total de prompts de todas las plataformas."347 />348 ) : (349 <div className="dashboard-no-data">350 <span className="bar-graph-title">USO DE IA</span>351 <p className="dashboard-no-data__text">No hay datos disponibles</p>352 </div>353 )}354 </div>355 356 {/* Usos principales de la IA (abajo) */}357 <div className="dashboard-card dashboard-card-list-with-percentages dashboard-card--full">358 {metrics.mainTasks?.length > 0 ? (359 <ListWithPercentages360 objectArray={metrics.mainTasks}361 itemsTitle="category"362 numberField="total_uses"363 title="USOS PRINCIPALES DE LA IA"364 infoTitle="Usos Principales de la IA"365 infoDescription="Muestra el desglose de las tareas automatizadas por categoría. Se calcula dividiendo el total de usos de cada tarea entre el total general y expresando como porcentaje."366 />367 ) : (368 <div className="dashboard-no-data">369 <span className="bar-graph-title">USOS PRINCIPALES DE LA IA</span>370 <p className="dashboard-no-data__text">No hay datos disponibles</p>371 </div>372 )}373 </div>374 375 </div>376 )}377 </section>378 379 </div>380 )381 }382 383 </>384 );385}386 387export default Dashboard;