- Service.fromJson: lat/lng con Decimal de Prisma llegaban como string y
el cast 'as num?' lanzaba TypeError silencioso → lista de servicios vacía.
Mismo patrón que average_score, fix: double.tryParse(?.toString())
- service_status: agregar enumToStringService que mapea el enum de Flutter
al string que espera el DTO del backend ('pending','accepted', etc.).
changeServiceStatus enviaba el índice entero → @IsEnum rechazaba → el
profesional no podía aceptar ni rechazar solicitudes
- maps_service: agregar MapsService.reverseGeocode que usa el Geocoder del
Maps JS SDK ya cargado, sin requerir la Geocoding API habilitada por separado
- dashboard_view: _reverseGeocode usa el JS Geocoder en vez del endpoint HTTP
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
121 lines
4.1 KiB
Dart
121 lines
4.1 KiB
Dart
import 'dart:async';
|
|
// ignore: avoid_web_libraries_in_flutter
|
|
import 'dart:js' as js;
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:prosapp_web_app/services/api_service.dart';
|
|
|
|
// Result of a reverse geocode via the Maps JS SDK Geocoder
|
|
class GeocodeResult {
|
|
final String formattedAddress;
|
|
final List<Map<String, dynamic>> addressComponents;
|
|
const GeocodeResult({required this.formattedAddress, required this.addressComponents});
|
|
}
|
|
|
|
class MapsService {
|
|
static bool _loaded = false;
|
|
static Completer<bool>? _completer;
|
|
|
|
static Future<bool> load() async {
|
|
if (_loaded) return true;
|
|
if (_completer != null) return _completer!.future;
|
|
|
|
_completer = Completer<bool>();
|
|
|
|
try {
|
|
final data = await ApiService.instance.get('/settings/maps-key');
|
|
final apiKey = data['api_key'] as String? ?? '';
|
|
|
|
if (apiKey.isEmpty) {
|
|
debugPrint('MapsService: API key no configurada');
|
|
_completer!.complete(false);
|
|
return false;
|
|
}
|
|
|
|
if (kIsWeb) {
|
|
_injectScript(apiKey);
|
|
}
|
|
|
|
_loaded = true;
|
|
_completer!.complete(true);
|
|
return true;
|
|
} catch (e) {
|
|
debugPrint('MapsService: error cargando key — $e');
|
|
_completer!.complete(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Uses the already-loaded Maps JS SDK Geocoder — no separate Geocoding API enablement needed.
|
|
static Future<GeocodeResult?> reverseGeocode(double lat, double lng) async {
|
|
final completer = Completer<GeocodeResult?>();
|
|
final id = '_gc${DateTime.now().millisecondsSinceEpoch}';
|
|
|
|
try {
|
|
js.context['${id}_ok'] = js.allowInterop((String address, String compsJson) {
|
|
try { js.context.deleteProperty('${id}_ok'); } catch (_) {}
|
|
try {
|
|
final rawList = js.context.callMethod('eval', ['JSON.parse(\'${compsJson.replaceAll("'", "\\'")}\')']);
|
|
final comps = <Map<String, dynamic>>[];
|
|
// rawList is a JS array; iterate by index
|
|
final len = (rawList['length'] as num?)?.toInt() ?? 0;
|
|
for (var i = 0; i < len; i++) {
|
|
final item = rawList[i] as js.JsObject;
|
|
final types = <String>[];
|
|
final tLen = (item['types']['length'] as num?)?.toInt() ?? 0;
|
|
for (var t = 0; t < tLen; t++) types.add(item['types'][t].toString());
|
|
comps.add({'long_name': item['long_name'].toString(), 'types': types});
|
|
}
|
|
completer.complete(GeocodeResult(formattedAddress: address, addressComponents: comps));
|
|
} catch (_) {
|
|
completer.complete(GeocodeResult(formattedAddress: address, addressComponents: []));
|
|
}
|
|
});
|
|
|
|
js.context['${id}_err'] = js.allowInterop(() {
|
|
try { js.context.deleteProperty('${id}_err'); } catch (_) {}
|
|
completer.complete(null);
|
|
});
|
|
|
|
js.context.callMethod('eval', ['''
|
|
(function(){
|
|
try {
|
|
var g = new google.maps.Geocoder();
|
|
g.geocode({location:{lat:$lat,lng:$lng},language:'es'}, function(r,s){
|
|
if(s==='OK'&&r&&r.length>0){
|
|
var compsStr = JSON.stringify(r[0].address_components.map(function(c){
|
|
return {long_name:c.long_name,types:c.types};
|
|
}));
|
|
window['${id}_ok'](r[0].formatted_address, compsStr);
|
|
} else { window['${id}_err'](); }
|
|
});
|
|
} catch(e){ window['${id}_err'](); }
|
|
})();
|
|
''']);
|
|
} catch (e) {
|
|
completer.complete(null);
|
|
}
|
|
|
|
return completer.future.timeout(const Duration(seconds: 10), onTimeout: () => null);
|
|
}
|
|
|
|
static void _injectScript(String apiKey) {
|
|
// Verifica si ya está cargado
|
|
final existing = js.context.callMethod('eval', [
|
|
'typeof google !== "undefined" && typeof google.maps !== "undefined"'
|
|
]);
|
|
if (existing == true) {
|
|
_loaded = true;
|
|
return;
|
|
}
|
|
|
|
js.context.callMethod('eval', ['''
|
|
(function() {
|
|
var s = document.createElement('script');
|
|
s.src = 'https://maps.googleapis.com/maps/api/js?key=$apiKey';
|
|
s.async = true;
|
|
document.head.appendChild(s);
|
|
})();
|
|
''']);
|
|
}
|
|
}
|