diff --git a/lib/pages/infrastructure/pages/dashboard_page.dart b/lib/pages/infrastructure/pages/dashboard_page.dart index 4b09ca9..bcc2ff5 100644 --- a/lib/pages/infrastructure/pages/dashboard_page.dart +++ b/lib/pages/infrastructure/pages/dashboard_page.dart @@ -41,45 +41,40 @@ class _DashboardPageState extends State @override Widget build(BuildContext context) { + final isLargeScreen = MediaQuery.of(context).size.width > 1200; + final isMediumScreen = MediaQuery.of(context).size.width > 800; + final isSmallScreen = MediaQuery.of(context).size.width <= 600; + return FadeTransition( opacity: _fadeAnimation, child: Consumer2( builder: (context, navigationProvider, componentesProvider, child) { return Container( - padding: const EdgeInsets.all(24), + padding: EdgeInsets.all(isSmallScreen ? 12 : 24), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Título de la página - _buildPageTitle(), + _buildPageTitle(isSmallScreen), - const SizedBox(height: 24), + SizedBox(height: isSmallScreen ? 16 : 24), // Cards de estadísticas principales - _buildStatsCards(componentesProvider), + _buildStatsCards(componentesProvider, isLargeScreen, + isMediumScreen, isSmallScreen), - const SizedBox(height: 24), + SizedBox(height: isSmallScreen ? 16 : 24), // Gráficos y métricas - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 2, - child: _buildComponentsOverview(componentesProvider), - ), - const SizedBox(width: 24), - Expanded( - child: _buildAlertasRecientes(), - ), - ], - ), + _buildContentSection(componentesProvider, isLargeScreen, + isMediumScreen, isSmallScreen), - const SizedBox(height: 24), + SizedBox(height: isSmallScreen ? 16 : 24), // Actividad reciente - _buildActivityFeed(), + _buildActivityFeed( + isLargeScreen, isMediumScreen, isSmallScreen), ], ), ), @@ -89,9 +84,9 @@ class _DashboardPageState extends State ); } - Widget _buildPageTitle() { + Widget _buildPageTitle(bool isSmallScreen) { return Container( - padding: const EdgeInsets.all(20), + padding: EdgeInsets.all(isSmallScreen ? 16 : 20), decoration: BoxDecoration( gradient: AppTheme.of(context).primaryGradient, borderRadius: BorderRadius.circular(16), @@ -106,37 +101,39 @@ class _DashboardPageState extends State child: Row( children: [ Container( - padding: const EdgeInsets.all(12), + padding: EdgeInsets.all(isSmallScreen ? 8 : 12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), - child: const Icon( + child: Icon( Icons.dashboard, color: Colors.white, - size: 24, + size: isSmallScreen ? 20 : 24, ), ), - const SizedBox(width: 16), + SizedBox(width: isSmallScreen ? 12 : 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( 'Dashboard MDF/IDF', style: TextStyle( color: Colors.white, - fontSize: 24, + fontSize: isSmallScreen ? 18 : 24, fontWeight: FontWeight.bold, ), ), - Text( - 'Panel de control de infraestructura de red', - style: TextStyle( - color: Colors.white.withOpacity(0.9), - fontSize: 14, + if (!isSmallScreen) ...[ + Text( + 'Panel de control de infraestructura de red', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 14, + ), ), - ), + ], ], ), ), @@ -145,50 +142,90 @@ class _DashboardPageState extends State ); } - Widget _buildStatsCards(ComponentesProvider componentesProvider) { - return Row( - children: [ - Expanded( - child: _buildStatCard( - 'Componentes Totales', - '${componentesProvider.componentes.length}', - Icons.inventory_2, - Colors.blue, - 'equipos registrados', - ), - ), - const SizedBox(width: 16), - Expanded( - child: _buildStatCard( - 'Componentes Activos', + Widget _buildStatsCards(ComponentesProvider componentesProvider, + bool isLargeScreen, bool isMediumScreen, bool isSmallScreen) { + final stats = [ + { + 'title': 'Componentes Totales', + 'value': '${componentesProvider.componentes.length}', + 'icon': Icons.inventory_2, + 'color': Colors.blue, + 'subtitle': 'equipos registrados', + }, + { + 'title': 'Componentes Activos', + 'value': '${componentesProvider.componentes.where((c) => c.activo).length}', - Icons.power, - Colors.green, - 'en funcionamiento', - ), - ), - const SizedBox(width: 16), - Expanded( - child: _buildStatCard( - 'En Uso', + 'icon': Icons.power, + 'color': Colors.green, + 'subtitle': 'en funcionamiento', + }, + { + 'title': 'En Uso', + 'value': '${componentesProvider.componentes.where((c) => c.enUso).length}', - Icons.trending_up, - Colors.orange, - 'siendo utilizados', - ), + 'icon': Icons.trending_up, + 'color': Colors.orange, + 'subtitle': 'siendo utilizados', + }, + { + 'title': 'Categorías', + 'value': '${componentesProvider.categorias.length}', + 'icon': Icons.category, + 'color': Colors.purple, + 'subtitle': 'tipos de equipos', + }, + ]; + + if (isSmallScreen) { + // En móvil: 2x2 grid + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1.1, + crossAxisSpacing: 12, + mainAxisSpacing: 12, ), - const SizedBox(width: 16), - Expanded( - child: _buildStatCard( - 'Categorías', - '${componentesProvider.categorias.length}', - Icons.category, - Colors.purple, - 'tipos de equipos', - ), - ), - ], - ); + itemCount: stats.length, + itemBuilder: (context, index) { + final stat = stats[index]; + return _buildStatCard( + stat['title'] as String, + stat['value'] as String, + stat['icon'] as IconData, + stat['color'] as Color, + stat['subtitle'] as String, + isSmallScreen, + ); + }, + ); + } else { + // En desktop/tablet: row horizontal + return Row( + children: stats.map((stat) { + final isLast = stat == stats.last; + return Expanded( + child: Row( + children: [ + Expanded( + child: _buildStatCard( + stat['title'] as String, + stat['value'] as String, + stat['icon'] as IconData, + stat['color'] as Color, + stat['subtitle'] as String, + isSmallScreen, + ), + ), + if (!isLast) const SizedBox(width: 16), + ], + ), + ); + }).toList(), + ); + } } Widget _buildStatCard( @@ -197,6 +234,7 @@ class _DashboardPageState extends State IconData icon, Color color, String subtitle, + bool isSmallScreen, ) { return TweenAnimationBuilder( duration: const Duration(milliseconds: 800), @@ -205,7 +243,7 @@ class _DashboardPageState extends State return Transform.scale( scale: 0.8 + (0.2 * animationValue), child: Container( - padding: const EdgeInsets.all(20), + padding: EdgeInsets.all(isSmallScreen ? 12 : 20), decoration: BoxDecoration( color: AppTheme.of(context).secondaryBackground, borderRadius: BorderRadius.circular(16), @@ -223,39 +261,43 @@ class _DashboardPageState extends State ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Container( - padding: const EdgeInsets.all(8), + padding: EdgeInsets.all(isSmallScreen ? 6 : 8), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), - child: Icon(icon, color: color, size: 20), + child: Icon(icon, + color: color, size: isSmallScreen ? 16 : 20), ), const Spacer(), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - 'MDF/IDF', - style: TextStyle( - color: color, - fontSize: 10, - fontWeight: FontWeight.bold, + if (!isSmallScreen) ...[ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'MDF/IDF', + style: TextStyle( + color: color, + fontSize: 10, + fontWeight: FontWeight.bold, + ), ), ), - ), + ], ], ), - const SizedBox(height: 16), + SizedBox(height: isSmallScreen ? 8 : 16), TweenAnimationBuilder( duration: Duration( milliseconds: 1000 + (animationValue * 500).round()), @@ -265,7 +307,7 @@ class _DashboardPageState extends State animatedValue.toString(), style: TextStyle( color: AppTheme.of(context).primaryText, - fontSize: 28, + fontSize: isSmallScreen ? 20 : 28, fontWeight: FontWeight.bold, ), ); @@ -276,17 +318,21 @@ class _DashboardPageState extends State title, style: TextStyle( color: AppTheme.of(context).primaryText, - fontSize: 14, + fontSize: isSmallScreen ? 12 : 14, fontWeight: FontWeight.w600, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - Text( - subtitle, - style: TextStyle( - color: AppTheme.of(context).secondaryText, - fontSize: 12, + if (!isSmallScreen) ...[ + Text( + subtitle, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 12, + ), ), - ), + ], ], ), ), @@ -295,9 +341,39 @@ class _DashboardPageState extends State ); } - Widget _buildComponentsOverview(ComponentesProvider componentesProvider) { + Widget _buildContentSection(ComponentesProvider componentesProvider, + bool isLargeScreen, bool isMediumScreen, bool isSmallScreen) { + if (isSmallScreen) { + // En móvil: columna vertical + return Column( + children: [ + _buildComponentsOverview(componentesProvider, isSmallScreen), + const SizedBox(height: 16), + _buildAlertasRecientes(isSmallScreen), + ], + ); + } else { + // En desktop/tablet: row horizontal + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: _buildComponentsOverview(componentesProvider, isSmallScreen), + ), + const SizedBox(width: 24), + Expanded( + child: _buildAlertasRecientes(isSmallScreen), + ), + ], + ); + } + } + + Widget _buildComponentsOverview( + ComponentesProvider componentesProvider, bool isSmallScreen) { return Container( - padding: const EdgeInsets.all(20), + padding: EdgeInsets.all(isSmallScreen ? 16 : 20), decoration: BoxDecoration( color: AppTheme.of(context).secondaryBackground, borderRadius: BorderRadius.circular(16), @@ -313,21 +389,27 @@ class _DashboardPageState extends State Icon( Icons.pie_chart, color: AppTheme.of(context).primaryColor, - size: 20, + size: isSmallScreen ? 18 : 20, ), - const SizedBox(width: 8), - Text( - 'Distribución de Componentes por Categoría', - style: TextStyle( - color: AppTheme.of(context).primaryText, - fontSize: 16, - fontWeight: FontWeight.bold, + SizedBox(width: isSmallScreen ? 6 : 8), + Expanded( + child: Text( + isSmallScreen + ? 'Componentes por Categoría' + : 'Distribución de Componentes por Categoría', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: isSmallScreen ? 14 : 16, + fontWeight: FontWeight.bold, + ), ), ), ], ), - const SizedBox(height: 20), - ...componentesProvider.categorias.take(5).map((categoria) { + SizedBox(height: isSmallScreen ? 16 : 20), + ...componentesProvider.categorias + .take(isSmallScreen ? 4 : 5) + .map((categoria) { final componentesDeCategoria = componentesProvider.componentes .where((c) => c.categoriaId == categoria.id) .length; @@ -338,49 +420,59 @@ class _DashboardPageState extends State : 0.0; return Container( - margin: const EdgeInsets.only(bottom: 12), - child: Row( + margin: EdgeInsets.only(bottom: isSmallScreen ? 8 : 12), + child: Column( children: [ - Expanded( - flex: 3, - child: Text( - categoria.nombre, - style: TextStyle( - color: AppTheme.of(context).primaryText, - fontSize: 14, + Row( + children: [ + Expanded( + flex: isSmallScreen ? 2 : 3, + child: Text( + categoria.nombre, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: isSmallScreen ? 12 : 14, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), - ), - ), - Expanded( - flex: 4, - child: Container( - height: 8, - decoration: BoxDecoration( - color: AppTheme.of(context).tertiaryBackground, - borderRadius: BorderRadius.circular(4), - ), - child: FractionallySizedBox( - alignment: Alignment.centerLeft, - widthFactor: porcentaje / 100, + SizedBox(width: isSmallScreen ? 6 : 8), + Expanded( + flex: isSmallScreen ? 3 : 4, child: Container( + height: isSmallScreen ? 6 : 8, decoration: BoxDecoration( - gradient: AppTheme.of(context).primaryGradient, + color: AppTheme.of(context).tertiaryBackground, borderRadius: BorderRadius.circular(4), ), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: porcentaje / 100, + child: Container( + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + borderRadius: BorderRadius.circular(4), + ), + ), + ), ), ), - ), - ), - const SizedBox(width: 8), - SizedBox( - width: 60, - child: Text( - '$componentesDeCategoria (${porcentaje.toStringAsFixed(1)}%)', - style: TextStyle( - color: AppTheme.of(context).secondaryText, - fontSize: 12, + SizedBox(width: isSmallScreen ? 6 : 8), + SizedBox( + width: isSmallScreen ? 40 : 60, + child: Text( + isSmallScreen + ? '$componentesDeCategoria' + : '$componentesDeCategoria (${porcentaje.toStringAsFixed(1)}%)', + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: isSmallScreen ? 10 : 12, + ), + textAlign: TextAlign.end, + ), ), - ), + ], ), ], ), @@ -391,7 +483,7 @@ class _DashboardPageState extends State ); } - Widget _buildAlertasRecientes() { + Widget _buildAlertasRecientes(bool isSmallScreen) { final alertas = [ { 'tipo': 'Warning', @@ -408,15 +500,16 @@ class _DashboardPageState extends State 'mensaje': 'Mantenimiento programado completado', 'tiempo': '1 hr' }, - { - 'tipo': 'Warning', - 'mensaje': 'Capacidad de cable al 85%', - 'tiempo': '2 hrs' - }, + if (!isSmallScreen) + { + 'tipo': 'Warning', + 'mensaje': 'Capacidad de cable al 85%', + 'tiempo': '2 hrs' + }, ]; return Container( - padding: const EdgeInsets.all(20), + padding: EdgeInsets.all(isSmallScreen ? 16 : 20), decoration: BoxDecoration( color: AppTheme.of(context).secondaryBackground, borderRadius: BorderRadius.circular(16), @@ -432,20 +525,20 @@ class _DashboardPageState extends State Icon( Icons.warning, color: Colors.orange, - size: 20, + size: isSmallScreen ? 18 : 20, ), - const SizedBox(width: 8), + SizedBox(width: isSmallScreen ? 6 : 8), Text( 'Alertas Recientes', style: TextStyle( color: AppTheme.of(context).primaryText, - fontSize: 16, + fontSize: isSmallScreen ? 14 : 16, fontWeight: FontWeight.bold, ), ), ], ), - const SizedBox(height: 16), + SizedBox(height: isSmallScreen ? 12 : 16), ...alertas.map((alerta) { Color alertColor; IconData alertIcon; @@ -465,8 +558,8 @@ class _DashboardPageState extends State } return Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(12), + margin: EdgeInsets.only(bottom: isSmallScreen ? 8 : 12), + padding: EdgeInsets.all(isSmallScreen ? 8 : 12), decoration: BoxDecoration( color: alertColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), @@ -476,8 +569,9 @@ class _DashboardPageState extends State ), child: Row( children: [ - Icon(alertIcon, color: alertColor, size: 16), - const SizedBox(width: 8), + Icon(alertIcon, + color: alertColor, size: isSmallScreen ? 14 : 16), + SizedBox(width: isSmallScreen ? 6 : 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -486,15 +580,17 @@ class _DashboardPageState extends State alerta['mensaje']!, style: TextStyle( color: AppTheme.of(context).primaryText, - fontSize: 12, + fontSize: isSmallScreen ? 11 : 12, fontWeight: FontWeight.w500, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), Text( 'hace ${alerta['tiempo']}', style: TextStyle( color: AppTheme.of(context).secondaryText, - fontSize: 10, + fontSize: isSmallScreen ? 9 : 10, ), ), ], @@ -509,9 +605,10 @@ class _DashboardPageState extends State ); } - Widget _buildActivityFeed() { + Widget _buildActivityFeed( + bool isLargeScreen, bool isMediumScreen, bool isSmallScreen) { return Container( - padding: const EdgeInsets.all(20), + padding: EdgeInsets.all(isSmallScreen ? 16 : 20), decoration: BoxDecoration( color: AppTheme.of(context).secondaryBackground, borderRadius: BorderRadius.circular(16), @@ -527,67 +624,105 @@ class _DashboardPageState extends State Icon( Icons.timeline, color: AppTheme.of(context).primaryColor, - size: 20, + size: isSmallScreen ? 18 : 20, ), - const SizedBox(width: 8), + SizedBox(width: isSmallScreen ? 6 : 8), Text( 'Actividad Reciente', style: TextStyle( color: AppTheme.of(context).primaryText, - fontSize: 16, + fontSize: isSmallScreen ? 14 : 16, fontWeight: FontWeight.bold, ), ), ], ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: _buildActivityItem( - 'Nuevo componente añadido', - 'Switch Cisco SG300-28 registrado en Rack 5', - '10:30 AM', - Icons.add_circle, - Colors.green, - ), - ), - const SizedBox(width: 16), - Expanded( - child: _buildActivityItem( - 'Mantenimiento completado', - 'Revisión de cables en Panel Principal', - '09:15 AM', - Icons.build_circle, - Colors.blue, - ), - ), - const SizedBox(width: 16), - Expanded( - child: _buildActivityItem( - 'Configuración actualizada', - 'Parámetros de red modificados', - '08:45 AM', - Icons.settings, - Colors.purple, - ), - ), - ], - ), + SizedBox(height: isSmallScreen ? 12 : 16), + _buildActivityItems(isSmallScreen), ], ), ); } + Widget _buildActivityItems(bool isSmallScreen) { + final activities = [ + { + 'title': 'Nuevo componente añadido', + 'description': 'Switch Cisco SG300-28 registrado en Rack 5', + 'time': '10:30 AM', + 'icon': Icons.add_circle, + 'color': Colors.green, + }, + { + 'title': 'Mantenimiento completado', + 'description': 'Revisión de cables en Panel Principal', + 'time': '09:15 AM', + 'icon': Icons.build_circle, + 'color': Colors.blue, + }, + { + 'title': 'Configuración actualizada', + 'description': 'Parámetros de red modificados', + 'time': '08:45 AM', + 'icon': Icons.settings, + 'color': Colors.purple, + }, + ]; + + if (isSmallScreen) { + // En móvil: lista vertical + return Column( + children: activities.map((activity) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + child: _buildActivityItem( + activity['title'] as String, + activity['description'] as String, + activity['time'] as String, + activity['icon'] as IconData, + activity['color'] as Color, + isSmallScreen, + ), + ); + }).toList(), + ); + } else { + // En desktop/tablet: fila horizontal + return Row( + children: activities.map((activity) { + final isLast = activity == activities.last; + return Expanded( + child: Row( + children: [ + Expanded( + child: _buildActivityItem( + activity['title'] as String, + activity['description'] as String, + activity['time'] as String, + activity['icon'] as IconData, + activity['color'] as Color, + isSmallScreen, + ), + ), + if (!isLast) const SizedBox(width: 16), + ], + ), + ); + }).toList(), + ); + } + } + Widget _buildActivityItem( String title, String description, String time, IconData icon, Color color, + bool isSmallScreen, ) { return Container( - padding: const EdgeInsets.all(16), + padding: EdgeInsets.all(isSmallScreen ? 12 : 16), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), @@ -595,44 +730,99 @@ class _DashboardPageState extends State color: color.withOpacity(0.3), ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(icon, color: color, size: 16), - const SizedBox(width: 8), - Text( - time, - style: TextStyle( - color: color, - fontSize: 12, - fontWeight: FontWeight.bold, + child: isSmallScreen + ? Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: color, size: 16), ), - ), - ], - ), - const SizedBox(height: 8), - Text( - title, - style: TextStyle( - color: AppTheme.of(context).primaryText, - fontSize: 14, - fontWeight: FontWeight.w600, + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + title, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Text( + time, + style: TextStyle( + color: color, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + description, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 11, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, color: color, size: 16), + const SizedBox(width: 8), + Text( + time, + style: TextStyle( + color: color, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + title, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 4), + Text( + description, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 12, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], ), - ), - const SizedBox(height: 4), - Text( - description, - style: TextStyle( - color: AppTheme.of(context).secondaryText, - fontSize: 12, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ), ); } }