Paginas creadas wip
This commit is contained in:
@@ -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/users_provider.dart';
|
||||||
import 'package:nethive_neo/providers/nethive/empresas_negocios_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/componentes_provider.dart';
|
||||||
|
import 'package:nethive_neo/providers/nethive/navigation_provider.dart';
|
||||||
import 'package:nethive_neo/helpers/globals.dart';
|
import 'package:nethive_neo/helpers/globals.dart';
|
||||||
import 'package:url_strategy/url_strategy.dart';
|
import 'package:url_strategy/url_strategy.dart';
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ void main() async {
|
|||||||
ChangeNotifierProvider(create: (_) => UsersProvider()),
|
ChangeNotifierProvider(create: (_) => UsersProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => EmpresasNegociosProvider()),
|
ChangeNotifierProvider(create: (_) => EmpresasNegociosProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => ComponentesProvider()),
|
ChangeNotifierProvider(create: (_) => ComponentesProvider()),
|
||||||
|
ChangeNotifierProvider(create: (_) => NavigationProvider()),
|
||||||
],
|
],
|
||||||
child: const MyApp(),
|
child: const MyApp(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pluto_grid/pluto_grid.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/providers/nethive/empresas_negocios_provider.dart';
|
||||||
import 'package:nethive_neo/pages/widgets/animated_hover_button.dart';
|
import 'package:nethive_neo/pages/widgets/animated_hover_button.dart';
|
||||||
import 'package:nethive_neo/theme/theme.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(
|
PlutoColumn(
|
||||||
title: 'Acciones',
|
title: 'Acciones',
|
||||||
field: 'editar',
|
field: 'editar',
|
||||||
|
|||||||
448
lib/pages/infrastructure/infrastructure_layout.dart
Normal file
448
lib/pages/infrastructure/infrastructure_layout.dart
Normal file
@@ -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<InfrastructureLayout> createState() => _InfrastructureLayoutState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InfrastructureLayoutState extends State<InfrastructureLayout>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
bool _isSidebarExpanded = true;
|
||||||
|
late AnimationController _fadeController;
|
||||||
|
late Animation<double> _fadeAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fadeController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_fadeAnimation = Tween<double>(
|
||||||
|
begin: 0.0,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _fadeController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Establecer el negocio seleccionado
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context
|
||||||
|
.read<NavigationProvider>()
|
||||||
|
.setNegocioSeleccionado(widget.negocioId);
|
||||||
|
context
|
||||||
|
.read<ComponentesProvider>()
|
||||||
|
.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<NavigationProvider>(
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
lib/pages/infrastructure/pages/alertas_page.dart
Normal file
110
lib/pages/infrastructure/pages/alertas_page.dart
Normal file
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
110
lib/pages/infrastructure/pages/configuracion_page.dart
Normal file
110
lib/pages/infrastructure/pages/configuracion_page.dart
Normal file
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
638
lib/pages/infrastructure/pages/dashboard_page.dart
Normal file
638
lib/pages/infrastructure/pages/dashboard_page.dart
Normal file
@@ -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<DashboardPage> createState() => _DashboardPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DashboardPageState extends State<DashboardPage>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _fadeAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 800),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_fadeAnimation = Tween<double>(
|
||||||
|
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<NavigationProvider, ComponentesProvider>(
|
||||||
|
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<double>(
|
||||||
|
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<int>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
879
lib/pages/infrastructure/pages/inventario_page.dart
Normal file
879
lib/pages/infrastructure/pages/inventario_page.dart
Normal file
@@ -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<InventarioPage> createState() => _InventarioPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InventarioPageState extends State<InventarioPage>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _fadeAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_fadeAnimation = Tween<double>(
|
||||||
|
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<ComponentesProvider>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
108
lib/pages/infrastructure/pages/topologia_page.dart
Normal file
108
lib/pages/infrastructure/pages/topologia_page.dart
Normal file
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
452
lib/pages/infrastructure/widgets/infrastructure_sidemenu.dart
Normal file
452
lib/pages/infrastructure/widgets/infrastructure_sidemenu.dart
Normal file
@@ -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<InfrastructureSidemenu> createState() => _InfrastructureSidemenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InfrastructureSidemenuState extends State<InfrastructureSidemenu>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _fadeAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_fadeAnimation = Tween<double>(
|
||||||
|
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<NavigationProvider>(
|
||||||
|
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<double>(
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -137,6 +137,7 @@ class EmpresasNegociosProvider extends ChangeNotifier {
|
|||||||
? "${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/imagenes/${negocio.imagenUrl}?${DateTime.now().millisecondsSinceEpoch}"
|
? "${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/imagenes/${negocio.imagenUrl}?${DateTime.now().millisecondsSinceEpoch}"
|
||||||
: '',
|
: '',
|
||||||
),
|
),
|
||||||
|
'acceder_infraestructura': PlutoCell(value: negocio.id),
|
||||||
'editar': PlutoCell(value: negocio.id),
|
'editar': PlutoCell(value: negocio.id),
|
||||||
'eliminar': PlutoCell(value: negocio.id),
|
'eliminar': PlutoCell(value: negocio.id),
|
||||||
'ver_componentes': PlutoCell(value: negocio.id),
|
'ver_componentes': PlutoCell(value: negocio.id),
|
||||||
|
|||||||
128
lib/providers/nethive/navigation_provider.dart
Normal file
128
lib/providers/nethive/navigation_provider.dart
Normal file
@@ -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<NavigationMenuItem> 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<void> 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
export 'package:nethive_neo/providers/visual_state_provider.dart';
|
export 'package:nethive_neo/providers/visual_state_provider.dart';
|
||||||
export 'package:nethive_neo/providers/users_provider.dart';
|
export 'package:nethive_neo/providers/users_provider.dart';
|
||||||
export 'package:nethive_neo/providers/user_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';
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import 'package:nethive_neo/functions/no_transition_route.dart';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'package:nethive_neo/helpers/globals.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/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/pages/pages.dart';
|
||||||
|
|
||||||
import 'package:nethive_neo/services/navigation_service.dart';
|
import 'package:nethive_neo/services/navigation_service.dart';
|
||||||
|
|
||||||
/// The route configuration.
|
/// The route configuration.
|
||||||
@@ -45,12 +41,20 @@ final GoRouter router = GoRouter(
|
|||||||
color: Colors.amber,
|
color: Colors.amber,
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: MediaQuery.of(context).size.height,
|
height: MediaQuery.of(context).size.height,
|
||||||
child: const Center(child: Text('Book Page Main')));
|
child: const Center(child: Text('Empresa Negocios')));
|
||||||
} else {
|
} else {
|
||||||
return const EmpresaNegociosPage();
|
return const EmpresaNegociosPage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/infrastructure/:negocioId',
|
||||||
|
name: 'infrastructure',
|
||||||
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
|
final negocioId = state.pathParameters['negocioId']!;
|
||||||
|
return InfrastructureLayout(negocioId: negocioId);
|
||||||
|
},
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
|
|||||||
Reference in New Issue
Block a user