diff --git a/lib/main.dart b/lib/main.dart index 1625bf9..5c9ec34 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'package:nethive_neo/providers/visual_state_provider.dart'; import 'package:nethive_neo/providers/users_provider.dart'; import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart'; import 'package:nethive_neo/providers/nethive/componentes_provider.dart'; +import 'package:nethive_neo/providers/nethive/navigation_provider.dart'; import 'package:nethive_neo/helpers/globals.dart'; import 'package:url_strategy/url_strategy.dart'; @@ -46,6 +47,7 @@ void main() async { ChangeNotifierProvider(create: (_) => UsersProvider()), ChangeNotifierProvider(create: (_) => EmpresasNegociosProvider()), ChangeNotifierProvider(create: (_) => ComponentesProvider()), + ChangeNotifierProvider(create: (_) => NavigationProvider()), ], child: const MyApp(), ), diff --git a/lib/pages/empresa_negocios/widgets/negocios_table.dart b/lib/pages/empresa_negocios/widgets/negocios_table.dart index 0b33b8d..02a2223 100644 --- a/lib/pages/empresa_negocios/widgets/negocios_table.dart +++ b/lib/pages/empresa_negocios/widgets/negocios_table.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:pluto_grid/pluto_grid.dart'; +import 'package:go_router/go_router.dart'; import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart'; import 'package:nethive_neo/pages/widgets/animated_hover_button.dart'; import 'package:nethive_neo/theme/theme.dart'; @@ -279,6 +280,87 @@ class NegociosTable extends StatelessWidget { ); }, ), + PlutoColumn( + title: 'Infraestructura', + field: 'acceder_infraestructura', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 200, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Container( + padding: const EdgeInsets.all(8), + child: Center( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.orange.shade600, + Colors.deepOrange.shade500, + ], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.orange.withOpacity(0.4), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + final negocioId = + rendererContext.row.cells['id']?.value; + final negocioNombre = + rendererContext.row.cells['nombre']?.value; + + if (negocioId != null) { + // Navegar al layout principal con el negocio seleccionado + context.go('/infrastructure/$negocioId'); + } + }, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.developer_board, + color: Colors.white, + size: 18, + ), + const SizedBox(width: 8), + Text( + 'Acceder a\nInfraestructura', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + height: 1.2, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + }, + ), PlutoColumn( title: 'Acciones', field: 'editar', diff --git a/lib/pages/infrastructure/infrastructure_layout.dart b/lib/pages/infrastructure/infrastructure_layout.dart new file mode 100644 index 0000000..4657ee0 --- /dev/null +++ b/lib/pages/infrastructure/infrastructure_layout.dart @@ -0,0 +1,448 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:nethive_neo/providers/nethive/navigation_provider.dart'; +import 'package:nethive_neo/providers/nethive/componentes_provider.dart'; +import 'package:nethive_neo/pages/infrastructure/widgets/infrastructure_sidemenu.dart'; +import 'package:nethive_neo/pages/infrastructure/pages/dashboard_page.dart'; +import 'package:nethive_neo/pages/infrastructure/pages/inventario_page.dart'; +import 'package:nethive_neo/pages/infrastructure/pages/topologia_page.dart'; +import 'package:nethive_neo/pages/infrastructure/pages/alertas_page.dart'; +import 'package:nethive_neo/pages/infrastructure/pages/configuracion_page.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class InfrastructureLayout extends StatefulWidget { + final String negocioId; + + const InfrastructureLayout({ + Key? key, + required this.negocioId, + }) : super(key: key); + + @override + State createState() => _InfrastructureLayoutState(); +} + +class _InfrastructureLayoutState extends State + with TickerProviderStateMixin { + bool _isSidebarExpanded = true; + late AnimationController _fadeController; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + _fadeController = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _fadeController, + curve: Curves.easeInOut, + )); + + // Establecer el negocio seleccionado + WidgetsBinding.instance.addPostFrameCallback((_) { + context + .read() + .setNegocioSeleccionado(widget.negocioId); + context + .read() + .setNegocioSeleccionado(widget.negocioId); + _fadeController.forward(); + }); + } + + @override + void dispose() { + _fadeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final isLargeScreen = MediaQuery.of(context).size.width > 1200; + final isMediumScreen = MediaQuery.of(context).size.width > 800; + + // Ajustar sidebar basado en tamaño de pantalla + if (!isLargeScreen && _isSidebarExpanded) { + _isSidebarExpanded = false; + } + + return Scaffold( + backgroundColor: AppTheme.of(context).primaryBackground, + body: FadeTransition( + opacity: _fadeAnimation, + child: Container( + decoration: BoxDecoration( + gradient: AppTheme.of(context).darkBackgroundGradient, + ), + child: Consumer( + builder: (context, navigationProvider, child) { + if (navigationProvider.negocioSeleccionado == null) { + return _buildLoadingScreen(); + } + + if (isMediumScreen) { + // Vista desktop/tablet + return Row( + children: [ + // Sidebar + InfrastructureSidemenu( + isExpanded: _isSidebarExpanded, + onToggle: () { + setState(() { + _isSidebarExpanded = !_isSidebarExpanded; + }); + }, + ), + + // Área principal + Expanded( + child: Column( + children: [ + // Header superior + _buildHeader(navigationProvider), + + // Contenido principal + Expanded( + child: _buildMainContent(navigationProvider), + ), + ], + ), + ), + ], + ); + } else { + // Vista móvil + return Column( + children: [ + // Header móvil + _buildMobileHeader(navigationProvider), + + // Contenido principal + Expanded( + child: _buildMainContent(navigationProvider), + ), + ], + ); + } + }, + ), + ), + ), + + // Drawer para móvil + drawer: MediaQuery.of(context).size.width <= 800 + ? Drawer( + backgroundColor: Colors.transparent, + child: InfrastructureSidemenu( + isExpanded: true, + onToggle: () => Navigator.of(context).pop(), + ), + ) + : null, + ); + } + + Widget _buildLoadingScreen() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + shape: BoxShape.circle, + ), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ), + const SizedBox(height: 20), + Text( + 'Cargando infraestructura...', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + Widget _buildHeader(NavigationProvider navigationProvider) { + final negocio = navigationProvider.negocioSeleccionado!; + final empresa = navigationProvider.empresaSeleccionada!; + final currentMenuItem = navigationProvider.getMenuItemByIndex( + navigationProvider.selectedMenuIndex, + ); + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + border: Border( + bottom: BorderSide( + color: AppTheme.of(context).primaryColor.withOpacity(0.2), + width: 1, + ), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Logo solo de Nethive + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + borderRadius: BorderRadius.circular(12), + ), + child: Image.asset( + 'assets/images/logo_nh.png', + width: 24, + height: 24, + color: Colors.white, + ), + ), + + const SizedBox(width: 20), + + // Breadcrumb mejorado + Expanded( + child: Row( + children: [ + // Empresa + Text( + empresa.nombre, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 8), + Icon( + Icons.arrow_forward_ios, + size: 12, + color: AppTheme.of(context).secondaryText, + ), + const SizedBox(width: 8), + + // Negocio (cuadro verde como en la imagen de referencia) + Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + negocio.nombre, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + Text( + '(${empresa.nombre})', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 10, + ), + ), + ], + ), + ), + + const SizedBox(width: 8), + Icon( + Icons.arrow_forward_ios, + size: 12, + color: AppTheme.of(context).secondaryText, + ), + const SizedBox(width: 8), + + // Página actual + Text( + currentMenuItem.title, + style: TextStyle( + color: AppTheme.of(context).primaryColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + + // Buscador (conservado como en la referencia) + Container( + width: 300, + height: 40, + decoration: BoxDecoration( + color: AppTheme.of(context).formBackground, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppTheme.of(context).primaryColor.withOpacity(0.3), + ), + ), + child: TextField( + style: TextStyle(color: AppTheme.of(context).primaryText), + decoration: InputDecoration( + hintText: 'Buscar en infraestructura...', + hintStyle: TextStyle( + color: AppTheme.of(context).hintText, + fontSize: 14, + ), + prefixIcon: Icon( + Icons.search, + color: AppTheme.of(context).primaryColor, + size: 20, + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildMobileHeader(NavigationProvider navigationProvider) { + final negocio = navigationProvider.negocioSeleccionado!; + final currentMenuItem = navigationProvider.getMenuItemByIndex( + navigationProvider.selectedMenuIndex, + ); + + return Container( + padding: const EdgeInsets.fromLTRB(16, 40, 16, 16), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + ), + child: Column( + children: [ + Row( + children: [ + // Botón de menú + IconButton( + onPressed: () => Scaffold.of(context).openDrawer(), + icon: const Icon(Icons.menu, color: Colors.white), + ), + + // Logo + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Image.asset( + 'assets/images/logo_nh.png', + width: 20, + height: 20, + color: Colors.white, + ), + ), + + const SizedBox(width: 12), + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'NETHIVE', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Text( + currentMenuItem.title, + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Info del negocio + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + const Icon(Icons.business, color: Colors.white, size: 16), + const SizedBox(width: 8), + Expanded( + child: Text( + negocio.nombre, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildMainContent(NavigationProvider navigationProvider) { + switch (navigationProvider.selectedMenuIndex) { + case 0: + return const DashboardPage(); + case 1: + return const InventarioPage(); + case 2: + return const TopologiaPage(); + case 3: + return const AlertasPage(); + case 4: + return const ConfiguracionPage(); + default: + return const DashboardPage(); + } + } +} diff --git a/lib/pages/infrastructure/pages/alertas_page.dart b/lib/pages/infrastructure/pages/alertas_page.dart new file mode 100644 index 0000000..90efff3 --- /dev/null +++ b/lib/pages/infrastructure/pages/alertas_page.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class AlertasPage extends StatelessWidget { + const AlertasPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.warning, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 16), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Centro de Alertas', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Monitoreo y gestión de alertas MDF/IDF', + style: TextStyle( + color: Colors.white70, + fontSize: 14, + ), + ), + ], + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + + // Contenido próximamente + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.orange, Colors.red], + ), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.warning, + size: 60, + color: Colors.white, + ), + ), + const SizedBox(height: 20), + Text( + 'Centro de Alertas', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + Text( + 'Sistema de monitoreo y alertas para infraestructura\nPróximamente disponible', + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 16, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/infrastructure/pages/configuracion_page.dart b/lib/pages/infrastructure/pages/configuracion_page.dart new file mode 100644 index 0000000..0c30524 --- /dev/null +++ b/lib/pages/infrastructure/pages/configuracion_page.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class ConfiguracionPage extends StatelessWidget { + const ConfiguracionPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.settings, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 16), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Configuración', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Configuración de sistema y infraestructura', + style: TextStyle( + color: Colors.white70, + fontSize: 14, + ), + ), + ], + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + + // Contenido próximamente + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.purple, Colors.blue], + ), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.settings, + size: 60, + color: Colors.white, + ), + ), + const SizedBox(height: 20), + Text( + 'Configuración del Sistema', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + Text( + 'Panel de configuración para infraestructura MDF/IDF\nPróximamente disponible', + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 16, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/infrastructure/pages/dashboard_page.dart b/lib/pages/infrastructure/pages/dashboard_page.dart new file mode 100644 index 0000000..4b09ca9 --- /dev/null +++ b/lib/pages/infrastructure/pages/dashboard_page.dart @@ -0,0 +1,638 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:nethive_neo/providers/nethive/navigation_provider.dart'; +import 'package:nethive_neo/providers/nethive/componentes_provider.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class DashboardPage extends StatefulWidget { + const DashboardPage({Key? key}) : super(key: key); + + @override + State createState() => _DashboardPageState(); +} + +class _DashboardPageState extends State + with TickerProviderStateMixin { + late AnimationController _animationController; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + )); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _fadeAnimation, + child: Consumer2( + builder: (context, navigationProvider, componentesProvider, child) { + return Container( + padding: const EdgeInsets.all(24), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Título de la página + _buildPageTitle(), + + const SizedBox(height: 24), + + // Cards de estadísticas principales + _buildStatsCards(componentesProvider), + + const SizedBox(height: 24), + + // Gráficos y métricas + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: _buildComponentsOverview(componentesProvider), + ), + const SizedBox(width: 24), + Expanded( + child: _buildAlertasRecientes(), + ), + ], + ), + + const SizedBox(height: 24), + + // Actividad reciente + _buildActivityFeed(), + ], + ), + ), + ); + }, + ), + ); + } + + Widget _buildPageTitle() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppTheme.of(context).primaryColor.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 5), + ), + ], + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.dashboard, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Dashboard MDF/IDF', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Panel de control de infraestructura de red', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 14, + ), + ), + ], + ), + ), + ], + ), + ); + } + + 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', + '${componentesProvider.componentes.where((c) => c.activo).length}', + Icons.power, + Colors.green, + 'en funcionamiento', + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildStatCard( + 'En Uso', + '${componentesProvider.componentes.where((c) => c.enUso).length}', + Icons.trending_up, + Colors.orange, + 'siendo utilizados', + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildStatCard( + 'Categorías', + '${componentesProvider.categorias.length}', + Icons.category, + Colors.purple, + 'tipos de equipos', + ), + ), + ], + ); + } + + Widget _buildStatCard( + String title, + String value, + IconData icon, + Color color, + String subtitle, + ) { + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 800), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, animationValue, child) { + return Transform.scale( + scale: 0.8 + (0.2 * animationValue), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: color.withOpacity(0.3), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: color.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: color, size: 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, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + TweenAnimationBuilder( + duration: Duration( + milliseconds: 1000 + (animationValue * 500).round()), + tween: IntTween(begin: 0, end: int.tryParse(value) ?? 0), + builder: (context, animatedValue, child) { + return Text( + animatedValue.toString(), + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ); + }, + ), + const SizedBox(height: 4), + Text( + title, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + Text( + subtitle, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 12, + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildComponentsOverview(ComponentesProvider componentesProvider) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppTheme.of(context).primaryColor.withOpacity(0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.pie_chart, + color: AppTheme.of(context).primaryColor, + size: 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, + ), + ), + ], + ), + const SizedBox(height: 20), + ...componentesProvider.categorias.take(5).map((categoria) { + final componentesDeCategoria = componentesProvider.componentes + .where((c) => c.categoriaId == categoria.id) + .length; + final porcentaje = componentesProvider.componentes.isNotEmpty + ? (componentesDeCategoria / + componentesProvider.componentes.length * + 100) + : 0.0; + + return Container( + margin: const EdgeInsets.only(bottom: 12), + child: Row( + children: [ + Expanded( + flex: 3, + child: Text( + categoria.nombre, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + ), + ), + ), + 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, + 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, + ), + ), + ), + ], + ), + ); + }).toList(), + ], + ), + ); + } + + Widget _buildAlertasRecientes() { + final alertas = [ + { + 'tipo': 'Warning', + 'mensaje': 'Switch en Rack 3 sobrecalentándose', + 'tiempo': '5 min' + }, + { + 'tipo': 'Error', + 'mensaje': 'Pérdida de conectividad en Panel A4', + 'tiempo': '12 min' + }, + { + 'tipo': 'Info', + 'mensaje': 'Mantenimiento programado completado', + 'tiempo': '1 hr' + }, + { + 'tipo': 'Warning', + 'mensaje': 'Capacidad de cable al 85%', + 'tiempo': '2 hrs' + }, + ]; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppTheme.of(context).primaryColor.withOpacity(0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.warning, + color: Colors.orange, + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Alertas Recientes', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 16), + ...alertas.map((alerta) { + Color alertColor; + IconData alertIcon; + + switch (alerta['tipo']) { + case 'Error': + alertColor = Colors.red; + alertIcon = Icons.error; + break; + case 'Warning': + alertColor = Colors.orange; + alertIcon = Icons.warning; + break; + default: + alertColor = Colors.blue; + alertIcon = Icons.info; + } + + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: alertColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: alertColor.withOpacity(0.3), + ), + ), + child: Row( + children: [ + Icon(alertIcon, color: alertColor, size: 16), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + alerta['mensaje']!, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + Text( + 'hace ${alerta['tiempo']}', + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 10, + ), + ), + ], + ), + ), + ], + ), + ); + }).toList(), + ], + ), + ); + } + + Widget _buildActivityFeed() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppTheme.of(context).primaryColor.withOpacity(0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.timeline, + color: AppTheme.of(context).primaryColor, + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Actividad Reciente', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 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, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildActivityItem( + String title, + String description, + String time, + IconData icon, + Color color, + ) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + 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, + ), + ), + ], + ), + 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, + ), + ], + ), + ); + } +} diff --git a/lib/pages/infrastructure/pages/inventario_page.dart b/lib/pages/infrastructure/pages/inventario_page.dart new file mode 100644 index 0000000..1f7db7c --- /dev/null +++ b/lib/pages/infrastructure/pages/inventario_page.dart @@ -0,0 +1,879 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:pluto_grid/pluto_grid.dart'; +import 'package:nethive_neo/providers/nethive/componentes_provider.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class InventarioPage extends StatefulWidget { + const InventarioPage({Key? key}) : super(key: key); + + @override + State createState() => _InventarioPageState(); +} + +class _InventarioPageState extends State + with TickerProviderStateMixin { + late AnimationController _animationController; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + )); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _fadeAnimation, + child: Consumer( + builder: (context, componentesProvider, child) { + return Container( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header de inventario + _buildInventoryHeader(componentesProvider), + + const SizedBox(height: 24), + + // Estadísticas rápidas + _buildQuickStats(componentesProvider), + + const SizedBox(height: 24), + + // Tabla de componentes (como en la imagen de referencia) + Expanded( + child: _buildComponentsTable(componentesProvider), + ), + ], + ), + ); + }, + ), + ); + } + + Widget _buildInventoryHeader(ComponentesProvider componentesProvider) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppTheme.of(context).primaryColor.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 5), + ), + ], + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.inventory_2, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Inventario MDF/IDF', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Gestión de componentes de infraestructura de red', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 14, + ), + ), + ], + ), + ), + // Botón para añadir componente + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: TextButton.icon( + onPressed: () { + // TODO: Abrir dialog para añadir componente + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Función de añadir componente próximamente'), + ), + ); + }, + icon: const Icon(Icons.add, color: Colors.white), + label: const Text( + 'Añadir Componente', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildQuickStats(ComponentesProvider componentesProvider) { + return Row( + children: [ + Expanded( + child: _buildStatCard( + 'Total Componentes', + componentesProvider.componentes.length, + Icons.devices, + Colors.blue, + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildStatCard( + 'Activos', + componentesProvider.componentes.where((c) => c.activo).length, + Icons.check_circle, + Colors.green, + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildStatCard( + 'En Uso', + componentesProvider.componentes.where((c) => c.enUso).length, + Icons.trending_up, + Colors.orange, + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildStatCard( + 'Categorías', + componentesProvider.categorias.length, + Icons.category, + Colors.purple, + ), + ), + ], + ); + } + + Widget _buildStatCard(String title, int value, IconData icon, Color color) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: color.withOpacity(0.3), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: color, size: 20), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + value.toString(), + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + title, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildComponentsTable(ComponentesProvider componentesProvider) { + return Container( + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppTheme.of(context).primaryColor.withOpacity(0.2), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + children: [ + // Header de la tabla + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppTheme.of(context).modernGradient, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.table_chart, + color: Colors.white, + size: 20, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Componentes de Red', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Inventario completo de infraestructura MDF/IDF', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 12, + ), + ), + ], + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${componentesProvider.componentesRows.length} registros', + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + + // Tabla de componentes con PlutoGrid (como en la imagen de referencia) + Expanded( + child: componentesProvider.componentesRows.isEmpty + ? _buildEmptyState() + : PlutoGrid( + key: UniqueKey(), + configuration: PlutoGridConfiguration( + enableMoveDownAfterSelecting: true, + enableMoveHorizontalInEditing: true, + localeText: const PlutoGridLocaleText.spanish(), + scrollbar: PlutoGridScrollbarConfig( + draggableScrollbar: true, + isAlwaysShown: false, + onlyDraggingThumb: true, + enableScrollAfterDragEnd: true, + scrollbarThickness: 12, + scrollbarThicknessWhileDragging: 16, + hoverWidth: 20, + scrollBarColor: + AppTheme.of(context).primaryColor.withOpacity(0.7), + scrollBarTrackColor: Colors.grey.withOpacity(0.2), + scrollbarRadius: const Radius.circular(8), + scrollbarRadiusWhileDragging: const Radius.circular(10), + ), + style: PlutoGridStyleConfig( + gridBorderColor: Colors.grey.withOpacity(0.3), + activatedBorderColor: AppTheme.of(context).primaryColor, + inactivatedBorderColor: Colors.grey.withOpacity(0.3), + gridBackgroundColor: + AppTheme.of(context).primaryBackground, + rowColor: AppTheme.of(context).secondaryBackground, + activatedColor: + AppTheme.of(context).primaryColor.withOpacity(0.1), + checkedColor: + AppTheme.of(context).primaryColor.withOpacity(0.2), + cellTextStyle: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 13, + ), + columnTextStyle: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + menuBackgroundColor: + AppTheme.of(context).secondaryBackground, + gridBorderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + rowHeight: 55, + ), + columnFilter: const PlutoGridColumnFilterConfig( + filters: [ + ...FilterHelper.defaultFilters, + ], + ), + ), + columns: [ + PlutoColumn( + title: 'ID', + field: 'id', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 100, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Text( + rendererContext.cell.value + .toString() + .substring(0, 8) + + '...', + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 11, + fontWeight: FontWeight.w500, + ), + ); + }, + ), + PlutoColumn( + title: 'Componente', + field: 'nombre', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.left, + width: 200, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Container( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + // Imagen del componente + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: AppTheme.of(context) + .primaryColor + .withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: rendererContext.row.cells['imagen_url'] + ?.value != + null && + rendererContext + .row.cells['imagen_url']!.value + .toString() + .isNotEmpty + ? ClipRRect( + borderRadius: + BorderRadius.circular(6), + child: Image.network( + rendererContext + .row.cells['imagen_url']!.value + .toString(), + fit: BoxFit.cover, + errorBuilder: + (context, error, stackTrace) { + return Icon( + Icons.devices, + color: AppTheme.of(context) + .primaryColor, + size: 16, + ); + }, + ), + ) + : Icon( + Icons.devices, + color: + AppTheme.of(context).primaryColor, + size: 16, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + rendererContext.cell.value.toString(), + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + ), + PlutoColumn( + title: 'Categoría', + field: 'categoria_nombre', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 140, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Container( + padding: const EdgeInsets.all(8), + child: Center( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.of(context) + .primaryColor + .withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppTheme.of(context) + .primaryColor + .withOpacity(0.3), + ), + ), + child: Text( + rendererContext.cell.value.toString(), + style: TextStyle( + color: AppTheme.of(context).primaryColor, + fontSize: 11, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ); + }, + ), + PlutoColumn( + title: 'Estado', + field: 'activo', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 100, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + final isActivo = + rendererContext.cell.value.toString() == 'Sí'; + return Container( + padding: const EdgeInsets.all(8), + child: Center( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: (isActivo ? Colors.green : Colors.red) + .withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isActivo + ? Icons.check_circle + : Icons.cancel, + color: + isActivo ? Colors.green : Colors.red, + size: 12, + ), + const SizedBox(width: 4), + Text( + isActivo ? 'Activo' : 'Inactivo', + style: TextStyle( + color: isActivo + ? Colors.green + : Colors.red, + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ); + }, + ), + PlutoColumn( + title: 'En Uso', + field: 'en_uso', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 100, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + final enUso = + rendererContext.cell.value.toString() == 'Sí'; + return Container( + padding: const EdgeInsets.all(8), + child: Center( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: (enUso ? Colors.orange : Colors.grey) + .withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + enUso ? 'En Uso' : 'Libre', + style: TextStyle( + color: enUso ? Colors.orange : Colors.grey, + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ); + }, + ), + PlutoColumn( + title: 'Ubicación', + field: 'ubicacion', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.left, + width: 180, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Container( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Icon( + Icons.location_on, + color: AppTheme.of(context).primaryColor, + size: 14, + ), + const SizedBox(width: 4), + Expanded( + child: Text( + rendererContext.cell.value + .toString() + .isEmpty + ? 'Sin ubicación' + : rendererContext.cell.value.toString(), + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 12, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + ), + PlutoColumn( + title: 'Descripción', + field: 'descripcion', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.left, + width: 200, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Container( + padding: const EdgeInsets.all(8), + child: Text( + rendererContext.cell.value.toString().isEmpty + ? 'Sin descripción' + : rendererContext.cell.value.toString(), + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 12, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + }, + ), + PlutoColumn( + title: 'Fecha Registro', + field: 'fecha_registro', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 120, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Container( + padding: const EdgeInsets.all(8), + child: Text( + rendererContext.cell.value.toString(), + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 11, + ), + ), + ); + }, + ), + PlutoColumn( + title: 'Acciones', + field: 'editar', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 120, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Botón ver detalles + Tooltip( + message: 'Ver detalles', + child: InkWell( + onTap: () { + // TODO: Implementar vista de detalles + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Ver detalles próximamente'), + ), + ); + }, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(4), + ), + child: const Icon( + Icons.visibility, + color: Colors.white, + size: 14, + ), + ), + ), + ), + const SizedBox(width: 4), + // Botón editar + Tooltip( + message: 'Editar', + child: InkWell( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Editar componente próximamente'), + ), + ); + }, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor, + borderRadius: BorderRadius.circular(4), + ), + child: const Icon( + Icons.edit, + color: Colors.white, + size: 14, + ), + ), + ), + ), + const SizedBox(width: 4), + // Botón eliminar + Tooltip( + message: 'Eliminar', + child: InkWell( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Eliminar componente próximamente'), + ), + ); + }, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(4), + ), + child: const Icon( + Icons.delete, + color: Colors.white, + size: 14, + ), + ), + ), + ), + ], + ); + }, + ), + ], + rows: componentesProvider.componentesRows, + onLoaded: (event) { + componentesProvider.componentesStateManager = + event.stateManager; + }, + createFooter: (stateManager) { + stateManager.setPageSize(15, notify: false); + return PlutoPagination(stateManager); + }, + ), + ), + ], + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.inventory_2, + color: AppTheme.of(context).primaryColor, + size: 48, + ), + ), + const SizedBox(height: 16), + Text( + 'No hay componentes registrados', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + 'Añade el primer componente para comenzar', + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 14, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/infrastructure/pages/topologia_page.dart b/lib/pages/infrastructure/pages/topologia_page.dart new file mode 100644 index 0000000..47879d1 --- /dev/null +++ b/lib/pages/infrastructure/pages/topologia_page.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class TopologiaPage extends StatelessWidget { + const TopologiaPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.account_tree, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 16), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Topología de Red', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Visualización de la infraestructura MDF/IDF', + style: TextStyle( + color: Colors.white70, + fontSize: 14, + ), + ), + ], + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + + // Contenido próximamente + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: AppTheme.of(context).modernGradient, + shape: BoxShape.circle, + ), + child: const Icon( + Icons.account_tree, + size: 60, + color: Colors.white, + ), + ), + const SizedBox(height: 20), + Text( + 'Topología de Red', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 12), + Text( + 'Visualización interactiva de la red MDF/IDF\nPróximamente disponible', + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 16, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/infrastructure/widgets/infrastructure_sidemenu.dart b/lib/pages/infrastructure/widgets/infrastructure_sidemenu.dart new file mode 100644 index 0000000..e54f332 --- /dev/null +++ b/lib/pages/infrastructure/widgets/infrastructure_sidemenu.dart @@ -0,0 +1,452 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nethive_neo/providers/nethive/navigation_provider.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class InfrastructureSidemenu extends StatefulWidget { + final bool isExpanded; + final VoidCallback onToggle; + + const InfrastructureSidemenu({ + Key? key, + required this.isExpanded, + required this.onToggle, + }) : super(key: key); + + @override + State createState() => _InfrastructureSidemenuState(); +} + +class _InfrastructureSidemenuState extends State + with TickerProviderStateMixin { + late AnimationController _animationController; + late Animation _fadeAnimation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + )); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _fadeAnimation, + child: Consumer( + builder: (context, navigationProvider, child) { + return Container( + width: widget.isExpanded ? 280 : 70, + decoration: BoxDecoration( + gradient: AppTheme.of(context).darkBackgroundGradient, + border: Border( + right: BorderSide( + color: AppTheme.of(context).primaryColor.withOpacity(0.2), + width: 1, + ), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(2, 0), + ), + ], + ), + child: Column( + children: [ + // Header con logo y toggle + _buildHeader(navigationProvider), + + // Información del negocio seleccionado + if (widget.isExpanded && + navigationProvider.negocioSeleccionado != null) + _buildBusinessInfo(navigationProvider), + + // Lista de opciones del menú + Expanded( + child: _buildMenuItems(navigationProvider), + ), + + // Footer con información adicional + if (widget.isExpanded) _buildFooter(), + ], + ), + ); + }, + ), + ); + } + + Widget _buildHeader(NavigationProvider navigationProvider) { + return Container( + padding: EdgeInsets.all(widget.isExpanded ? 20 : 15), + decoration: BoxDecoration( + gradient: AppTheme.of(context).primaryGradient, + boxShadow: [ + BoxShadow( + color: AppTheme.of(context).primaryColor.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 5), + ), + ], + ), + child: Row( + children: [ + // Toggle button + GestureDetector( + onTap: widget.onToggle, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + widget.isExpanded ? Icons.menu_open : Icons.menu, + color: Colors.white, + size: 24, + ), + ), + ), + + if (widget.isExpanded) ...[ + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShaderMask( + shaderCallback: (bounds) => LinearGradient( + colors: [Colors.white, Colors.white.withOpacity(0.8)], + ).createShader(bounds), + child: const Text( + 'NETHIVE', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + letterSpacing: 1.2, + ), + ), + ), + Text( + 'Infraestructura', + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ], + ), + ); + } + + Widget _buildBusinessInfo(NavigationProvider navigationProvider) { + final negocio = navigationProvider.negocioSeleccionado!; + final empresa = navigationProvider.empresaSeleccionada!; + + return Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppTheme.of(context).primaryColor.withOpacity(0.1), + AppTheme.of(context).tertiaryColor.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppTheme.of(context).primaryColor.withOpacity(0.3), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(6), + ), + child: const Icon( + Icons.business_center, + color: Colors.white, + size: 16, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + empresa.nombre, + style: TextStyle( + color: AppTheme.of(context).primaryColor, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + negocio.nombre, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + '(${negocio.tipoLocal})', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 11, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildMenuItems(NavigationProvider navigationProvider) { + return ListView.builder( + padding: EdgeInsets.symmetric( + vertical: 8, + horizontal: widget.isExpanded ? 16 : 8, + ), + itemCount: navigationProvider.menuItems.length, + itemBuilder: (context, index) { + final menuItem = navigationProvider.menuItems[index]; + final isSelected = + navigationProvider.selectedMenuIndex == menuItem.index; + final isSpecial = menuItem.isSpecial; + + return TweenAnimationBuilder( + duration: Duration(milliseconds: 200 + (index * 50)), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return Transform.translate( + offset: Offset(-30 * (1 - value), 0), + child: Opacity( + opacity: value, + child: _buildMenuItem( + menuItem, + isSelected, + isSpecial, + navigationProvider, + ), + ), + ); + }, + ); + }, + ); + } + + Widget _buildMenuItem( + NavigationMenuItem menuItem, + bool isSelected, + bool isSpecial, + NavigationProvider navigationProvider, + ) { + return Container( + margin: const EdgeInsets.only(bottom: 4), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _handleMenuTap(menuItem, navigationProvider), + borderRadius: BorderRadius.circular(12), + child: Container( + padding: EdgeInsets.all(widget.isExpanded ? 12 : 8), + decoration: BoxDecoration( + gradient: isSelected + ? AppTheme.of(context).primaryGradient + : isSpecial + ? LinearGradient( + colors: [ + Colors.orange.withOpacity(0.1), + Colors.deepOrange.withOpacity(0.1), + ], + ) + : null, + borderRadius: BorderRadius.circular(12), + border: isSpecial + ? Border.all( + color: Colors.orange.withOpacity(0.3), + width: 1, + ) + : null, + ), + child: Row( + children: [ + Icon( + menuItem.icon, + color: isSelected + ? Colors.white + : isSpecial + ? Colors.orange + : AppTheme.of(context).primaryText, + size: 20, + ), + if (widget.isExpanded) ...[ + const SizedBox(width: 12), + Expanded( + child: Text( + menuItem.title, + style: TextStyle( + color: isSelected + ? Colors.white + : isSpecial + ? Colors.orange + : AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: + isSelected ? FontWeight.bold : FontWeight.w500, + ), + ), + ), + if (isSelected) + Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: 12, + ), + ), + ], + ], + ), + ), + ), + ), + ); + } + + Widget _buildFooter() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppTheme.of(context).primaryBackground.withOpacity(0.0), + AppTheme.of(context).primaryBackground, + ], + ), + ), + child: Column( + children: [ + Container( + height: 1, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + AppTheme.of(context).primaryColor.withOpacity(0.5), + Colors.transparent, + ], + ), + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Icon( + Icons.shield_outlined, + color: AppTheme.of(context).primaryColor, + size: 16, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Conexión segura', + style: TextStyle( + color: AppTheme.of(context).primaryColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ], + ), + ); + } + + void _handleMenuTap( + NavigationMenuItem menuItem, NavigationProvider navigationProvider) { + if (menuItem.isSpecial) { + // Si es "Empresas", regresar a la página de empresas + navigationProvider.clearSelection(); + context.go('/'); + } else { + // Cambiar la selección del menú + navigationProvider.setSelectedMenuIndex(menuItem.index); + + // Aquí puedes agregar navegación específica si es necesario + // Por ahora solo cambiaremos la vista en el layout principal + } + } +} diff --git a/lib/providers/nethive/empresas_negocios_provider.dart b/lib/providers/nethive/empresas_negocios_provider.dart index b6d73fe..18a23f5 100644 --- a/lib/providers/nethive/empresas_negocios_provider.dart +++ b/lib/providers/nethive/empresas_negocios_provider.dart @@ -137,6 +137,7 @@ class EmpresasNegociosProvider extends ChangeNotifier { ? "${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/imagenes/${negocio.imagenUrl}?${DateTime.now().millisecondsSinceEpoch}" : '', ), + 'acceder_infraestructura': PlutoCell(value: negocio.id), 'editar': PlutoCell(value: negocio.id), 'eliminar': PlutoCell(value: negocio.id), 'ver_componentes': PlutoCell(value: negocio.id), diff --git a/lib/providers/nethive/navigation_provider.dart b/lib/providers/nethive/navigation_provider.dart new file mode 100644 index 0000000..40b02a5 --- /dev/null +++ b/lib/providers/nethive/navigation_provider.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:nethive_neo/models/nethive/negocio_model.dart'; +import 'package:nethive_neo/models/nethive/empresa_model.dart'; +import 'package:nethive_neo/helpers/globals.dart'; + +class NavigationProvider extends ChangeNotifier { + // Estados principales + String? _negocioSeleccionadoId; + Negocio? _negocioSeleccionado; + Empresa? _empresaSeleccionada; + int _selectedMenuIndex = 0; + + // Getters + String? get negocioSeleccionadoId => _negocioSeleccionadoId; + Negocio? get negocioSeleccionado => _negocioSeleccionado; + Empresa? get empresaSeleccionada => _empresaSeleccionada; + int get selectedMenuIndex => _selectedMenuIndex; + + // Lista de opciones del sidemenu + final List menuItems = [ + NavigationMenuItem( + title: 'Dashboard', + icon: Icons.dashboard, + route: '/dashboard', + index: 0, + ), + NavigationMenuItem( + title: 'Inventario', + icon: Icons.inventory_2, + route: '/inventario', + index: 1, + ), + NavigationMenuItem( + title: 'Topología', + icon: Icons.account_tree, + route: '/topologia', + index: 2, + ), + NavigationMenuItem( + title: 'Alertas', + icon: Icons.warning, + route: '/alertas', + index: 3, + ), + NavigationMenuItem( + title: 'Configuración', + icon: Icons.settings, + route: '/configuracion', + index: 4, + ), + NavigationMenuItem( + title: 'Empresas', + icon: Icons.business, + route: '/empresas', + index: 5, + isSpecial: true, // Para diferenciarlo como opción de regreso + ), + ]; + + // Métodos para establecer el negocio seleccionado + Future setNegocioSeleccionado(String negocioId) async { + try { + _negocioSeleccionadoId = negocioId; + + // Obtener datos completos del negocio + final negocioResponse = await supabaseLU.from('negocio').select(''' + *, + empresa!inner(*) + ''').eq('id', negocioId).single(); + + _negocioSeleccionado = Negocio.fromMap(negocioResponse); + _empresaSeleccionada = Empresa.fromMap(negocioResponse['empresa']); + + // Reset menu selection when changing business + _selectedMenuIndex = 0; + + notifyListeners(); + } catch (e) { + print('Error al establecer negocio seleccionado: $e'); + } + } + + // Método para cambiar la selección del menú + void setSelectedMenuIndex(int index) { + _selectedMenuIndex = index; + notifyListeners(); + } + + // Método para limpiar la selección (al regresar a empresas) + void clearSelection() { + _negocioSeleccionadoId = null; + _negocioSeleccionado = null; + _empresaSeleccionada = null; + _selectedMenuIndex = 0; + notifyListeners(); + } + + // Método para obtener el item del menú por índice + NavigationMenuItem getMenuItemByIndex(int index) { + return menuItems.firstWhere((item) => item.index == index); + } + + // Método para obtener el item del menú por ruta + NavigationMenuItem? getMenuItemByRoute(String route) { + try { + return menuItems.firstWhere((item) => item.route == route); + } catch (e) { + return null; + } + } +} + +// Modelo para los items del menú +class NavigationMenuItem { + final String title; + final IconData icon; + final String route; + final int index; + final bool isSpecial; + + NavigationMenuItem({ + required this.title, + required this.icon, + required this.route, + required this.index, + this.isSpecial = false, + }); +} diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 0492a29..e13fa1d 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -1,3 +1,6 @@ export 'package:nethive_neo/providers/visual_state_provider.dart'; export 'package:nethive_neo/providers/users_provider.dart'; export 'package:nethive_neo/providers/user_provider.dart'; +export 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart'; +export 'package:nethive_neo/providers/nethive/componentes_provider.dart'; +export 'package:nethive_neo/providers/nethive/navigation_provider.dart'; diff --git a/lib/router/router.dart b/lib/router/router.dart index 809bb86..28c4c5a 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -1,14 +1,10 @@ -import 'package:nethive_neo/functions/no_transition_route.dart'; - import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:nethive_neo/helpers/globals.dart'; -import 'package:nethive_neo/models/models.dart'; import 'package:nethive_neo/pages/empresa_negocios/empresa_negocios_page.dart'; - +import 'package:nethive_neo/pages/infrastructure/infrastructure_layout.dart'; import 'package:nethive_neo/pages/pages.dart'; - import 'package:nethive_neo/services/navigation_service.dart'; /// The route configuration. @@ -45,12 +41,20 @@ final GoRouter router = GoRouter( color: Colors.amber, width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, - child: const Center(child: Text('Book Page Main'))); + child: const Center(child: Text('Empresa Negocios'))); } else { return const EmpresaNegociosPage(); } }, ), + GoRoute( + path: '/infrastructure/:negocioId', + name: 'infrastructure', + builder: (BuildContext context, GoRouterState state) { + final negocioId = state.pathParameters['negocioId']!; + return InfrastructureLayout(negocioId: negocioId); + }, + ), GoRoute( path: '/login', name: 'login',