import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart' show LatLng; import '../../models/app_settings.dart'; import '../../models/live_position.dart'; import '../../models/profile.dart'; import '../../providers/profile_provider.dart'; import '../../providers/whereabouts_provider.dart'; import '../../providers/workforce_provider.dart'; import '../../widgets/responsive_body.dart'; import '../../utils/app_time.dart'; class WhereaboutsScreen extends ConsumerStatefulWidget { const WhereaboutsScreen({super.key}); @override ConsumerState createState() => _WhereaboutsScreenState(); } class _WhereaboutsScreenState extends ConsumerState { final _mapController = MapController(); @override Widget build(BuildContext context) { final positionsAsync = ref.watch(livePositionsProvider); final profilesAsync = ref.watch(profilesProvider); final geofenceAsync = ref.watch(geofenceProvider); final Map profileById = { for (final p in profilesAsync.valueOrNull ?? []) p.id: p, }; return ResponsiveBody( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Text( 'Whereabouts', style: Theme.of( context, ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700), ), ), Expanded( child: positionsAsync.when( data: (positions) => _buildMap( context, positions, profileById, geofenceAsync.valueOrNull, ), loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center(child: Text('Failed to load positions: $e')), ), ), // Staff list below the map positionsAsync.when( data: (positions) => _buildStaffList(context, positions, profileById), loading: () => const SizedBox.shrink(), error: (_, _) => const SizedBox.shrink(), ), ], ), ); } Widget _buildMap( BuildContext context, List positions, Map profileById, GeofenceConfig? geofenceConfig, ) { final markers = positions.map((pos) { final name = profileById[pos.userId]?.fullName ?? 'Unknown'; final inPremise = pos.inPremise; return Marker( point: LatLng(pos.lat, pos.lng), width: 80, height: 60, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.15), blurRadius: 4, ), ], ), child: Text( name.split(' ').first, style: Theme.of( context, ).textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ), Icon( Icons.location_pin, size: 28, color: inPremise ? Colors.green : Colors.orange, ), ], ), ); }).toList(); // Build geofence polygon overlay if available final polygonLayers = []; if (geofenceConfig != null && geofenceConfig.hasPolygon) { final List points = geofenceConfig.polygon! .map((p) => LatLng(p.lat, p.lng)) .toList(); if (points.isNotEmpty) { polygonLayers.add( PolygonLayer( polygons: [ Polygon( points: points, color: Colors.blue.withValues(alpha: 0.1), borderColor: Colors.blue, borderStrokeWidth: 2.0, ), ], ), ); } } // Default center: CRMC Cotabato City area const defaultCenter = LatLng(7.2046, 124.2460); return FlutterMap( mapController: _mapController, options: MapOptions( initialCenter: positions.isNotEmpty ? LatLng(positions.first.lat, positions.first.lng) : defaultCenter, initialZoom: 16.0, ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.tasq.app', ), ...polygonLayers, MarkerLayer(markers: markers), ], ); } Widget _buildStaffList( BuildContext context, List positions, Map profileById, ) { if (positions.isEmpty) return const SizedBox.shrink(); return Container( constraints: const BoxConstraints(maxHeight: 180), child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), itemCount: positions.length, itemBuilder: (context, index) { final pos = positions[index]; final p = profileById[pos.userId]; final name = p?.fullName ?? 'Unknown'; final role = p?.role ?? '-'; final timeAgo = _timeAgo(pos.updatedAt); return ListTile( dense: true, leading: CircleAvatar( radius: 16, backgroundColor: pos.inPremise ? Colors.green.shade100 : Colors.orange.shade100, child: Icon( pos.inPremise ? Icons.check : Icons.location_off, size: 16, color: pos.inPremise ? Colors.green : Colors.orange, ), ), title: Text(name), subtitle: Text('${_roleLabel(role)} ยท $timeAgo'), trailing: Text( pos.inPremise ? 'In premise' : 'Off-site', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: pos.inPremise ? Colors.green : Colors.orange, ), ), onTap: () => _mapController.move(LatLng(pos.lat, pos.lng), 17.0), ); }, ), ); } String _timeAgo(DateTime dt) { final diff = AppTime.now().difference(dt); if (diff.inMinutes < 1) return 'Just now'; if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; return '${diff.inHours}h ago'; } String _roleLabel(String role) { switch (role) { case 'admin': return 'Admin'; case 'dispatcher': return 'Dispatcher'; case 'it_staff': return 'IT Staff'; default: return 'Standard'; } } }