import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:latlong2/latlong.dart'; import '../../../../core/constants/auth_constants.dart'; import '../../../../core/theme/app_theme.dart'; const _kRelleno = LatLng(20.5111, -100.9037); const _kRouteColors = [ Colors.blue, Colors.green, Colors.orange, Colors.red, Colors.purple, Colors.teal, ]; class _RouteData { const _RouteData({ required this.routeId, required this.currentPositionId, required this.status, required this.positions, }); final String routeId; final int currentPositionId; final String status; final List positions; } class AdminDashboardScreen extends StatefulWidget { const AdminDashboardScreen({super.key}); @override State createState() => _AdminDashboardScreenState(); } class _AdminDashboardScreenState extends State { List<_RouteData> _routes = []; bool _isLoading = true; String? _error; @override void initState() { super.initState(); _loadRoutes(); } Future _loadRoutes() async { setState(() { _isLoading = true; _error = null; }); try { const storage = FlutterSecureStorage(); final token = await storage.read(key: authTokenStorageKey) ?? ''; final dio = Dio( BaseOptions( baseUrl: const String.fromEnvironment( 'API_BASE_URL', defaultValue: 'http://10.0.2.2:8000', ), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, ), ); final routesRes = await dio.get>('/routes'); final routeList = routesRes.data ?? []; final List<_RouteData> loaded = []; for (final r in routeList) { final routeId = (r['route_id'] ?? r['id'] ?? '').toString(); if (routeId.isEmpty) continue; List positions = []; try { final posRes = await dio.get>('/routes/$routeId/positions'); positions = (posRes.data ?? []) .map((p) => LatLng( (p['lat'] as num).toDouble(), (p['lng'] as num).toDouble(), )) .toList(); } catch (_) {} loaded.add(_RouteData( routeId: routeId, currentPositionId: (r['current_position_id'] as int?) ?? 1, status: (r['status'] ?? 'pendiente').toString(), positions: positions, )); } if (mounted) { setState(() { _routes = loaded; _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _isLoading = false; }); } } } Marker _buildTruckMarker(_RouteData route, Color color) { final posIdx = (route.currentPositionId - 1).clamp(0, route.positions.length - 1); return Marker( point: route.positions[posIdx], width: 48, height: 48, child: Tooltip( message: '${route.routeId} · pos ${route.currentPositionId} · ${route.status}', child: Container( decoration: BoxDecoration( color: color, shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), ), child: const Icon(Icons.local_shipping, color: Colors.white, size: 22), ), ), ); } @override Widget build(BuildContext context) { final routesWithPos = _routes.where((r) => r.positions.isNotEmpty).toList(); return Scaffold( appBar: AppBar( title: const Text('Panel de Control - Flotilla'), backgroundColor: AppTheme.primaryDark, foregroundColor: Colors.white, elevation: 0, actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _loadRoutes, tooltip: 'Actualizar', ), ], ), body: Column( children: [ Container( width: double.infinity, color: AppTheme.amberLight, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: const Row( children: [ Icon(Icons.security, color: AppTheme.amber, size: 20), SizedBox(width: 10), Expanded( child: Text( 'Privilegio Admin: Vista global de coordenadas. No compartir pantalla.', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: AppTheme.amber, ), ), ), ], ), ), Expanded( child: Stack( children: [ FlutterMap( options: const MapOptions( initialCenter: _kRelleno, initialZoom: 12.5, interactionOptions: InteractionOptions( flags: InteractiveFlag.all, ), ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.onlineshack.recolecta', ), if (routesWithPos.isNotEmpty) ...[ PolylineLayer( polylines: [ for (int i = 0; i < routesWithPos.length; i++) if (routesWithPos[i].positions.length > 1) Polyline( points: routesWithPos[i].positions, color: _kRouteColors[i % _kRouteColors.length] .withValues(alpha: 0.7), strokeWidth: 3.5, ), ], ), MarkerLayer( markers: [ for (int i = 0; i < routesWithPos.length; i++) _buildTruckMarker( routesWithPos[i], _kRouteColors[i % _kRouteColors.length], ), ], ), ], ], ), if (_isLoading) const Center(child: CircularProgressIndicator()), if (_error != null && !_isLoading) Positioned( bottom: 16, left: 16, right: 16, child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppTheme.danger.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(8), ), child: Text( 'Sin conexión al backend: $_error', style: const TextStyle( color: Colors.white, fontSize: 12, ), ), ), ), if (routesWithPos.isNotEmpty) Positioned( top: 12, right: 12, child: _RouteLegend(routes: routesWithPos), ), ], ), ), ], ), ); } } class _RouteLegend extends StatelessWidget { const _RouteLegend({required this.routes}); final List<_RouteData> routes; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.93), borderRadius: BorderRadius.circular(8), boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)], ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ for (int i = 0; i < routes.length; i++) Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 10, height: 10, decoration: BoxDecoration( color: _kRouteColors[i % _kRouteColors.length], shape: BoxShape.circle, ), ), const SizedBox(width: 6), Text( '${routes[i].routeId} · pos ${routes[i].currentPositionId}', style: const TextStyle( fontSize: 10, color: AppTheme.textPrimary, ), ), ], ), ), ], ), ); } }