import 'dart:async'; import 'package:flutter/material.dart'; import '../theme/app_theme.dart'; import '../models/models.dart'; import '../widgets/widgets.dart' as w; // ── Modelo de parada ────────────────────────────────────────────────────────── enum EstadoParada { pendiente, enCamino, completada, saltada } class StopModel { final String id; final String direccion; final String colonia; final String referencias; final int orden; EstadoParada estado; StopModel({ required this.id, required this.direccion, required this.colonia, required this.referencias, required this.orden, this.estado = EstadoParada.pendiente, }); } // ── Shell principal del Chofer ───────────────────────────────────────────────── class DriverShell extends StatefulWidget { const DriverShell({super.key}); @override State createState() => _DriverShellState(); } class _DriverShellState extends State { int _currentIndex = 0; final List _screens = const [ DriverRouteScreen(), DriverStopsScreen(), DriverHistoryScreen(), DriverProfileScreen(), ]; @override Widget build(BuildContext context) { return Scaffold( body: IndexedStack(index: _currentIndex, children: _screens), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: (i) => setState(() => _currentIndex = i), type: BottomNavigationBarType.fixed, backgroundColor: AppTheme.surface, selectedItemColor: AppTheme.primary, unselectedItemColor: AppTheme.textSecondary, selectedFontSize: 11, unselectedFontSize: 11, elevation: 12, items: const [ BottomNavigationBarItem( icon: Icon(Icons.map_outlined), activeIcon: Icon(Icons.map), label: 'Mi ruta', ), BottomNavigationBarItem( icon: Icon(Icons.list_alt_outlined), activeIcon: Icon(Icons.list_alt), label: 'Paradas', ), BottomNavigationBarItem( icon: Icon(Icons.history_outlined), activeIcon: Icon(Icons.history), label: 'Historial', ), BottomNavigationBarItem( icon: Icon(Icons.person_outline), activeIcon: Icon(Icons.person), label: 'Perfil', ), ], ), ); } } // ── Pantalla principal: Mi ruta (estado del turno) ──────────────────────────── class DriverRouteScreen extends StatefulWidget { const DriverRouteScreen({super.key}); @override State createState() => _DriverRouteScreenState(); } class _DriverRouteScreenState extends State { bool _turnoActivo = false; bool _enPausa = false; Timer? _timer; Duration _duracion = Duration.zero; // Datos de ejemplo final TruckLocation _camion = TruckLocation( id: 'truck-01', ruta: 'Ruta Norte', latitud: 20.5255, longitud: -100.8220, ultimaActualizacion: DateTime.now(), enServicio: true, ); @override void dispose() { _timer?.cancel(); super.dispose(); } void _iniciarTurno() { setState(() { _turnoActivo = true; _enPausa = false; }); _timer = Timer.periodic(const Duration(seconds: 1), (_) { if (!_enPausa && mounted) { setState(() => _duracion += const Duration(seconds: 1)); } }); } void _pausarReanudar() { setState(() => _enPausa = !_enPausa); } void _finalizarTurno() { showDialog( context: context, builder: (_) => AlertDialog( backgroundColor: AppTheme.surface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppTheme.radiusLg)), title: const Text('Finalizar turno', style: TextStyle( fontSize: 17, fontWeight: FontWeight.w700, color: AppTheme.textPrimary)), content: const Text( '¿Confirmas que has terminado el recorrido de hoy?', style: TextStyle(fontSize: 14, color: AppTheme.textSecondary), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), style: TextButton.styleFrom( foregroundColor: AppTheme.textSecondary), child: const Text('Cancelar'), ), TextButton( onPressed: () { Navigator.pop(context); _timer?.cancel(); setState(() { _turnoActivo = false; _enPausa = false; _duracion = Duration.zero; }); }, style: TextButton.styleFrom(foregroundColor: AppTheme.danger), child: const Text('Finalizar', style: TextStyle(fontWeight: FontWeight.w600)), ), ], ), ); } String get _tiempoFormateado { final h = _duracion.inHours.toString().padLeft(2, '0'); final m = (_duracion.inMinutes % 60).toString().padLeft(2, '0'); final s = (_duracion.inSeconds % 60).toString().padLeft(2, '0'); return '$h:$m:$s'; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.background, appBar: AppBar( title: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(AppTheme.radiusSm), ), child: const Icon(Icons.directions_bus_rounded, color: Colors.white, size: 18), ), const SizedBox(width: 10), const Text('Panel del chofer'), ], ), actions: [ if (_turnoActivo) Container( margin: const EdgeInsets.only(right: 12), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(AppTheme.radiusFull), ), child: Row( children: [ Container( width: 7, height: 7, decoration: BoxDecoration( color: _enPausa ? Colors.amber : const Color(0xFF7AFFC5), shape: BoxShape.circle, ), ), const SizedBox(width: 6), Text( _enPausa ? 'Pausado' : 'En servicio', style: const TextStyle( fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600), ), ], ), ), ], ), body: ListView( padding: const EdgeInsets.all(16), children: [ // ── Datos del chofer ────────────────────────────────────── _DriverInfoBanner(ruta: _camion.ruta), const SizedBox(height: 16), // ── Cronómetro / estado de turno ────────────────────────── _TurnoCronometro( turnoActivo: _turnoActivo, enPausa: _enPausa, tiempo: _tiempoFormateado, ), const SizedBox(height: 16), // ── Botones de control ──────────────────────────────────── if (!_turnoActivo) SizedBox( width: double.infinity, height: 52, child: ElevatedButton.icon( onPressed: _iniciarTurno, icon: const Icon(Icons.play_circle_outline_rounded), label: const Text('Iniciar turno'), ), ) else Row( children: [ Expanded( child: SizedBox( height: 52, child: OutlinedButton.icon( onPressed: _pausarReanudar, style: OutlinedButton.styleFrom( foregroundColor: AppTheme.amber, side: const BorderSide(color: AppTheme.amber), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppTheme.radiusMd), ), ), icon: Icon(_enPausa ? Icons.play_circle_outline_rounded : Icons.pause_circle_outline_rounded), label: Text(_enPausa ? 'Reanudar' : 'Pausar', style: const TextStyle( fontWeight: FontWeight.w600)), ), ), ), const SizedBox(width: 12), Expanded( child: SizedBox( height: 52, child: ElevatedButton.icon( onPressed: _finalizarTurno, style: ElevatedButton.styleFrom( backgroundColor: AppTheme.danger), icon: const Icon(Icons.stop_circle_outlined), label: const Text('Finalizar', style: TextStyle(fontWeight: FontWeight.w600)), ), ), ), ], ), const SizedBox(height: 24), // ── Estadísticas del día ────────────────────────────────── w.SectionTitle(title: 'Hoy'), Row( children: [ Expanded( child: _SmallStatCard( icon: Icons.location_on_outlined, label: 'Paradas', value: '14 / 22', color: AppTheme.primary, bgColor: AppTheme.primaryLight, ), ), const SizedBox(width: 12), Expanded( child: _SmallStatCard( icon: Icons.notifications_active_outlined, label: 'Alertas enviadas', value: '61', color: AppTheme.blue, bgColor: AppTheme.blueLight, ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: _SmallStatCard( icon: Icons.access_time_outlined, label: 'Tiempo en ruta', value: _turnoActivo ? _tiempoFormateado : '--:--:--', color: AppTheme.amber, bgColor: AppTheme.amberLight, ), ), const SizedBox(width: 12), Expanded( child: _SmallStatCard( icon: Icons.speed_outlined, label: 'Velocidad prom.', value: '12 km/h', color: AppTheme.primaryDark, bgColor: AppTheme.primaryLight, ), ), ], ), const SizedBox(height: 24), // ── Próxima parada ──────────────────────────────────────── w.SectionTitle(title: 'Próxima parada'), _ProximaParadaCard(), const SizedBox(height: 24), // ── Acciones rápidas ────────────────────────────────────── w.SectionTitle(title: 'Acciones rápidas'), w.MenuTile( icon: Icons.report_problem_outlined, title: 'Reportar incidencia', subtitle: 'Tráfico, avería, desvío…', onTap: () => _mostrarReporteIncidencia(context), ), w.MenuTile( icon: Icons.local_gas_station_outlined, title: 'Registrar carga de combustible', onTap: () {}, ), w.MenuTile( icon: Icons.phone_in_talk_outlined, title: 'Contactar a Control', subtitle: '+52 461 800 0000', onTap: () {}, ), const SizedBox(height: 32), ], ), ); } void _mostrarReporteIncidencia(BuildContext context) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: AppTheme.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(AppTheme.radiusXl)), ), builder: (_) => const _IncidentReportSheet(), ); } } // ── Pantalla de Paradas ─────────────────────────────────────────────────────── class DriverStopsScreen extends StatefulWidget { const DriverStopsScreen({super.key}); @override State createState() => _DriverStopsScreenState(); } class _DriverStopsScreenState extends State { late List _paradas; @override void initState() { super.initState(); _paradas = [ StopModel( id: 's-01', direccion: 'Av. Insurgentes 245', colonia: 'Col. Centro', referencias: 'Casa esquina, portón azul', orden: 1, estado: EstadoParada.completada), StopModel( id: 's-02', direccion: 'Calle Morelos 18', colonia: 'Col. Centro', referencias: 'Frente a la farmacia', orden: 2, estado: EstadoParada.completada), StopModel( id: 's-03', direccion: 'Privada Las Flores 7', colonia: 'Col. Las Palmas', referencias: 'Entrada sin número', orden: 3, estado: EstadoParada.enCamino), StopModel( id: 's-04', direccion: 'Blvd. Torres Landa 310', colonia: 'Col. Las Palmas', referencias: 'Edificio verde', orden: 4, estado: EstadoParada.pendiente), StopModel( id: 's-05', direccion: 'Calle Hidalgo 89', colonia: 'Col. Primavera', referencias: 'Casa con árbol en la entrada', orden: 5, estado: EstadoParada.pendiente), StopModel( id: 's-06', direccion: 'Av. Revolución 440', colonia: 'Col. Primavera', referencias: 'Condominio Piso 1', orden: 6, estado: EstadoParada.pendiente), StopModel( id: 's-07', direccion: 'Calle Juárez 112', colonia: 'Col. Los Pinos', referencias: 'Casa color salmón', orden: 7, estado: EstadoParada.saltada), ]; } void _marcarCompletada(StopModel parada) { setState(() => parada.estado = EstadoParada.completada); } void _marcarSaltada(StopModel parada) { setState(() => parada.estado = EstadoParada.saltada); } int get _completadas => _paradas.where((p) => p.estado == EstadoParada.completada).length; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.background, appBar: AppBar(title: const Text('Paradas de hoy')), body: Column( children: [ // Barra de progreso _ProgressHeader( completadas: _completadas, total: _paradas.length), // Lista de paradas Expanded( child: ListView.builder( padding: const EdgeInsets.fromLTRB(16, 12, 16, 32), itemCount: _paradas.length, itemBuilder: (_, i) => _StopCard( parada: _paradas[i], onCompletada: () => _marcarCompletada(_paradas[i]), onSaltada: () => _marcarSaltada(_paradas[i]), ), ), ), ], ), ); } } // ── Pantalla de Historial ───────────────────────────────────────────────────── class DriverHistoryScreen extends StatelessWidget { const DriverHistoryScreen({super.key}); static const List> _historial = [ { 'fecha': 'Hoy', 'ruta': 'Ruta Norte', 'duracion': '2h 43min', 'paradas': '14 / 22', 'alertas': 61, 'completada': false, }, { 'fecha': 'Jue 22 may', 'ruta': 'Ruta Norte', 'duracion': '3h 12min', 'paradas': '22 / 22', 'alertas': 89, 'completada': true, }, { 'fecha': 'Mié 21 may', 'ruta': 'Ruta Norte', 'duracion': '2h 55min', 'paradas': '22 / 22', 'alertas': 74, 'completada': true, }, { 'fecha': 'Mar 20 may', 'ruta': 'Ruta Norte', 'duracion': '3h 05min', 'paradas': '21 / 22', 'alertas': 68, 'completada': true, }, { 'fecha': 'Lun 19 may', 'ruta': 'Ruta Norte', 'duracion': '2h 48min', 'paradas': '22 / 22', 'alertas': 85, 'completada': true, }, ]; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.background, appBar: AppBar(title: const Text('Historial')), body: ListView( padding: const EdgeInsets.all(16), children: [ // Resumen semanal _WeeklySummaryBanner(), const SizedBox(height: 20), w.SectionTitle(title: 'Recorridos recientes'), ..._historial.map((h) => _HistoryCard(data: h)), const SizedBox(height: 32), ], ), ); } } // ── Pantalla de Perfil del Chofer ───────────────────────────────────────────── class DriverProfileScreen extends StatelessWidget { const DriverProfileScreen({super.key}); final DriverInfo _chofer = const DriverInfo( nombre: 'Miguel', apellido: 'Hernández', telefono: '+52 461 100 0001', ruta: 'Ruta Norte', vehiculo: 'Camión #03 · MXX-483', turno: '7:00 – 10:00 a.m.', antiguedad: '3 años', ); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.background, appBar: AppBar(title: const Text('Mi perfil')), body: ListView( padding: const EdgeInsets.all(16), children: [ // Header _DriverProfileHeader(chofer: _chofer), const SizedBox(height: 20), // Mi turno w.SectionTitle(title: 'Mi turno'), w.InfoRow( icon: Icons.route_outlined, label: 'Ruta asignada', value: _chofer.ruta), const SizedBox(height: 8), w.InfoRow( icon: Icons.directions_bus_outlined, label: 'Vehículo', value: _chofer.vehiculo), const SizedBox(height: 8), w.InfoRow( icon: Icons.schedule_outlined, label: 'Horario', value: _chofer.turno), const SizedBox(height: 8), w.InfoRow( icon: Icons.work_outline_rounded, label: 'Antigüedad', value: _chofer.antiguedad), const SizedBox(height: 20), // Cuenta w.SectionTitle(title: 'Mi cuenta'), w.MenuTile( icon: Icons.person_outline, title: 'Editar datos personales', onTap: () {}, ), w.MenuTile( icon: Icons.lock_outline, title: 'Cambiar contraseña', onTap: () {}, ), w.MenuTile( icon: Icons.phone_outlined, title: 'Teléfono de emergencia', subtitle: 'Agregar contacto', onTap: () {}, ), const SizedBox(height: 16), // Soporte w.SectionTitle(title: 'Soporte'), w.MenuTile( icon: Icons.help_outline, title: 'Manual del operador', onTap: () {}, ), w.MenuTile( icon: Icons.bug_report_outlined, title: 'Reportar problema técnico', onTap: () {}, ), const SizedBox(height: 16), // Cerrar sesión w.MenuTile( icon: Icons.logout_rounded, title: 'Cerrar sesión', iconColor: AppTheme.danger, titleColor: AppTheme.danger, trailing: const SizedBox.shrink(), onTap: () {}, ), const SizedBox(height: 32), Center( child: Text( 'RutaVerde v1.0.0 · Chofer\nServicio de Limpia · Celaya, Gto.', textAlign: TextAlign.center, style: const TextStyle( fontSize: 12, color: AppTheme.textHint, height: 1.6), ), ), const SizedBox(height: 24), ], ), ); } } // ── Modelo simple para perfil del chofer ────────────────────────────────────── class DriverInfo { final String nombre; final String apellido; final String telefono; final String ruta; final String vehiculo; final String turno; final String antiguedad; const DriverInfo({ required this.nombre, required this.apellido, required this.telefono, required this.ruta, required this.vehiculo, required this.turno, required this.antiguedad, }); String get nombreCompleto => '$nombre $apellido'; String get iniciales => '${nombre.isNotEmpty ? nombre[0] : ''}${apellido.isNotEmpty ? apellido[0] : ''}' .toUpperCase(); } // ───────────────────────────────────────────────────────────────────────────── // WIDGETS INTERNOS // ───────────────────────────────────────────────────────────────────────────── // ── Widgets de Mi ruta ──────────────────────────────────────────────────────── class _DriverInfoBanner extends StatelessWidget { final String ruta; const _DriverInfoBanner({required this.ruta}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [AppTheme.primary, AppTheme.primaryDark], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(AppTheme.radiusLg), ), child: Row( children: [ Container( width: 52, height: 52, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), shape: BoxShape.circle, ), child: const Center( child: Text( 'MH', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w800, color: Colors.white), ), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Miguel Hernández', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Colors.white), ), const SizedBox(height: 3), Row( children: [ const Icon(Icons.route_outlined, size: 13, color: Colors.white70), const SizedBox(width: 4), Text( ruta, style: const TextStyle( fontSize: 12, color: Colors.white70), ), ], ), const SizedBox(height: 3), Row( children: [ const Icon(Icons.directions_bus_outlined, size: 13, color: Colors.white70), const SizedBox(width: 4), const Text( 'Camión #03 · MXX-483', style: TextStyle(fontSize: 12, color: Colors.white70), ), ], ), ], ), ), ], ), ); } } class _TurnoCronometro extends StatelessWidget { final bool turnoActivo; final bool enPausa; final String tiempo; const _TurnoCronometro({ required this.turnoActivo, required this.enPausa, required this.tiempo, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.radiusLg), border: Border.all(color: AppTheme.border, width: 0.5), boxShadow: AppTheme.softShadow, ), child: Column( children: [ Text( turnoActivo ? (enPausa ? 'Turno pausado' : 'Turno activo') : 'Sin turno activo', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: turnoActivo ? (enPausa ? AppTheme.amber : AppTheme.primary) : AppTheme.textSecondary), ), const SizedBox(height: 10), Text( tiempo, style: TextStyle( fontSize: 40, fontWeight: FontWeight.w800, letterSpacing: 2, color: turnoActivo ? (enPausa ? AppTheme.amber : AppTheme.textPrimary) : AppTheme.textHint), ), if (turnoActivo) ...[ const SizedBox(height: 8), Text( enPausa ? 'El GPS sigue activo durante la pausa' : 'GPS activo · Enviando ubicación', style: const TextStyle( fontSize: 11, color: AppTheme.textSecondary), ), ], ], ), ); } } class _SmallStatCard extends StatelessWidget { final IconData icon; final String label; final String value; final Color color; final Color bgColor; const _SmallStatCard({ required this.icon, required this.label, required this.value, required this.color, required this.bgColor, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.radiusLg), border: Border.all(color: AppTheme.border, width: 0.5), boxShadow: AppTheme.softShadow, ), child: Row( children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: color, size: 20), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(value, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w800, color: AppTheme.textPrimary)), Text(label, style: const TextStyle( fontSize: 10, color: AppTheme.textSecondary, height: 1.3)), ], ), ), ], ), ); } } class _ProximaParadaCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.radiusLg), border: Border.all(color: AppTheme.primary, width: 1.5), boxShadow: AppTheme.cardShadow, ), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: AppTheme.primaryLight, borderRadius: BorderRadius.circular(10), ), child: const Icon(Icons.location_on_rounded, color: AppTheme.primary, size: 24), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Privada Las Flores 7', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: AppTheme.textPrimary), ), const SizedBox(height: 2), const Text( 'Col. Las Palmas · Parada #3', style: TextStyle( fontSize: 12, color: AppTheme.textSecondary), ), const SizedBox(height: 5), w.StatusBadge.green('~3 min'), ], ), ), IconButton( onPressed: () {}, icon: const Icon(Icons.open_in_new_rounded, color: AppTheme.primary, size: 20), ), ], ), ); } } // ── Widgets de Paradas ──────────────────────────────────────────────────────── class _ProgressHeader extends StatelessWidget { final int completadas; final int total; const _ProgressHeader({required this.completadas, required this.total}); @override Widget build(BuildContext context) { final pct = total > 0 ? completadas / total : 0.0; return Container( color: AppTheme.primary, padding: const EdgeInsets.fromLTRB(16, 12, 16, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '$completadas de $total paradas', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white), ), Text( '${(pct * 100).toStringAsFixed(0)}%', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white), ), ], ), const SizedBox(height: 8), ClipRRect( borderRadius: BorderRadius.circular(AppTheme.radiusFull), child: LinearProgressIndicator( value: pct, minHeight: 8, backgroundColor: Colors.white24, valueColor: const AlwaysStoppedAnimation(Colors.white), ), ), ], ), ); } } class _StopCard extends StatelessWidget { final StopModel parada; final VoidCallback onCompletada; final VoidCallback onSaltada; const _StopCard({ required this.parada, required this.onCompletada, required this.onSaltada, }); Color get _borderColor { switch (parada.estado) { case EstadoParada.completada: return AppTheme.primaryMid; case EstadoParada.enCamino: return AppTheme.primary; case EstadoParada.saltada: return AppTheme.danger; case EstadoParada.pendiente: return AppTheme.border; } } Color get _iconBg { switch (parada.estado) { case EstadoParada.completada: return AppTheme.primaryLight; case EstadoParada.enCamino: return AppTheme.primaryLight; case EstadoParada.saltada: return AppTheme.dangerLight; case EstadoParada.pendiente: return AppTheme.background; } } Color get _iconColor { switch (parada.estado) { case EstadoParada.completada: return AppTheme.primary; case EstadoParada.enCamino: return AppTheme.primary; case EstadoParada.saltada: return AppTheme.danger; case EstadoParada.pendiente: return AppTheme.textSecondary; } } IconData get _icon { switch (parada.estado) { case EstadoParada.completada: return Icons.check_circle_rounded; case EstadoParada.enCamino: return Icons.directions_bus_rounded; case EstadoParada.saltada: return Icons.cancel_outlined; case EstadoParada.pendiente: return Icons.location_on_outlined; } } String get _etiqueta { switch (parada.estado) { case EstadoParada.completada: return 'Completada'; case EstadoParada.enCamino: return 'En camino'; case EstadoParada.saltada: return 'Saltada'; case EstadoParada.pendiente: return 'Pendiente'; } } bool get _esPendienteOEnCamino => parada.estado == EstadoParada.pendiente || parada.estado == EstadoParada.enCamino; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.radiusLg), border: Border.all(color: _borderColor, width: 0.8), boxShadow: AppTheme.softShadow, ), child: Column( children: [ Padding( padding: const EdgeInsets.all(14), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Número de orden Container( width: 28, height: 28, decoration: BoxDecoration( color: _iconBg, shape: BoxShape.circle, ), child: Center( child: Text( '${parada.orden}', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w800, color: _iconColor), ), ), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(parada.direccion, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppTheme.textPrimary)), const SizedBox(height: 2), Text(parada.colonia, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary)), if (parada.referencias.isNotEmpty) ...[ const SizedBox(height: 4), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon(Icons.info_outline_rounded, size: 12, color: AppTheme.textHint), const SizedBox(width: 4), Expanded( child: Text(parada.referencias, style: const TextStyle( fontSize: 11, color: AppTheme.textHint)), ), ], ), ], ], ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _iconBg, borderRadius: BorderRadius.circular(AppTheme.radiusFull), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(_icon, size: 11, color: _iconColor), const SizedBox(width: 4), Text(_etiqueta, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: _iconColor)), ], ), ), ], ), ), // Acciones (solo si pendiente o en camino) if (_esPendienteOEnCamino) ...[ Divider(color: AppTheme.borderLight, height: 1), Padding( padding: const EdgeInsets.fromLTRB(10, 8, 10, 10), child: Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: onCompletada, style: OutlinedButton.styleFrom( foregroundColor: AppTheme.primary, side: const BorderSide(color: AppTheme.primary), padding: const EdgeInsets.symmetric(vertical: 8), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppTheme.radiusSm), ), ), icon: const Icon(Icons.check_rounded, size: 15), label: const Text('Completar', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600)), ), ), const SizedBox(width: 8), Expanded( child: OutlinedButton.icon( onPressed: onSaltada, style: OutlinedButton.styleFrom( foregroundColor: AppTheme.textSecondary, side: const BorderSide(color: AppTheme.border), padding: const EdgeInsets.symmetric(vertical: 8), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppTheme.radiusSm), ), ), icon: const Icon(Icons.skip_next_rounded, size: 15), label: const Text('Saltar', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600)), ), ), ], ), ), ], ], ), ); } } // ── Widgets de Historial ────────────────────────────────────────────────────── class _WeeklySummaryBanner extends StatelessWidget { @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [AppTheme.primaryDark, AppTheme.primary], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(AppTheme.radiusLg), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Resumen semanal', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _SumItem(label: 'Turno activo', value: '4/5 días'), _VertDiv(), _SumItem(label: 'Alertas', value: '377'), _VertDiv(), _SumItem(label: 'Paradas', value: '101 / 110'), ], ), ], ), ); } } class _SumItem extends StatelessWidget { final String label; final String value; const _SumItem({required this.label, required this.value}); @override Widget build(BuildContext context) { return Column( children: [ Text(value, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w800, color: Colors.white)), const SizedBox(height: 2), Text(label, style: const TextStyle(fontSize: 11, color: Colors.white70)), ], ); } } class _VertDiv extends StatelessWidget { @override Widget build(BuildContext context) { return Container(width: 1, height: 32, color: Colors.white24); } } class _HistoryCard extends StatelessWidget { final Map data; const _HistoryCard({required this.data}); @override Widget build(BuildContext context) { final completada = data['completada'] as bool; return Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.radiusLg), border: Border.all(color: AppTheme.border, width: 0.5), boxShadow: AppTheme.softShadow, ), child: Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( color: completada ? AppTheme.primaryLight : AppTheme.amberLight, borderRadius: BorderRadius.circular(10), ), child: Icon( completada ? Icons.check_circle_outline_rounded : Icons.timelapse_rounded, color: completada ? AppTheme.primary : AppTheme.amber, size: 22, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(data['fecha'] as String, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppTheme.textPrimary)), const SizedBox(height: 2), Text(data['ruta'] as String, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary)), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text(data['duracion'] as String, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppTheme.textPrimary)), const SizedBox(height: 3), Text( '${data['paradas']} · ${data['alertas']} alertas', style: const TextStyle( fontSize: 11, color: AppTheme.textSecondary), ), ], ), ], ), ); } } // ── Widgets de Perfil del Chofer ────────────────────────────────────────────── class _DriverProfileHeader extends StatelessWidget { final DriverInfo chofer; const _DriverProfileHeader({required this.chofer}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [AppTheme.primary, AppTheme.primaryDark], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(AppTheme.radiusLg), ), child: Row( children: [ Container( width: 60, height: 60, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), shape: BoxShape.circle, border: Border.all(color: Colors.white38, width: 2), ), child: Center( child: Text( chofer.iniciales, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w800, color: Colors.white), ), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(chofer.nombreCompleto, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, color: Colors.white)), const SizedBox(height: 3), Text(chofer.telefono, style: const TextStyle( fontSize: 12, color: Colors.white70)), const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(AppTheme.radiusFull), ), child: const Text( 'Operador certificado', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Colors.white), ), ), ], ), ), IconButton( icon: const Icon(Icons.edit_outlined, color: Colors.white70, size: 20), onPressed: () {}, ), ], ), ); } } // ── Bottom Sheet: Reporte de incidencia ─────────────────────────────────────── class _IncidentReportSheet extends StatefulWidget { const _IncidentReportSheet(); @override State<_IncidentReportSheet> createState() => _IncidentReportSheetState(); } class _IncidentReportSheetState extends State<_IncidentReportSheet> { int _tipoSeleccionado = 0; final List> _tipos = [ {'icon': Icons.traffic_rounded, 'label': 'Tráfico'}, {'icon': Icons.build_outlined, 'label': 'Avería'}, {'icon': Icons.alt_route_rounded, 'label': 'Desvío'}, {'icon': Icons.warning_amber_rounded, 'label': 'Otro'}, ]; @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only( top: 16, left: 20, right: 20, bottom: MediaQuery.of(context).viewInsets.bottom + 24, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: Container( width: 36, height: 4, decoration: BoxDecoration( color: AppTheme.border, borderRadius: BorderRadius.circular(AppTheme.radiusFull), ), ), ), const SizedBox(height: 16), const Text('Reportar incidencia', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: AppTheme.textPrimary)), const SizedBox(height: 6), const Text('El control será notificado de inmediato.', style: TextStyle( fontSize: 13, color: AppTheme.textSecondary)), const SizedBox(height: 20), // Tipo de incidencia w.SectionTitle(title: 'Tipo'), Row( children: List.generate(_tipos.length, (i) { final sel = i == _tipoSeleccionado; return Expanded( child: GestureDetector( onTap: () => setState(() => _tipoSeleccionado = i), child: AnimatedContainer( duration: const Duration(milliseconds: 160), margin: EdgeInsets.only(right: i < 3 ? 8 : 0), padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: sel ? AppTheme.primaryLight : AppTheme.surface, borderRadius: BorderRadius.circular(AppTheme.radiusMd), border: Border.all( color: sel ? AppTheme.primary : AppTheme.border, width: sel ? 1.5 : 0.5, ), ), child: Column( children: [ Icon( _tipos[i]['icon'] as IconData, color: sel ? AppTheme.primary : AppTheme.textSecondary, size: 22, ), const SizedBox(height: 5), Text( _tipos[i]['label'] as String, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: sel ? AppTheme.primary : AppTheme.textSecondary), ), ], ), ), ), ); }), ), const SizedBox(height: 16), w.FormField( label: 'Descripción (opcional)', hint: 'Cuéntanos qué está pasando…', maxLines: 3, ), const SizedBox(height: 20), SizedBox( width: double.infinity, height: 50, child: ElevatedButton.icon( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.send_rounded), label: const Text('Enviar reporte'), ), ), ], ), ); } }