diff --git a/lib/features/agents/data/models/agent_profile.dart b/lib/features/agents/data/models/agent_profile.dart index 918fc78..ed72bbc 100644 --- a/lib/features/agents/data/models/agent_profile.dart +++ b/lib/features/agents/data/models/agent_profile.dart @@ -96,10 +96,12 @@ class AgentProfile { } String get location { - // Direct fields first (state first, then city) - if (city != null && state != null) return '$state, $city'; - if (state != null) return state!; - if (city != null) return city!; + // Direct fields first (state first, then city) — normalized + if (city != null && state != null) { + return '${_normalizeState(state!)}, ${_stripStatePrefix(city!)}'; + } + if (state != null) return _normalizeState(state!); + if (city != null) return _stripStatePrefix(city!); // Fallback: extract from fieldValues (matches web getLocationString) String? fvCity; @@ -107,9 +109,9 @@ class AgentProfile { for (final fv in fieldValues) { final slug = fv.fieldSlug.toLowerCase(); if (slug == 'city' || slug == 'city_name') { - fvCity = _extractFieldString(fv); + fvCity = _extractFieldString(fv, isCity: true); } else if (slug == 'state' || slug == 'state_name') { - fvState = _extractFieldString(fv); + fvState = _extractFieldString(fv, isCity: false); } } final parts = [fvState, fvCity].where((s) => s != null && s.isNotEmpty); @@ -121,20 +123,50 @@ class AgentProfile { return ''; } + /// Uppercase 2-letter state codes (e.g. "nd" → "ND"); leave full names alone. + static String _normalizeState(String raw) { + final trimmed = raw.trim(); + if (trimmed.length == 2) return trimmed.toUpperCase(); + return trimmed; + } + + /// Strip the 2-letter state prefix from a city value ("nd_hurley" → "Hurley", + /// "Nd Hurley" → "Hurley"). Matches web's `stripCityPrefix` behavior. + static String _stripStatePrefix(String raw) { + var s = raw.trim(); + // Snake-case prefix: nd_hurley + s = s.replaceFirst(RegExp(r'^[a-z]{2}_', caseSensitive: false), ''); + // Title-cased prefix token: "Nd Hurley" + s = s.replaceFirst(RegExp(r'^[A-Za-z]{2}\s+'), ''); + return s; + } + /// Extract a display string from a field value (handles jsonValue arrays). - static String? _extractFieldString(AgentFieldValue fv) { + /// For city fields, strip the state-code prefix before title-casing. + static String? _extractFieldString(AgentFieldValue fv, {required bool isCity}) { + String format(String raw) { + var s = raw; + if (isCity) { + // Strip slug state prefix: "nd_hurley" → "hurley" + s = s.replaceFirst(RegExp(r'^[a-z]{2}_', caseSensitive: false), ''); + } + final titled = s + .split('_') + .map((w) => w.isNotEmpty + ? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}' + : '') + .join(' '); + if (!isCity && titled.length == 2) return titled.toUpperCase(); + return titled; + } + if (fv.jsonValue is List && (fv.jsonValue as List).isNotEmpty) { final val = (fv.jsonValue as List).first; - if (val is String && val.isNotEmpty) { - return val - .split('_') - .map((w) => w.isNotEmpty - ? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}' - : '') - .join(' '); - } + if (val is String && val.isNotEmpty) return format(val); + } + if (fv.textValue != null && fv.textValue!.isNotEmpty) { + return format(fv.textValue!); } - if (fv.textValue != null && fv.textValue!.isNotEmpty) return fv.textValue; return null; } diff --git a/lib/features/agents/presentation/screens/agent_search_screen.dart b/lib/features/agents/presentation/screens/agent_search_screen.dart index a78839b..014d905 100644 --- a/lib/features/agents/presentation/screens/agent_search_screen.dart +++ b/lib/features/agents/presentation/screens/agent_search_screen.dart @@ -594,23 +594,41 @@ class _AgentCardState extends State<_AgentCard> { const SizedBox(width: 4), Builder(builder: (_) { final allParts = []; - // Extract states first, then cities (state-first display) - List extractValues(dynamic fv) { + // Format raw slug/label. For cities, strip 2-letter + // state prefix ("nd_hurley" → "Hurley"). For states, + // uppercase 2-letter codes ("nd" → "ND"). Matches + // web's stripCityPrefix + extractAllValues. + String formatPart(String raw, {required bool isCity}) { + var s = raw.trim(); + if (isCity) { + s = s.replaceFirst( + RegExp(r'^[a-z]{2}_', caseSensitive: false), ''); + } + final titled = s + .split('_') + .map((w) => w.isNotEmpty + ? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}' + : '') + .join(' '); + if (!isCity && titled.length == 2) { + return titled.toUpperCase(); + } + return titled; + } + + List extractValues(dynamic fv, + {required bool isCity}) { final out = []; if (fv.jsonValue is List) { for (final v in (fv.jsonValue as List)) { final s = v.toString().trim(); if (s.isEmpty) continue; - out.add(s - .split('_') - .map((w) => w.isNotEmpty - ? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}' - : '') - .join(' ')); + out.add(formatPart(s, isCity: isCity)); } } else if (fv.textValue != null && (fv.textValue as String).isNotEmpty) { - out.add(fv.textValue as String); + out.add(formatPart(fv.textValue as String, + isCity: isCity)); } return out; } @@ -626,7 +644,7 @@ class _AgentCardState extends State<_AgentCard> { for (final fv in agent.fieldValues) { final slug = fv.fieldSlug.toLowerCase(); if (slug == 'state' || slug == 'state_name') { - for (final v in extractValues(fv)) { + for (final v in extractValues(fv, isCity: false)) { addUnique(v); } } @@ -635,7 +653,7 @@ class _AgentCardState extends State<_AgentCard> { for (final fv in agent.fieldValues) { final slug = fv.fieldSlug.toLowerCase(); if (slug == 'city' || slug == 'city_name') { - for (final v in extractValues(fv)) { + for (final v in extractValues(fv, isCity: true)) { addUnique(v); } } diff --git a/lib/features/agents/presentation/widgets/agent_filter_sheet.dart b/lib/features/agents/presentation/widgets/agent_filter_sheet.dart index 91594b8..4bea447 100644 --- a/lib/features/agents/presentation/widgets/agent_filter_sheet.dart +++ b/lib/features/agents/presentation/widgets/agent_filter_sheet.dart @@ -160,13 +160,13 @@ class _AgentFilterSheetState extends State { top: false, child: Row( children: [ - // Clear All + // Clear All — reset selections but keep the sheet open + // so the user sees the cleared state and can continue. Expanded( child: GestureDetector( onTap: () { _clearAll(); widget.onClearAll(); - Navigator.of(context).pop(); }, child: Container( height: 29,