302 lines
9.2 KiB
Dart
302 lines
9.2 KiB
Dart
import 'package:real_estate_mobile/features/agents/data/models/agent_type.dart';
|
|
|
|
class AgentProfile {
|
|
final String id;
|
|
final String? userId;
|
|
final String firstName;
|
|
final String lastName;
|
|
final String? headline;
|
|
final String? bio;
|
|
final String? avatar;
|
|
final String? companyName;
|
|
final String? phone;
|
|
final String? website;
|
|
final String? city;
|
|
final String? state;
|
|
final String? country;
|
|
final double? averageRating;
|
|
final int totalReviews;
|
|
final bool isVerified;
|
|
final bool isFeatured;
|
|
final bool isActive;
|
|
final AgentType? agentType;
|
|
final AgentUser? user;
|
|
final List<AgentFieldValue> fieldValues;
|
|
final String? createdAt;
|
|
final List<String> legacySpecializations;
|
|
final List<String> languages;
|
|
final List<String> serviceAreas;
|
|
|
|
const AgentProfile({
|
|
required this.id,
|
|
this.userId,
|
|
required this.firstName,
|
|
required this.lastName,
|
|
this.headline,
|
|
this.bio,
|
|
this.avatar,
|
|
this.companyName,
|
|
this.phone,
|
|
this.website,
|
|
this.city,
|
|
this.state,
|
|
this.country,
|
|
this.averageRating,
|
|
this.totalReviews = 0,
|
|
this.isVerified = false,
|
|
this.isFeatured = false,
|
|
this.isActive = true,
|
|
this.agentType,
|
|
this.user,
|
|
this.fieldValues = const [],
|
|
this.createdAt,
|
|
this.legacySpecializations = const [],
|
|
this.languages = const [],
|
|
this.serviceAreas = const [],
|
|
});
|
|
|
|
String get fullName => '$firstName $lastName'.trim();
|
|
|
|
/// Format "Member Since March 2024" from createdAt date string.
|
|
String get memberSinceText {
|
|
if (createdAt == null || createdAt!.isEmpty) return 'Member Since 2024';
|
|
try {
|
|
final date = DateTime.parse(createdAt!);
|
|
const months = [
|
|
'January', 'February', 'March', 'April', 'May', 'June',
|
|
'July', 'August', 'September', 'October', 'November', 'December',
|
|
];
|
|
return 'Member Since ${months[date.month - 1]} ${date.year}';
|
|
} catch (_) {
|
|
return 'Member Since 2024';
|
|
}
|
|
}
|
|
|
|
/// Get avatar URL — check agent avatar first, then user avatar
|
|
String? get avatarUrl => avatar ?? user?.avatar;
|
|
|
|
/// Get description — check fieldValues 'description' first, then bio (matches web)
|
|
String get description {
|
|
for (final fv in fieldValues) {
|
|
if (fv.fieldSlug == 'description' && fv.textValue != null && fv.textValue!.isNotEmpty) {
|
|
return fv.textValue!;
|
|
}
|
|
}
|
|
return bio ?? '';
|
|
}
|
|
|
|
String get location {
|
|
// Direct fields first
|
|
if (city != null && state != null) return '$city, $state';
|
|
if (city != null) return city!;
|
|
if (state != null) return state!;
|
|
|
|
// Fallback: extract from fieldValues (matches web getLocationString)
|
|
String? fvCity;
|
|
String? fvState;
|
|
for (final fv in fieldValues) {
|
|
final slug = fv.fieldSlug.toLowerCase();
|
|
if (slug == 'city' || slug == 'city_name') {
|
|
fvCity = _extractFieldString(fv);
|
|
} else if (slug == 'state' || slug == 'state_name') {
|
|
fvState = _extractFieldString(fv);
|
|
}
|
|
}
|
|
final parts = [fvCity, fvState].where((s) => s != null && s.isNotEmpty);
|
|
if (parts.isNotEmpty) return parts.join(', ');
|
|
|
|
if (country != null && country!.isNotEmpty) return country!;
|
|
if (serviceAreas.isNotEmpty) return serviceAreas.first;
|
|
|
|
return '';
|
|
}
|
|
|
|
/// Extract a display string from a field value (handles jsonValue arrays).
|
|
static String? _extractFieldString(AgentFieldValue fv) {
|
|
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 (fv.textValue != null && fv.textValue!.isNotEmpty) return fv.textValue;
|
|
return null;
|
|
}
|
|
|
|
/// Get experience from field values
|
|
String get experienceText {
|
|
for (final fv in fieldValues) {
|
|
if (fv.fieldSlug.contains('experience') ||
|
|
fv.fieldName.toLowerCase().contains('experience')) {
|
|
if (fv.textValue != null) return fv.textValue!;
|
|
if (fv.numberValue != null) {
|
|
return '${fv.numberValue!.toInt()}+ years in real estate.';
|
|
}
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/// Get specializations / expertise from field values.
|
|
/// Matches web's getExpertiseTags slugs.
|
|
List<String> get specializations {
|
|
final tags = <String>[];
|
|
const expertiseSlugs = [
|
|
'about_me_expertise',
|
|
'expertise_areas',
|
|
'specializations',
|
|
'areas_of_expertise',
|
|
'expertise',
|
|
'specialization',
|
|
'property_type',
|
|
'loan_type',
|
|
];
|
|
|
|
for (final fv in fieldValues) {
|
|
final slug = fv.fieldSlug.toLowerCase();
|
|
if (!expertiseSlugs.any((s) => slug.contains(s))) continue;
|
|
|
|
if (fv.jsonValue is List) {
|
|
for (final v in (fv.jsonValue as List)) {
|
|
final s = v.toString().trim();
|
|
if (s.isEmpty) continue;
|
|
final display = s
|
|
.split('_')
|
|
.map((w) => w.isNotEmpty
|
|
? '${w[0].toUpperCase()}${w.substring(1).toLowerCase()}'
|
|
: '')
|
|
.join(' ');
|
|
if (!tags.contains(display)) tags.add(display);
|
|
}
|
|
} else if (fv.textValue != null && fv.textValue!.trim().isNotEmpty) {
|
|
final display = fv.textValue!.trim();
|
|
if (!tags.contains(display)) tags.add(display);
|
|
}
|
|
}
|
|
|
|
// Add from legacy specializations array
|
|
for (final spec in legacySpecializations) {
|
|
if (spec.isNotEmpty && !tags.contains(spec)) tags.add(spec);
|
|
}
|
|
|
|
// Add from languages array
|
|
for (final lang in languages) {
|
|
if (lang.isNotEmpty && !tags.contains(lang)) tags.add(lang);
|
|
}
|
|
|
|
return tags;
|
|
}
|
|
|
|
factory AgentProfile.fromJson(Map<String, dynamic> json) {
|
|
return AgentProfile(
|
|
id: json['id'] as String,
|
|
userId: json['userId'] as String?,
|
|
firstName: json['firstName'] as String? ?? '',
|
|
lastName: json['lastName'] as String? ?? '',
|
|
headline: json['headline'] as String?,
|
|
bio: json['bio'] as String?,
|
|
avatar: json['avatar'] as String?,
|
|
companyName: json['companyName'] as String?,
|
|
phone: json['phone'] as String?,
|
|
website: json['website'] as String?,
|
|
city: json['city'] as String?,
|
|
state: json['state'] as String?,
|
|
country: json['country'] as String?,
|
|
averageRating: (json['averageRating'] as num?)?.toDouble(),
|
|
totalReviews: json['totalReviews'] as int? ?? 0,
|
|
isVerified: json['isVerified'] as bool? ?? false,
|
|
isFeatured: json['isFeatured'] as bool? ?? false,
|
|
isActive: json['isActive'] as bool? ?? true,
|
|
agentType: json['agentType'] != null
|
|
? AgentType.fromJson(json['agentType'] as Map<String, dynamic>)
|
|
: null,
|
|
user: json['user'] != null
|
|
? AgentUser.fromJson(json['user'] as Map<String, dynamic>)
|
|
: null,
|
|
fieldValues: (json['fieldValues'] as List<dynamic>?)
|
|
?.map((e) => AgentFieldValue.fromJson(e as Map<String, dynamic>))
|
|
.toList() ??
|
|
const [],
|
|
createdAt: json['createdAt'] as String?,
|
|
legacySpecializations: (json['specializations'] as List<dynamic>?)
|
|
?.map((e) => e.toString())
|
|
.toList() ??
|
|
const [],
|
|
languages: (json['languages'] as List<dynamic>?)
|
|
?.map((e) => e.toString())
|
|
.toList() ??
|
|
const [],
|
|
serviceAreas: (json['serviceAreas'] as List<dynamic>?)
|
|
?.map((e) => e.toString())
|
|
.toList() ??
|
|
const [],
|
|
);
|
|
}
|
|
}
|
|
|
|
class AgentUser {
|
|
final String id;
|
|
final String email;
|
|
final String? avatar;
|
|
|
|
const AgentUser({
|
|
required this.id,
|
|
required this.email,
|
|
this.avatar,
|
|
});
|
|
|
|
factory AgentUser.fromJson(Map<String, dynamic> json) {
|
|
return AgentUser(
|
|
id: json['id'] as String,
|
|
email: json['email'] as String? ?? '',
|
|
avatar: json['avatar'] as String?,
|
|
);
|
|
}
|
|
}
|
|
|
|
class AgentFieldValue {
|
|
final String id;
|
|
final String fieldSlug;
|
|
final String fieldName;
|
|
final String fieldType;
|
|
final String? textValue;
|
|
final num? numberValue;
|
|
final bool? booleanValue;
|
|
final dynamic jsonValue;
|
|
final String? dateValue;
|
|
|
|
const AgentFieldValue({
|
|
required this.id,
|
|
required this.fieldSlug,
|
|
required this.fieldName,
|
|
required this.fieldType,
|
|
this.textValue,
|
|
this.numberValue,
|
|
this.booleanValue,
|
|
this.jsonValue,
|
|
this.dateValue,
|
|
});
|
|
|
|
factory AgentFieldValue.fromJson(Map<String, dynamic> json) {
|
|
// API returns field info nested: { field: { slug, name, fieldType } }
|
|
// Fallback to top-level fieldSlug/fieldName/fieldType for compatibility
|
|
final field = json['field'] as Map<String, dynamic>?;
|
|
return AgentFieldValue(
|
|
id: json['id'] as String? ?? '',
|
|
fieldSlug: field?['slug'] as String? ?? json['fieldSlug'] as String? ?? '',
|
|
fieldName: field?['name'] as String? ?? json['fieldName'] as String? ?? '',
|
|
fieldType: field?['fieldType'] as String? ?? json['fieldType'] as String? ?? '',
|
|
textValue: json['textValue'] as String?,
|
|
numberValue: json['numberValue'] as num?,
|
|
booleanValue: json['booleanValue'] as bool?,
|
|
jsonValue: json['jsonValue'],
|
|
dateValue: json['dateValue'] as String?,
|
|
);
|
|
}
|
|
}
|