diff --git a/lib/models/suspected_location.dart b/lib/models/suspected_location.dart index 638a802..fd7c05a 100644 --- a/lib/models/suspected_location.dart +++ b/lib/models/suspected_location.dart @@ -4,36 +4,24 @@ import 'package:latlong2/latlong.dart'; /// A suspected surveillance location from the CSV data class SuspectedLocation { final String ticketNo; - final String? urlFull; - final String? addr; - final String? street; - final String? city; - final String? state; - final String? digSiteIntersectingStreet; - final String? digWorkDoneFor; - final String? digSiteRemarks; - final Map? geoJson; final LatLng centroid; final List bounds; + final Map? geoJson; + final Map allFields; // All CSV fields except location and ticket_no SuspectedLocation({ required this.ticketNo, - this.urlFull, - this.addr, - this.street, - this.city, - this.state, - this.digSiteIntersectingStreet, - this.digWorkDoneFor, - this.digSiteRemarks, - this.geoJson, required this.centroid, required this.bounds, + this.geoJson, + required this.allFields, }); /// Create from CSV row data factory SuspectedLocation.fromCsvRow(Map row) { final locationString = row['location'] as String?; + final ticketNo = row['ticket_no']?.toString() ?? ''; + LatLng centroid = const LatLng(0, 0); List bounds = []; Map? geoJson; @@ -47,24 +35,22 @@ class SuspectedLocation { bounds = coordinates.bounds; } catch (e) { // If GeoJSON parsing fails, use default coordinates - print('[SuspectedLocation] Failed to parse GeoJSON for ticket ${row['ticket_no']}: $e'); + print('[SuspectedLocation] Failed to parse GeoJSON for ticket $ticketNo: $e'); print('[SuspectedLocation] Location string: $locationString'); } } + // Store all fields except location and ticket_no + final allFields = Map.from(row); + allFields.remove('location'); + allFields.remove('ticket_no'); + return SuspectedLocation( - ticketNo: row['ticket_no']?.toString() ?? '', - urlFull: row['url_full']?.toString(), - addr: row['addr']?.toString(), - street: row['street']?.toString(), - city: row['city']?.toString(), - state: row['state']?.toString(), - digSiteIntersectingStreet: row['dig_site_intersecting_street']?.toString(), - digWorkDoneFor: row['dig_work_done_for']?.toString(), - digSiteRemarks: row['dig_site_remarks']?.toString(), - geoJson: geoJson, + ticketNo: ticketNo, centroid: centroid, bounds: bounds, + geoJson: geoJson, + allFields: allFields, ); } @@ -149,18 +135,11 @@ class SuspectedLocation { /// Convert to JSON for storage Map toJson() => { 'ticket_no': ticketNo, - 'url_full': urlFull, - 'addr': addr, - 'street': street, - 'city': city, - 'state': state, - 'dig_site_intersecting_street': digSiteIntersectingStreet, - 'dig_work_done_for': digWorkDoneFor, - 'dig_site_remarks': digSiteRemarks, 'geo_json': geoJson, 'centroid_lat': centroid.latitude, 'centroid_lng': centroid.longitude, 'bounds': bounds.map((p) => [p.latitude, p.longitude]).toList(), + 'all_fields': allFields, }; /// Create from stored JSON @@ -173,26 +152,24 @@ class SuspectedLocation { return SuspectedLocation( ticketNo: json['ticket_no'] ?? '', - urlFull: json['url_full'], - addr: json['addr'], - street: json['street'], - city: json['city'], - state: json['state'], - digSiteIntersectingStreet: json['dig_site_intersecting_street'], - digWorkDoneFor: json['dig_work_done_for'], - digSiteRemarks: json['dig_site_remarks'], geoJson: json['geo_json'], centroid: LatLng( (json['centroid_lat'] as num).toDouble(), (json['centroid_lng'] as num).toDouble(), ), bounds: bounds, + allFields: Map.from(json['all_fields'] ?? {}), ); } /// Get a formatted display address String get displayAddress { final parts = []; + final addr = allFields['addr']?.toString(); + final street = allFields['street']?.toString(); + final city = allFields['city']?.toString(); + final state = allFields['state']?.toString(); + if (addr?.isNotEmpty == true) parts.add(addr!); if (street?.isNotEmpty == true) parts.add(street!); if (city?.isNotEmpty == true) parts.add(city!); diff --git a/lib/services/suspected_location_service.dart b/lib/services/suspected_location_service.dart index 2dbda32..233b95a 100644 --- a/lib/services/suspected_location_service.dart +++ b/lib/services/suspected_location_service.dart @@ -138,16 +138,8 @@ class SuspectedLocationService { final dataRows = csvData.skip(1); debugPrint('[SuspectedLocationService] Data rows count: ${dataRows.length}'); - // Find required column indices + // Find required column indices - we only need ticket_no and location final ticketNoIndex = headers.indexOf('ticket_no'); - final urlFullIndex = headers.indexOf('url_full'); - final addrIndex = headers.indexOf('addr'); - final streetIndex = headers.indexOf('street'); - final cityIndex = headers.indexOf('city'); - final stateIndex = headers.indexOf('state'); - final digSiteIntersectingStreetIndex = headers.indexOf('dig_site_intersecting_street'); - final digWorkDoneForIndex = headers.indexOf('dig_work_done_for'); - final digSiteRemarksIndex = headers.indexOf('dig_site_remarks'); final locationIndex = headers.indexOf('location'); debugPrint('[SuspectedLocationService] Column indices - ticket_no: $ticketNoIndex, location: $locationIndex'); @@ -157,7 +149,7 @@ class SuspectedLocationService { return false; } - // Parse rows and store as raw data (don't process GeoJSON yet) + // Parse rows and store all data dynamically final List> rawDataList = []; int rowIndex = 0; int validRows = 0; @@ -166,22 +158,14 @@ class SuspectedLocationService { try { final Map rowData = {}; - if (ticketNoIndex < row.length) rowData['ticket_no'] = row[ticketNoIndex]; - if (urlFullIndex != -1 && urlFullIndex < row.length) rowData['url_full'] = row[urlFullIndex]; - if (addrIndex != -1 && addrIndex < row.length) rowData['addr'] = row[addrIndex]; - if (streetIndex != -1 && streetIndex < row.length) rowData['street'] = row[streetIndex]; - if (cityIndex != -1 && cityIndex < row.length) rowData['city'] = row[cityIndex]; - if (stateIndex != -1 && stateIndex < row.length) rowData['state'] = row[stateIndex]; - if (digSiteIntersectingStreetIndex != -1 && digSiteIntersectingStreetIndex < row.length) { - rowData['dig_site_intersecting_street'] = row[digSiteIntersectingStreetIndex]; + // Store all columns dynamically + for (int i = 0; i < headers.length && i < row.length; i++) { + final headerName = headers[i]; + final cellValue = row[i]; + if (cellValue != null) { + rowData[headerName] = cellValue; + } } - if (digWorkDoneForIndex != -1 && digWorkDoneForIndex < row.length) { - rowData['dig_work_done_for'] = row[digWorkDoneForIndex]; - } - if (digSiteRemarksIndex != -1 && digSiteRemarksIndex < row.length) { - rowData['dig_site_remarks'] = row[digSiteRemarksIndex]; - } - if (locationIndex < row.length) rowData['location'] = row[locationIndex]; // Basic validation - must have ticket_no and location if (rowData['ticket_no']?.toString().isNotEmpty == true && diff --git a/lib/widgets/suspected_location_sheet.dart b/lib/widgets/suspected_location_sheet.dart index 1e5e5b1..8d278cd 100644 --- a/lib/widgets/suspected_location_sheet.dart +++ b/lib/widgets/suspected_location_sheet.dart @@ -19,36 +19,17 @@ class SuspectedLocationSheet extends StatelessWidget { final appState = context.watch(); final locService = LocalizationService.instance; - Future _launchUrl() async { - if (location.urlFull?.isNotEmpty == true) { - final uri = Uri.parse(location.urlFull!); - if (await canLaunchUrl(uri)) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } else { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Could not open URL: ${location.urlFull}'), - ), - ); - } - } + + + // Get all fields except location and ticket_no + final displayData = {}; + for (final entry in location.allFields.entries) { + final value = entry.value?.toString(); + if (value != null && value.isNotEmpty) { + displayData[entry.key] = value; } } - // Create display data map using localized labels - final Map displayData = { - locService.t('suspectedLocation.ticketNo'): location.ticketNo, - locService.t('suspectedLocation.address'): location.addr, - locService.t('suspectedLocation.street'): location.street, - locService.t('suspectedLocation.city'): location.city, - locService.t('suspectedLocation.state'): location.state, - locService.t('suspectedLocation.intersectingStreet'): location.digSiteIntersectingStreet, - locService.t('suspectedLocation.workDoneFor'): location.digWorkDoneFor, - locService.t('suspectedLocation.remarks'): location.digSiteRemarks, - locService.t('suspectedLocation.url'): location.urlFull, - }; - return SafeArea( child: Padding( padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), @@ -64,7 +45,7 @@ class SuspectedLocationSheet extends StatelessWidget { const SizedBox(height: 12), // Display all fields - ...displayData.entries.where((e) => e.value?.isNotEmpty == true).map( + ...displayData.entries.map( (e) => Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( @@ -79,11 +60,24 @@ class SuspectedLocationSheet extends StatelessWidget { ), const SizedBox(width: 8), Expanded( - child: e.key == 'URL' && e.value?.isNotEmpty == true + child: e.key.toLowerCase().contains('url') && e.value.isNotEmpty ? GestureDetector( - onTap: _launchUrl, + onTap: () async { + final uri = Uri.parse(e.value); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Could not open URL: ${e.value}'), + ), + ); + } + } + }, child: Text( - e.value!, + e.value, style: TextStyle( color: Theme.of(context).colorScheme.primary, decoration: TextDecoration.underline, @@ -92,7 +86,7 @@ class SuspectedLocationSheet extends StatelessWidget { ), ) : Text( - e.value ?? '', + e.value, style: TextStyle( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ),