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/nethive/empresas_negocios_provider.dart';
|
||||
import 'package:nethive_neo/providers/nethive/componentes_provider.dart';
|
||||
import 'package:nethive_neo/providers/nethive/navigation_provider.dart';
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:url_strategy/url_strategy.dart';
|
||||
|
||||
@@ -46,6 +47,7 @@ void main() async {
|
||||
ChangeNotifierProvider(create: (_) => UsersProvider()),
|
||||
ChangeNotifierProvider(create: (_) => EmpresasNegociosProvider()),
|
||||
ChangeNotifierProvider(create: (_) => ComponentesProvider()),
|
||||
ChangeNotifierProvider(create: (_) => NavigationProvider()),
|
||||
],
|
||||
child: const MyApp(),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pluto_grid/pluto_grid.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart';
|
||||
import 'package:nethive_neo/pages/widgets/animated_hover_button.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
@@ -279,6 +280,87 @@ class NegociosTable extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
PlutoColumn(
|
||||
title: 'Infraestructura',
|
||||
field: 'acceder_infraestructura',
|
||||
titleTextAlign: PlutoColumnTextAlign.center,
|
||||
textAlign: PlutoColumnTextAlign.center,
|
||||
width: 200,
|
||||
type: PlutoColumnType.text(),
|
||||
enableEditingMode: false,
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
enableContextMenu: false,
|
||||
enableDropToResize: false,
|
||||
renderer: (rendererContext) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.orange.shade600,
|
||||
Colors.deepOrange.shade500,
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.orange.withOpacity(0.4),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
final negocioId =
|
||||
rendererContext.row.cells['id']?.value;
|
||||
final negocioNombre =
|
||||
rendererContext.row.cells['nombre']?.value;
|
||||
|
||||
if (negocioId != null) {
|
||||
// Navegar al layout principal con el negocio seleccionado
|
||||
context.go('/infrastructure/$negocioId');
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.developer_board,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Acceder a\nInfraestructura',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PlutoColumn(
|
||||
title: 'Acciones',
|
||||
field: 'editar',
|
||||
|
||||
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}"
|
||||
: '',
|
||||
),
|
||||
'acceder_infraestructura': PlutoCell(value: negocio.id),
|
||||
'editar': PlutoCell(value: negocio.id),
|
||||
'eliminar': 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/users_provider.dart';
|
||||
export 'package:nethive_neo/providers/user_provider.dart';
|
||||
export 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart';
|
||||
export 'package:nethive_neo/providers/nethive/componentes_provider.dart';
|
||||
export 'package:nethive_neo/providers/nethive/navigation_provider.dart';
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import 'package:nethive_neo/functions/no_transition_route.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:nethive_neo/models/models.dart';
|
||||
import 'package:nethive_neo/pages/empresa_negocios/empresa_negocios_page.dart';
|
||||
|
||||
import 'package:nethive_neo/pages/infrastructure/infrastructure_layout.dart';
|
||||
import 'package:nethive_neo/pages/pages.dart';
|
||||
|
||||
import 'package:nethive_neo/services/navigation_service.dart';
|
||||
|
||||
/// The route configuration.
|
||||
@@ -45,12 +41,20 @@ final GoRouter router = GoRouter(
|
||||
color: Colors.amber,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: const Center(child: Text('Book Page Main')));
|
||||
child: const Center(child: Text('Empresa Negocios')));
|
||||
} else {
|
||||
return const EmpresaNegociosPage();
|
||||
}
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/infrastructure/:negocioId',
|
||||
name: 'infrastructure',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
final negocioId = state.pathParameters['negocioId']!;
|
||||
return InfrastructureLayout(negocioId: negocioId);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
|
||||
Reference in New Issue
Block a user