import 'package:flutter/material.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/app_widgets.dart'; // ── Modelos locales ─────────────────────────────────────────────────────────── enum TruckStatus { disponible, enRuta, mantenimiento, detenido } extension TruckStatusX on TruckStatus { String get label => switch (this) { TruckStatus.disponible => 'Disponible', TruckStatus.enRuta => 'En ruta', TruckStatus.mantenimiento => 'Mantenimiento', TruckStatus.detenido => 'Detenido', }; AppStatusBadge get badge => switch (this) { TruckStatus.disponible => AppStatusBadge.green(label), TruckStatus.enRuta => AppStatusBadge.amber(label), TruckStatus.mantenimiento => AppStatusBadge.gray(label), TruckStatus.detenido => AppStatusBadge.gray(label), }; } class _AdminUser { final String id, nombre, apellido, email, telefono; const _AdminUser({ required this.id, required this.nombre, required this.apellido, required this.email, required this.telefono, }); String get nombreCompleto => '$nombre $apellido'; String get iniciales => '${nombre.isNotEmpty ? nombre[0] : ''}${apellido.isNotEmpty ? apellido[0] : ''}' .toUpperCase(); _AdminUser copyWith({String? nombre, String? apellido, String? email, String? telefono}) => _AdminUser(id: id, nombre: nombre ?? this.nombre, apellido: apellido ?? this.apellido, email: email ?? this.email, telefono: telefono ?? this.telefono); } class _AdminRoute { final String id, nombre, zona; final bool activa; const _AdminRoute({required this.id, required this.nombre, required this.zona, this.activa = true}); _AdminRoute copyWith({String? nombre, String? zona, bool? activa}) => _AdminRoute(id: id, nombre: nombre ?? this.nombre, zona: zona ?? this.zona, activa: activa ?? this.activa); } class _AdminTruck { final String id, placas, modelo, conductor, rutaId; final TruckStatus status; const _AdminTruck({ required this.id, required this.placas, required this.modelo, required this.conductor, required this.status, required this.rutaId, }); _AdminTruck copyWith({String? placas, String? modelo, String? conductor, TruckStatus? status, String? rutaId}) => _AdminTruck(id: id, placas: placas ?? this.placas, modelo: modelo ?? this.modelo, conductor: conductor ?? this.conductor, status: status ?? this.status, rutaId: rutaId ?? this.rutaId); } // ── Pantalla ────────────────────────────────────────────────────────────────── class AdminScreen extends StatefulWidget { const AdminScreen({super.key}); @override State createState() => _AdminScreenState(); } class _AdminScreenState extends State with SingleTickerProviderStateMixin { late final TabController _tabController; int _activeTab = 0; final List<_AdminUser> _usuarios = [ const _AdminUser(id: 'u-01', nombre: 'Laura', apellido: 'Gómez', email: 'laura@recolecta.com', telefono: '+52 461 987 1234'), const _AdminUser(id: 'u-02', nombre: 'Miguel', apellido: 'Sánchez', email: 'miguel@recolecta.com', telefono: '+52 461 123 7890'), ]; final List<_AdminRoute> _rutas = [ const _AdminRoute(id: 'RUTA-01', nombre: 'Ruta Norte', zona: 'Zona Norte'), const _AdminRoute(id: 'RUTA-02', nombre: 'Ruta Sur', zona: 'Zona Sur', activa: false), ]; final List<_AdminTruck> _camiones = [ const _AdminTruck(id: 't-01', placas: 'GTO-101', modelo: 'Volvo FH', conductor: 'Javier Pérez', status: TruckStatus.enRuta, rutaId: 'RUTA-01'), const _AdminTruck(id: 't-02', placas: 'GTO-103', modelo: 'Mercedes 1830', conductor: 'Ana Díaz', status: TruckStatus.disponible, rutaId: 'RUTA-02'), ]; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this) ..addListener(() { if (!_tabController.indexIsChanging) { setState(() => _activeTab = _tabController.index); } }); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.background, appBar: AppBar( title: const Text('Panel de administración'), bottom: TabBar( controller: _tabController, indicatorColor: Colors.white, labelColor: Colors.white, unselectedLabelColor: Colors.white70, tabs: const [ Tab(text: 'Usuarios'), Tab(text: 'Rutas'), Tab(text: 'Camiones'), ], ), ), body: TabBarView( controller: _tabController, children: [ _buildUsersTab(), _buildRoutesTab(), _buildTrucksTab(), ], ), floatingActionButton: FloatingActionButton.extended( onPressed: () { if (_activeTab == 0) _showUserForm(); else if (_activeTab == 1) _showRouteForm(); else _showTruckForm(); }, backgroundColor: AppTheme.primary, label: Text(_activeTab == 0 ? 'Nuevo usuario' : _activeTab == 1 ? 'Nueva ruta' : 'Nuevo camión'), icon: const Icon(Icons.add), ), ); } // ── Tab usuarios ──────────────────────────────────────────────────────────── Widget _buildUsersTab() { if (_usuarios.isEmpty) return _emptyState('No hay usuarios registrados.'); return ListView.separated( padding: const EdgeInsets.all(16), itemCount: _usuarios.length, separatorBuilder: (_, i) => const SizedBox(height: 12), itemBuilder: (context, i) { final u = _usuarios[i]; return AppCard( child: Row( children: [ CircleAvatar( backgroundColor: AppTheme.primaryLight, foregroundColor: AppTheme.primary, child: Text(u.iniciales), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(u.nombreCompleto, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), const SizedBox(height: 4), Text(u.email, style: const TextStyle(fontSize: 13, color: AppTheme.textSecondary)), Text(u.telefono, style: const TextStyle(fontSize: 13)), ], ), ), IconButton(icon: const Icon(Icons.edit_outlined, color: AppTheme.primary), onPressed: () => _showUserForm(user: u)), IconButton(icon: const Icon(Icons.delete_outline, color: AppTheme.danger), onPressed: () => _confirmDelete('usuario', () => setState(() => _usuarios.removeWhere((x) => x.id == u.id)))), ], ), ); }, ); } // ── Tab rutas ─────────────────────────────────────────────────────────────── Widget _buildRoutesTab() { if (_rutas.isEmpty) return _emptyState('No hay rutas registradas.'); return ListView.separated( padding: const EdgeInsets.all(16), itemCount: _rutas.length, separatorBuilder: (_, i) => const SizedBox(height: 12), itemBuilder: (context, i) { final r = _rutas[i]; return AppCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded(child: Text(r.nombre, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600))), r.activa ? AppStatusBadge.green('Activa') : AppStatusBadge.gray('Inactiva'), ], ), const SizedBox(height: 6), Text(r.zona, style: const TextStyle(fontSize: 13, color: AppTheme.textSecondary)), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon(onPressed: () => _showRouteForm(route: r), icon: const Icon(Icons.edit_outlined, size: 18), label: const Text('Editar')), const SizedBox(width: 8), TextButton.icon( onPressed: () => _confirmDelete('ruta', () => setState(() => _rutas.removeWhere((x) => x.id == r.id))), icon: const Icon(Icons.delete_outline, size: 18), label: const Text('Eliminar'), style: TextButton.styleFrom(foregroundColor: AppTheme.danger), ), ], ), ], ), ); }, ); } // ── Tab camiones ──────────────────────────────────────────────────────────── Widget _buildTrucksTab() { if (_camiones.isEmpty) return _emptyState('No hay camiones registrados.'); return ListView.separated( padding: const EdgeInsets.all(16), itemCount: _camiones.length, separatorBuilder: (_, i) => const SizedBox(height: 12), itemBuilder: (context, i) { final t = _camiones[i]; final ruta = _rutas.firstWhere((r) => r.id == t.rutaId, orElse: () => const _AdminRoute(id: '', nombre: 'Sin ruta', zona: '')); return AppCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded(child: Text(t.placas, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600))), t.status.badge, ], ), const SizedBox(height: 6), Text('${t.modelo} · ${t.conductor}', style: const TextStyle(fontSize: 13)), Text('Ruta: ${ruta.nombre}', style: const TextStyle(fontSize: 13, color: AppTheme.textSecondary)), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon(onPressed: () => _showTruckForm(truck: t), icon: const Icon(Icons.edit_outlined, size: 18), label: const Text('Editar')), const SizedBox(width: 8), TextButton.icon( onPressed: () => _confirmDelete('camión', () => setState(() => _camiones.removeWhere((x) => x.id == t.id))), icon: const Icon(Icons.delete_outline, size: 18), label: const Text('Eliminar'), style: TextButton.styleFrom(foregroundColor: AppTheme.danger), ), ], ), ], ), ); }, ); } Widget _emptyState(String msg) => Center(child: Padding(padding: const EdgeInsets.all(24), child: Text(msg, textAlign: TextAlign.center, style: const TextStyle(fontSize: 15, color: AppTheme.textSecondary)))); // ── Confirmación de borrado ───────────────────────────────────────────────── void _confirmDelete(String tipo, VoidCallback onConfirm) { showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: AppTheme.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.radiusLg)), title: Text('Eliminar $tipo'), content: Text('¿Deseas eliminar este $tipo?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), style: TextButton.styleFrom(foregroundColor: AppTheme.textSecondary), child: const Text('Cancelar')), TextButton( onPressed: () { onConfirm(); Navigator.pop(ctx); }, style: TextButton.styleFrom(foregroundColor: AppTheme.danger), child: const Text('Eliminar'), ), ], ), ); } // ── Formulario usuario ────────────────────────────────────────────────────── void _showUserForm({_AdminUser? user}) { final nombreCtrl = TextEditingController(text: user?.nombre); final apellidoCtrl = TextEditingController(text: user?.apellido); final emailCtrl = TextEditingController(text: user?.email); final telefonoCtrl = TextEditingController(text: user?.telefono); showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: AppTheme.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.radiusLg)), title: Text(user == null ? 'Nuevo usuario' : 'Editar usuario'), content: SingleChildScrollView( child: Column(mainAxisSize: MainAxisSize.min, children: [ TextField(controller: nombreCtrl, decoration: const InputDecoration(labelText: 'Nombre')), TextField(controller: apellidoCtrl, decoration: const InputDecoration(labelText: 'Apellido')), TextField(controller: emailCtrl, decoration: const InputDecoration(labelText: 'Correo'), keyboardType: TextInputType.emailAddress), TextField(controller: telefonoCtrl, decoration: const InputDecoration(labelText: 'Teléfono'), keyboardType: TextInputType.phone), ]), ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), style: TextButton.styleFrom(foregroundColor: AppTheme.textSecondary), child: const Text('Cancelar')), TextButton( onPressed: () { final nuevo = _AdminUser(id: user?.id ?? 'u-${DateTime.now().millisecondsSinceEpoch}', nombre: nombreCtrl.text.trim(), apellido: apellidoCtrl.text.trim(), email: emailCtrl.text.trim(), telefono: telefonoCtrl.text.trim()); setState(() { if (user == null) { _usuarios.add(nuevo); } else { final idx = _usuarios.indexWhere((x) => x.id == user.id); if (idx >= 0) _usuarios[idx] = nuevo; } }); Navigator.pop(ctx); }, child: Text(user == null ? 'Crear' : 'Guardar'), ), ], ), ); } // ── Formulario ruta ───────────────────────────────────────────────────────── void _showRouteForm({_AdminRoute? route}) { final nombreCtrl = TextEditingController(text: route?.nombre); final zonaCtrl = TextEditingController(text: route?.zona); bool activa = route?.activa ?? true; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setInner) => AlertDialog( backgroundColor: AppTheme.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.radiusLg)), title: Text(route == null ? 'Nueva ruta' : 'Editar ruta'), content: Column(mainAxisSize: MainAxisSize.min, children: [ TextField(controller: nombreCtrl, decoration: const InputDecoration(labelText: 'Nombre de ruta')), TextField(controller: zonaCtrl, decoration: const InputDecoration(labelText: 'Zona')), Row(children: [ const Expanded(child: Text('Ruta activa')), Switch.adaptive(value: activa, onChanged: (v) => setInner(() => activa = v)), ]), ]), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), style: TextButton.styleFrom(foregroundColor: AppTheme.textSecondary), child: const Text('Cancelar')), TextButton( onPressed: () { final nueva = _AdminRoute(id: route?.id ?? 'r-${DateTime.now().millisecondsSinceEpoch}', nombre: nombreCtrl.text.trim(), zona: zonaCtrl.text.trim(), activa: activa); setState(() { if (route == null) { _rutas.add(nueva); } else { final idx = _rutas.indexWhere((x) => x.id == route.id); if (idx >= 0) _rutas[idx] = nueva; } }); Navigator.pop(ctx); }, child: Text(route == null ? 'Crear' : 'Guardar'), ), ], ), ), ); } // ── Formulario camión ─────────────────────────────────────────────────────── void _showTruckForm({_AdminTruck? truck}) { final placasCtrl = TextEditingController(text: truck?.placas); final modeloCtrl = TextEditingController(text: truck?.modelo); final conductorCtrl = TextEditingController(text: truck?.conductor); TruckStatus status = truck?.status ?? TruckStatus.disponible; String selectedRuta = truck?.rutaId ?? (_rutas.isNotEmpty ? _rutas.first.id : ''); showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setInner) => AlertDialog( backgroundColor: AppTheme.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.radiusLg)), title: Text(truck == null ? 'Nuevo camión' : 'Editar camión'), content: SingleChildScrollView( child: Column(mainAxisSize: MainAxisSize.min, children: [ TextField(controller: placasCtrl, decoration: const InputDecoration(labelText: 'Placas')), TextField(controller: modeloCtrl, decoration: const InputDecoration(labelText: 'Modelo')), TextField(controller: conductorCtrl, decoration: const InputDecoration(labelText: 'Conductor')), const SizedBox(height: 12), DropdownButtonFormField( value: selectedRuta.isEmpty ? null : selectedRuta, decoration: const InputDecoration(labelText: 'Ruta'), items: _rutas.map((r) => DropdownMenuItem(value: r.id, child: Text(r.nombre))).toList(), onChanged: (v) { if (v != null) setInner(() => selectedRuta = v); }, ), const SizedBox(height: 12), DropdownButtonFormField( value: status, decoration: const InputDecoration(labelText: 'Estatus'), items: TruckStatus.values.map((s) => DropdownMenuItem(value: s, child: Text(s.label))).toList(), onChanged: (v) { if (v != null) setInner(() => status = v); }, ), ]), ), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), style: TextButton.styleFrom(foregroundColor: AppTheme.textSecondary), child: const Text('Cancelar')), TextButton( onPressed: () { final nuevo = _AdminTruck(id: truck?.id ?? 't-${DateTime.now().millisecondsSinceEpoch}', placas: placasCtrl.text.trim(), modelo: modeloCtrl.text.trim(), conductor: conductorCtrl.text.trim(), status: status, rutaId: selectedRuta); setState(() { if (truck == null) { _camiones.add(nuevo); } else { final idx = _camiones.indexWhere((x) => x.id == truck.id); if (idx >= 0) _camiones[idx] = nuevo; } }); Navigator.pop(ctx); }, child: Text(truck == null ? 'Crear' : 'Guardar'), ), ], ), ), ); } }