refactor: normalize location display by stripping state prefixes from cities and uppercasing state codes

This commit is contained in:
pradeepkumar
2026-04-20 09:36:55 +05:30
parent c006c141f7
commit 7cacc19c8e
3 changed files with 79 additions and 29 deletions

View File

@@ -96,10 +96,12 @@ class AgentProfile {
} }
String get location { String get location {
// Direct fields first (state first, then city) // Direct fields first (state first, then city) — normalized
if (city != null && state != null) return '$state, $city'; if (city != null && state != null) {
if (state != null) return state!; return '${_normalizeState(state!)}, ${_stripStatePrefix(city!)}';
if (city != null) return city!; }
if (state != null) return _normalizeState(state!);
if (city != null) return _stripStatePrefix(city!);
// Fallback: extract from fieldValues (matches web getLocationString) // Fallback: extract from fieldValues (matches web getLocationString)
String? fvCity; String? fvCity;
@@ -107,9 +109,9 @@ class AgentProfile {
for (final fv in fieldValues) { for (final fv in fieldValues) {
final slug = fv.fieldSlug.toLowerCase(); final slug = fv.fieldSlug.toLowerCase();
if (slug == 'city' || slug == 'city_name') { if (slug == 'city' || slug == 'city_name') {
fvCity = _extractFieldString(fv); fvCity = _extractFieldString(fv, isCity: true);
} else if (slug == 'state' || slug == 'state_name') { } 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); final parts = [fvState, fvCity].where((s) => s != null && s.isNotEmpty);
@@ -121,20 +123,50 @@ class AgentProfile {
return ''; 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). /// 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) { if (fv.jsonValue is List && (fv.jsonValue as List).isNotEmpty) {
final val = (fv.jsonValue as List).first; final val = (fv.jsonValue as List).first;
if (val is String && val.isNotEmpty) { if (val is String && val.isNotEmpty) return format(val);
return val }
.split('_') if (fv.textValue != null && fv.textValue!.isNotEmpty) {
.map((w) => w.isNotEmpty return format(fv.textValue!);
? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}'
: '')
.join(' ');
}
} }
if (fv.textValue != null && fv.textValue!.isNotEmpty) return fv.textValue;
return null; return null;
} }

View File

@@ -594,23 +594,41 @@ class _AgentCardState extends State<_AgentCard> {
const SizedBox(width: 4), const SizedBox(width: 4),
Builder(builder: (_) { Builder(builder: (_) {
final allParts = <String>[]; final allParts = <String>[];
// Extract states first, then cities (state-first display) // Format raw slug/label. For cities, strip 2-letter
List<String> extractValues(dynamic fv) { // 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<String> extractValues(dynamic fv,
{required bool isCity}) {
final out = <String>[]; final out = <String>[];
if (fv.jsonValue is List) { if (fv.jsonValue is List) {
for (final v in (fv.jsonValue as List)) { for (final v in (fv.jsonValue as List)) {
final s = v.toString().trim(); final s = v.toString().trim();
if (s.isEmpty) continue; if (s.isEmpty) continue;
out.add(s out.add(formatPart(s, isCity: isCity));
.split('_')
.map((w) => w.isNotEmpty
? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}'
: '')
.join(' '));
} }
} else if (fv.textValue != null && } else if (fv.textValue != null &&
(fv.textValue as String).isNotEmpty) { (fv.textValue as String).isNotEmpty) {
out.add(fv.textValue as String); out.add(formatPart(fv.textValue as String,
isCity: isCity));
} }
return out; return out;
} }
@@ -626,7 +644,7 @@ class _AgentCardState extends State<_AgentCard> {
for (final fv in agent.fieldValues) { for (final fv in agent.fieldValues) {
final slug = fv.fieldSlug.toLowerCase(); final slug = fv.fieldSlug.toLowerCase();
if (slug == 'state' || slug == 'state_name') { if (slug == 'state' || slug == 'state_name') {
for (final v in extractValues(fv)) { for (final v in extractValues(fv, isCity: false)) {
addUnique(v); addUnique(v);
} }
} }
@@ -635,7 +653,7 @@ class _AgentCardState extends State<_AgentCard> {
for (final fv in agent.fieldValues) { for (final fv in agent.fieldValues) {
final slug = fv.fieldSlug.toLowerCase(); final slug = fv.fieldSlug.toLowerCase();
if (slug == 'city' || slug == 'city_name') { if (slug == 'city' || slug == 'city_name') {
for (final v in extractValues(fv)) { for (final v in extractValues(fv, isCity: true)) {
addUnique(v); addUnique(v);
} }
} }

View File

@@ -160,13 +160,13 @@ class _AgentFilterSheetState extends State<AgentFilterSheet> {
top: false, top: false,
child: Row( child: Row(
children: [ children: [
// Clear All // Clear All — reset selections but keep the sheet open
// so the user sees the cleared state and can continue.
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
_clearAll(); _clearAll();
widget.onClearAll(); widget.onClearAll();
Navigator.of(context).pop();
}, },
child: Container( child: Container(
height: 29, height: 29,