empresas y negocios selector integrado
This commit is contained in:
287
lib/pages/empresa_negocios/empresa_negocios_page.dart
Normal file
287
lib/pages/empresa_negocios/empresa_negocios_page.dart
Normal file
@@ -0,0 +1,287 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart';
|
||||
import 'package:nethive_neo/pages/empresa_negocios/widgets/empresa_selector_sidebar.dart';
|
||||
import 'package:nethive_neo/pages/empresa_negocios/widgets/negocios_table.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
|
||||
class EmpresaNegociosPage extends StatefulWidget {
|
||||
const EmpresaNegociosPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<EmpresaNegociosPage> createState() => _EmpresaNegociosPageState();
|
||||
}
|
||||
|
||||
class _EmpresaNegociosPageState extends State<EmpresaNegociosPage> {
|
||||
bool showMapView = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
body: Consumer<EmpresasNegociosProvider>(
|
||||
builder: (context, provider, child) {
|
||||
return Row(
|
||||
children: [
|
||||
// Sidebar izquierdo con empresas
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: EmpresaSelectorSidebar(
|
||||
provider: provider,
|
||||
onEmpresaSelected: (empresaId) {
|
||||
provider.setEmpresaSeleccionada(empresaId);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Área principal
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header con título y switch
|
||||
_buildHeader(provider),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Contenido principal (tabla o mapa)
|
||||
Expanded(
|
||||
child: showMapView
|
||||
? _buildMapView()
|
||||
: _buildTableView(provider),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(EmpresasNegociosProvider provider) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).secondaryBackground,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
provider.empresaSeleccionada != null
|
||||
? 'Sucursales de ${provider.empresaSeleccionada!.nombre}'
|
||||
: 'Selecciona una empresa para ver sus sucursales',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (provider.empresaSeleccionada != null)
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
'${provider.negocios.length} sucursales',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
provider.empresaSeleccionada!.nombre,
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).secondaryText,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Switch para cambiar vista
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
'Vista de Mapa',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Switch(
|
||||
value: showMapView,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
showMapView = value;
|
||||
});
|
||||
},
|
||||
activeColor: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableView(EmpresasNegociosProvider provider) {
|
||||
if (provider.empresaSeleccionada == null) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.business,
|
||||
size: 80,
|
||||
color: AppTheme.of(context).secondaryText,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Selecciona una empresa para ver sus sucursales',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).secondaryText,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).secondaryBackground,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Header de la tabla
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.store,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Sucursales',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'Mostrando 1 a ${provider.negocios.length} de ${provider.negocios.length} sucursales',
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Tabla de negocios
|
||||
Expanded(
|
||||
child: NegociosTable(provider: provider),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMapView() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Colors.blue,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.map,
|
||||
size: 80,
|
||||
color: Colors.blue[700],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Vista de Mapa',
|
||||
style: TextStyle(
|
||||
color: Colors.blue[700],
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Próximamente se implementará el mapa con las ubicaciones de las sucursales',
|
||||
style: TextStyle(
|
||||
color: Colors.blue[600],
|
||||
fontSize: 16,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
357
lib/pages/empresa_negocios/widgets/add_empresa_dialog.dart
Normal file
357
lib/pages/empresa_negocios/widgets/add_empresa_dialog.dart
Normal file
@@ -0,0 +1,357 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
|
||||
class AddEmpresaDialog extends StatefulWidget {
|
||||
final EmpresasNegociosProvider provider;
|
||||
|
||||
const AddEmpresaDialog({
|
||||
Key? key,
|
||||
required this.provider,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AddEmpresaDialog> createState() => _AddEmpresaDialogState();
|
||||
}
|
||||
|
||||
class _AddEmpresaDialogState extends State<AddEmpresaDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _nombreController = TextEditingController();
|
||||
final _rfcController = TextEditingController();
|
||||
final _direccionController = TextEditingController();
|
||||
final _telefonoController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nombreController.dispose();
|
||||
_rfcController.dispose();
|
||||
_direccionController.dispose();
|
||||
_telefonoController.dispose();
|
||||
_emailController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.business,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Añadir Nueva Empresa',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 500,
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Nombre
|
||||
TextFormField(
|
||||
controller: _nombreController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nombre de la empresa *',
|
||||
hintText: 'Ej: TechCorp Solutions',
|
||||
prefixIcon: Icon(Icons.business),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'El nombre es requerido';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// RFC
|
||||
TextFormField(
|
||||
controller: _rfcController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'RFC *',
|
||||
hintText: 'Ej: ABC123456789',
|
||||
prefixIcon: Icon(Icons.assignment),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'El RFC es requerido';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Dirección
|
||||
TextFormField(
|
||||
controller: _direccionController,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Dirección *',
|
||||
hintText: 'Dirección completa de la empresa',
|
||||
prefixIcon: Icon(Icons.location_on),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'La dirección es requerida';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Teléfono
|
||||
TextFormField(
|
||||
controller: _telefonoController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Teléfono *',
|
||||
hintText: 'Ej: +52 555 123 4567',
|
||||
prefixIcon: Icon(Icons.phone),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'El teléfono es requerido';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Email
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Email *',
|
||||
hintText: 'contacto@empresa.com',
|
||||
prefixIcon: Icon(Icons.email),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'El email es requerido';
|
||||
}
|
||||
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
|
||||
.hasMatch(value)) {
|
||||
return 'Email inválido';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Sección de archivos
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).secondaryBackground,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Archivos (Opcional)',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.of(context).primaryText,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Logo
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: widget.provider.selectLogo,
|
||||
icon: Icon(Icons.image),
|
||||
label: Text(
|
||||
widget.provider.logoFileName ??
|
||||
'Seleccionar Logo',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.provider.logoToUpload != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: widget.provider.getImageWidget(
|
||||
widget.provider.logoToUpload,
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Imagen
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: widget.provider.selectImagen,
|
||||
icon: Icon(Icons.photo),
|
||||
label: Text(
|
||||
widget.provider.imagenFileName ??
|
||||
'Seleccionar Imagen',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.provider.imagenToUpload != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: widget.provider.getImageWidget(
|
||||
widget.provider.imagenToUpload,
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () {
|
||||
widget.provider.resetFormData();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'Cancelar',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).secondaryText,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _isLoading ? null : _crearEmpresa,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: const Text('Crear Empresa'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _crearEmpresa() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final success = await widget.provider.crearEmpresa(
|
||||
nombre: _nombreController.text.trim(),
|
||||
rfc: _rfcController.text.trim(),
|
||||
direccion: _direccionController.text.trim(),
|
||||
telefono: _telefonoController.text.trim(),
|
||||
email: _emailController.text.trim(),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
if (success) {
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Empresa creada exitosamente'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Error al crear la empresa'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
487
lib/pages/empresa_negocios/widgets/add_negocio_dialog.dart
Normal file
487
lib/pages/empresa_negocios/widgets/add_negocio_dialog.dart
Normal file
@@ -0,0 +1,487 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
|
||||
class AddNegocioDialog extends StatefulWidget {
|
||||
final EmpresasNegociosProvider provider;
|
||||
final String empresaId;
|
||||
|
||||
const AddNegocioDialog({
|
||||
Key? key,
|
||||
required this.provider,
|
||||
required this.empresaId,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AddNegocioDialog> createState() => _AddNegocioDialogState();
|
||||
}
|
||||
|
||||
class _AddNegocioDialogState extends State<AddNegocioDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _nombreController = TextEditingController();
|
||||
final _direccionController = TextEditingController();
|
||||
final _latitudController = TextEditingController();
|
||||
final _longitudController = TextEditingController();
|
||||
final _tipoLocalController = TextEditingController(text: 'Sucursal');
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
final List<String> _tiposLocal = [
|
||||
'Sucursal',
|
||||
'Oficina Central',
|
||||
'Bodega',
|
||||
'Centro de Distribución',
|
||||
'Punto de Venta',
|
||||
'Otro'
|
||||
];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nombreController.dispose();
|
||||
_direccionController.dispose();
|
||||
_latitudController.dispose();
|
||||
_longitudController.dispose();
|
||||
_tipoLocalController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.store,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Añadir Nueva Sucursal',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 500,
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Información de la empresa
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
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: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.business,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Empresa: ${widget.provider.empresaSeleccionada?.nombre ?? ""}',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Nombre
|
||||
TextFormField(
|
||||
controller: _nombreController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nombre de la sucursal *',
|
||||
hintText: 'Ej: Sucursal Centro, Sede Norte',
|
||||
prefixIcon: Icon(Icons.store),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'El nombre es requerido';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Dirección
|
||||
TextFormField(
|
||||
controller: _direccionController,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Dirección *',
|
||||
hintText: 'Dirección completa de la sucursal',
|
||||
prefixIcon: Icon(Icons.location_on),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'La dirección es requerida';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Tipo de local
|
||||
DropdownButtonFormField<String>(
|
||||
value: _tipoLocalController.text,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Tipo de local *',
|
||||
prefixIcon: Icon(Icons.category),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
items: _tiposLocal.map((String tipo) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: tipo,
|
||||
child: Text(tipo),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? newValue) {
|
||||
if (newValue != null) {
|
||||
_tipoLocalController.text = newValue;
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Selecciona un tipo de local';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Coordenadas
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).secondaryBackground,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.map,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Coordenadas Geográficas *',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.of(context).primaryText,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
// Latitud
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _latitudController,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'^-?\d*\.?\d*'),
|
||||
),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Latitud',
|
||||
hintText: 'Ej: 19.4326',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Requerido';
|
||||
}
|
||||
final lat = double.tryParse(value);
|
||||
if (lat == null) {
|
||||
return 'Número inválido';
|
||||
}
|
||||
if (lat < -90 || lat > 90) {
|
||||
return 'Rango: -90 a 90';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Longitud
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _longitudController,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r'^-?\d*\.?\d*'),
|
||||
),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Longitud',
|
||||
hintText: 'Ej: -99.1332',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Requerido';
|
||||
}
|
||||
final lng = double.tryParse(value);
|
||||
if (lng == null) {
|
||||
return 'Número inválido';
|
||||
}
|
||||
if (lng < -180 || lng > 180) {
|
||||
return 'Rango: -180 a 180';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Tip: Puedes obtener las coordenadas desde Google Maps',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppTheme.of(context).secondaryText,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Sección de archivos
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).secondaryBackground,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Archivos (Opcional)',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppTheme.of(context).primaryText,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Logo
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: widget.provider.selectLogo,
|
||||
icon: Icon(Icons.image),
|
||||
label: Text(
|
||||
widget.provider.logoFileName ??
|
||||
'Seleccionar Logo',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.provider.logoToUpload != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: widget.provider.getImageWidget(
|
||||
widget.provider.logoToUpload,
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Imagen
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: widget.provider.selectImagen,
|
||||
icon: Icon(Icons.photo),
|
||||
label: Text(
|
||||
widget.provider.imagenFileName ??
|
||||
'Seleccionar Imagen',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.provider.imagenToUpload != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: widget.provider.getImageWidget(
|
||||
widget.provider.imagenToUpload,
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () {
|
||||
widget.provider.resetFormData();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'Cancelar',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).secondaryText,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _isLoading ? null : _crearNegocio,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: const Text('Crear Sucursal'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _crearNegocio() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final success = await widget.provider.crearNegocio(
|
||||
empresaId: widget.empresaId,
|
||||
nombre: _nombreController.text.trim(),
|
||||
direccion: _direccionController.text.trim(),
|
||||
latitud: double.parse(_latitudController.text.trim()),
|
||||
longitud: double.parse(_longitudController.text.trim()),
|
||||
tipoLocal: _tipoLocalController.text.trim(),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
if (success) {
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Sucursal creada exitosamente'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Error al crear la sucursal'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
285
lib/pages/empresa_negocios/widgets/empresa_selector_sidebar.dart
Normal file
285
lib/pages/empresa_negocios/widgets/empresa_selector_sidebar.dart
Normal file
@@ -0,0 +1,285 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nethive_neo/helpers/globals.dart';
|
||||
import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart';
|
||||
import 'package:nethive_neo/pages/empresa_negocios/widgets/add_empresa_dialog.dart';
|
||||
import 'package:nethive_neo/pages/empresa_negocios/widgets/add_negocio_dialog.dart';
|
||||
import 'package:nethive_neo/theme/theme.dart';
|
||||
|
||||
class EmpresaSelectorSidebar extends StatelessWidget {
|
||||
final EmpresasNegociosProvider provider;
|
||||
final Function(String) onEmpresaSelected;
|
||||
|
||||
const EmpresaSelectorSidebar({
|
||||
Key? key,
|
||||
required this.provider,
|
||||
required this.onEmpresaSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: AppTheme.of(context).secondaryBackground,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Seleccionar Empresa',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Haz una empresas para ver sus sucursales',
|
||||
style: TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Lista de empresas
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(8),
|
||||
itemCount: provider.empresas.length,
|
||||
itemBuilder: (context, index) {
|
||||
final empresa = provider.empresas[index];
|
||||
final isSelected = provider.empresaSeleccionadaId == empresa.id;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppTheme.of(context).primaryColor.withOpacity(0.1)
|
||||
: AppTheme.of(context).primaryBackground,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? AppTheme.of(context).primaryColor
|
||||
: Colors.grey.withOpacity(0.3),
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => onEmpresaSelected(empresa.id),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// Logo de la empresa
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: empresa.logoUrl != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.network(
|
||||
"${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/logos/${empresa.logoUrl}",
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) {
|
||||
return Image.asset(
|
||||
'assets/images/placeholder_no_image.jpg',
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Image.asset(
|
||||
'assets/images/placeholder_no_image.jpg',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
empresa.nombre,
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
'Tecnología',
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppTheme.of(context).secondaryText,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Sucursales: ${isSelected ? provider.negocios.length : '...'}',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).secondaryText,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Botón añadir empresa
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddEmpresaDialog(provider: provider),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.add, color: Colors.white),
|
||||
label: const Text(
|
||||
'Añadir Empresa',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Información de empresa seleccionada
|
||||
if (provider.empresaSeleccionada != null) ...[
|
||||
Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: AppTheme.of(context).primaryColor.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Empresa seleccionada:',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).secondaryText,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
provider.empresaSeleccionada!.nombre,
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.store,
|
||||
size: 16,
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${provider.negocios.length} Sucursales',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddNegocioDialog(
|
||||
provider: provider,
|
||||
empresaId: provider.empresaSeleccionada!.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.add, size: 16),
|
||||
label: const Text(
|
||||
'Añadir Sucursal',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
447
lib/pages/empresa_negocios/widgets/negocios_table.dart
Normal file
447
lib/pages/empresa_negocios/widgets/negocios_table.dart
Normal file
@@ -0,0 +1,447 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pluto_grid/pluto_grid.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';
|
||||
|
||||
class NegociosTable extends StatelessWidget {
|
||||
final EmpresasNegociosProvider provider;
|
||||
|
||||
const NegociosTable({
|
||||
Key? key,
|
||||
required this.provider,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return 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: 14,
|
||||
),
|
||||
columnTextStyle: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
menuBackgroundColor: AppTheme.of(context).secondaryBackground,
|
||||
gridBorderRadius: BorderRadius.circular(8),
|
||||
rowHeight: 60,
|
||||
),
|
||||
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: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PlutoColumn(
|
||||
title: 'Nombre de Sucursal',
|
||||
field: 'nombre',
|
||||
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: Row(
|
||||
children: [
|
||||
// Logo del negocio
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child:
|
||||
rendererContext.row.cells['logo_url']?.value != null &&
|
||||
rendererContext.row.cells['logo_url']!.value
|
||||
.toString()
|
||||
.isNotEmpty
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Image.network(
|
||||
rendererContext.row.cells['logo_url']!.value
|
||||
.toString(),
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Image.asset(
|
||||
'assets/images/placeholder_no_image.jpg',
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Image.asset(
|
||||
'assets/images/placeholder_no_image.jpg',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
rendererContext.cell.value.toString(),
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PlutoColumn(
|
||||
title: 'Ciudad',
|
||||
field: 'direccion',
|
||||
titleTextAlign: PlutoColumnTextAlign.center,
|
||||
textAlign: PlutoColumnTextAlign.center,
|
||||
width: 180,
|
||||
type: PlutoColumnType.text(),
|
||||
enableEditingMode: false,
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
enableContextMenu: false,
|
||||
enableDropToResize: false,
|
||||
renderer: (rendererContext) {
|
||||
// Extraer solo la ciudad de la dirección completa
|
||||
String direccionCompleta = rendererContext.cell.value.toString();
|
||||
String ciudad = _extraerCiudad(direccionCompleta);
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
ciudad,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PlutoColumn(
|
||||
title: 'Empleados',
|
||||
field: 'tipo_local',
|
||||
titleTextAlign: PlutoColumnTextAlign.center,
|
||||
textAlign: PlutoColumnTextAlign.center,
|
||||
width: 120,
|
||||
type: PlutoColumnType.text(),
|
||||
enableEditingMode: false,
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
enableContextMenu: false,
|
||||
enableDropToResize: false,
|
||||
renderer: (rendererContext) {
|
||||
// Simulamos cantidad de empleados basado en el tipo de local
|
||||
String empleados =
|
||||
rendererContext.cell.value.toString() == 'Sucursal'
|
||||
? '95'
|
||||
: '120';
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
'$empleados empleados',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PlutoColumn(
|
||||
title: 'Dirección Completa',
|
||||
field: 'direccion_completa',
|
||||
titleTextAlign: PlutoColumnTextAlign.center,
|
||||
textAlign: PlutoColumnTextAlign.center,
|
||||
width: 280,
|
||||
type: PlutoColumnType.text(),
|
||||
enableEditingMode: false,
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
enableContextMenu: false,
|
||||
enableDropToResize: false,
|
||||
renderer: (rendererContext) {
|
||||
// Usamos la dirección del row en lugar del cell
|
||||
String direccion =
|
||||
rendererContext.row.cells['direccion']?.value.toString() ?? '';
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
direccion,
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PlutoColumn(
|
||||
title: 'Coordenadas',
|
||||
field: 'latitud',
|
||||
titleTextAlign: PlutoColumnTextAlign.center,
|
||||
textAlign: PlutoColumnTextAlign.center,
|
||||
width: 150,
|
||||
type: PlutoColumnType.text(),
|
||||
enableEditingMode: false,
|
||||
backgroundColor: AppTheme.of(context).primaryColor,
|
||||
enableContextMenu: false,
|
||||
enableDropToResize: false,
|
||||
renderer: (rendererContext) {
|
||||
final latitud = rendererContext.row.cells['latitud']?.value ?? '0';
|
||||
final longitud =
|
||||
rendererContext.row.cells['longitud']?.value ?? '0';
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Lat: ${double.parse(latitud.toString()).toStringAsFixed(4)}',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Lng: ${double.parse(longitud.toString()).toStringAsFixed(4)}',
|
||||
style: TextStyle(
|
||||
color: AppTheme.of(context).primaryText,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
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 editar
|
||||
Tooltip(
|
||||
message: 'Editar negocio',
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// TODO: Implementar edición
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Función de edición próximamente')),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.edit,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Botón ver componentes
|
||||
Tooltip(
|
||||
message: 'Ver componentes',
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// TODO: Navegar a componentes
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Navegando a componentes de ${rendererContext.row.cells['nombre']?.value}',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.inventory_2,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Botón eliminar
|
||||
Tooltip(
|
||||
message: 'Eliminar negocio',
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
_showDeleteDialog(
|
||||
context,
|
||||
rendererContext.row.cells['id']?.value,
|
||||
rendererContext.row.cells['nombre']?.value,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.delete,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
rows: provider.negociosRows,
|
||||
onLoaded: (event) {
|
||||
provider.negociosStateManager = event.stateManager;
|
||||
},
|
||||
createFooter: (stateManager) {
|
||||
stateManager.setPageSize(10, notify: false);
|
||||
return PlutoPagination(stateManager);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showDeleteDialog(
|
||||
BuildContext context, String? negocioId, String? nombre) {
|
||||
if (negocioId == null) return;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
title: const Text('Confirmar eliminación'),
|
||||
content: Text(
|
||||
'¿Estás seguro de que deseas eliminar la sucursal "$nombre"?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
'Cancelar',
|
||||
style: TextStyle(color: AppTheme.of(context).secondaryText),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
final success = await provider.eliminarNegocio(negocioId);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
success
|
||||
? 'Sucursal eliminada correctamente'
|
||||
: 'Error al eliminar la sucursal',
|
||||
),
|
||||
backgroundColor: success ? Colors.green : Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
'Eliminar',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _extraerCiudad(String direccionCompleta) {
|
||||
// Lógica para extraer la ciudad de la dirección completa
|
||||
// Suponiendo que la ciudad es la segunda palabra en la dirección
|
||||
List<String> partes = direccionCompleta.split(',');
|
||||
return partes.length > 1 ? partes[1].trim() : direccionCompleta;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user