2026-01-24 23:10:02 +05:30
|
|
|
|
import { FieldValueResponse } from '@/services/agents.service';
|
|
|
|
|
|
|
|
|
|
|
|
// Experience data structure expected by ExperienceSection component
|
|
|
|
|
|
export interface ExperienceData {
|
|
|
|
|
|
years: string;
|
|
|
|
|
|
contracts: string;
|
|
|
|
|
|
licensingAreas: string[];
|
|
|
|
|
|
expertiseYears: { area: string; years: string }[];
|
|
|
|
|
|
certifications: { name: string; org: string }[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Certification entry from repeater field
|
|
|
|
|
|
interface CertificationEntry {
|
|
|
|
|
|
certification_name?: string;
|
|
|
|
|
|
issuing_organization?: string;
|
|
|
|
|
|
issue_date?: string;
|
|
|
|
|
|
expiration_date?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper to get field value by slug
|
|
|
|
|
|
function getFieldValue(fieldValues: FieldValueResponse[], slug: string): unknown {
|
|
|
|
|
|
const field = fieldValues.find(f => f.fieldSlug === slug);
|
|
|
|
|
|
return field?.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper to get label from value for select/radio fields
|
|
|
|
|
|
function mapYearsValueToLabel(value: string | undefined): string {
|
|
|
|
|
|
if (!value) return '-';
|
|
|
|
|
|
|
|
|
|
|
|
const mapping: Record<string, string> = {
|
|
|
|
|
|
'<2': 'Less than 2 years',
|
|
|
|
|
|
'2+': '2+ years',
|
|
|
|
|
|
'5+': '5+ years',
|
|
|
|
|
|
'10+': '10+ years',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return mapping[value] || value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper to format contracts count
|
|
|
|
|
|
function formatContractsCount(value: unknown): string {
|
|
|
|
|
|
if (!value) return '-';
|
|
|
|
|
|
|
|
|
|
|
|
const num = typeof value === 'number' ? value : parseInt(String(value), 10);
|
|
|
|
|
|
if (isNaN(num)) return '-';
|
|
|
|
|
|
|
|
|
|
|
|
if (num >= 100) return '100+ Contracts';
|
|
|
|
|
|
if (num >= 50) return '50+ Contracts';
|
|
|
|
|
|
if (num >= 20) return '20+ Contracts';
|
|
|
|
|
|
if (num >= 10) return '10+ Contracts';
|
|
|
|
|
|
if (num >= 5) return '5+ Contracts';
|
|
|
|
|
|
return `${num} Contract${num !== 1 ? 's' : ''}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse expertise areas with years (format: "Area Name - X yrs" or just "Area Name")
|
|
|
|
|
|
function parseExpertiseAreas(value: unknown): { area: string; years: string }[] {
|
|
|
|
|
|
if (!value || !Array.isArray(value)) return [];
|
|
|
|
|
|
|
|
|
|
|
|
return value.map((item: string) => {
|
|
|
|
|
|
// Check if it contains a year indicator
|
|
|
|
|
|
const yearMatch = item.match(/^(.+?)\s*[-–]\s*(\d+)\s*(yrs?|years?)$/i);
|
|
|
|
|
|
if (yearMatch) {
|
|
|
|
|
|
return { area: yearMatch[1].trim(), years: `${yearMatch[2]} yrs` };
|
|
|
|
|
|
}
|
|
|
|
|
|
// Just area name without years
|
|
|
|
|
|
return { area: item.trim(), years: '' };
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse certification entries from repeater field
|
|
|
|
|
|
function parseCertifications(value: unknown): { name: string; org: string }[] {
|
|
|
|
|
|
if (!value) return [];
|
|
|
|
|
|
|
|
|
|
|
|
// If it's an array of certification entries
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
|
return value
|
|
|
|
|
|
.filter((entry: CertificationEntry) => entry?.certification_name)
|
|
|
|
|
|
.map((entry: CertificationEntry) => ({
|
|
|
|
|
|
name: entry.certification_name || '',
|
|
|
|
|
|
org: entry.issuing_organization || '',
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Maps field values from API response to ExperienceData structure
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function mapFieldValuesToExperience(fieldValues: FieldValueResponse[]): ExperienceData {
|
|
|
|
|
|
// Get years in business (could be years_in_business from Professional or Lender experience)
|
|
|
|
|
|
const yearsInBusiness = getFieldValue(fieldValues, 'years_in_business') as string | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
// Get contracts completed
|
|
|
|
|
|
const contractsCompleted = getFieldValue(fieldValues, 'contracts_completed');
|
|
|
|
|
|
|
|
|
|
|
|
// Get licensing areas (could be licensed_areas from Professional or Lender)
|
|
|
|
|
|
const licensedAreas = getFieldValue(fieldValues, 'licensed_areas') as string[] | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
// Get expertise areas with years
|
|
|
|
|
|
const expertiseAreas = getFieldValue(fieldValues, 'expertise_areas') as string[] | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
// Get certification entries from repeater
|
|
|
|
|
|
const certificationEntries = getFieldValue(fieldValues, 'certification_entries');
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
years: mapYearsValueToLabel(yearsInBusiness),
|
|
|
|
|
|
contracts: formatContractsCount(contractsCompleted),
|
|
|
|
|
|
licensingAreas: licensedAreas || [],
|
|
|
|
|
|
expertiseYears: parseExpertiseAreas(expertiseAreas),
|
|
|
|
|
|
certifications: parseCertifications(certificationEntries),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Check if experience data has any meaningful content
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function hasExperienceData(experience: ExperienceData): boolean {
|
|
|
|
|
|
return (
|
|
|
|
|
|
experience.years !== '-' ||
|
|
|
|
|
|
experience.contracts !== '-' ||
|
|
|
|
|
|
experience.licensingAreas.length > 0 ||
|
|
|
|
|
|
experience.expertiseYears.length > 0 ||
|
|
|
|
|
|
experience.certifications.length > 0
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-01-24 23:31:32 +05:30
|
|
|
|
|
|
|
|
|
|
// Profile card data structure
|
|
|
|
|
|
export interface ProfileCardData {
|
|
|
|
|
|
bio: string;
|
|
|
|
|
|
expertise: string[];
|
|
|
|
|
|
serviceAreas: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-25 01:15:02 +05:30
|
|
|
|
// Dynamic field data structure for SpecializationSection component
|
|
|
|
|
|
// Each field within the "Specialization" section becomes a card
|
|
|
|
|
|
export interface SpecializationFieldItem {
|
|
|
|
|
|
fieldSlug: string;
|
|
|
|
|
|
fieldName: string;
|
|
|
|
|
|
icon?: string;
|
|
|
|
|
|
values: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SpecializationFieldsData {
|
|
|
|
|
|
fields: SpecializationFieldItem[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 23:31:32 +05:30
|
|
|
|
// Availability data structure
|
|
|
|
|
|
export interface AvailabilityData {
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
schedule: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Map availability option values to human-readable labels
|
|
|
|
|
|
const availabilityLabels: Record<string, string> = {
|
|
|
|
|
|
'mf_9_5': 'Monday - Friday, 9 AM - 5 PM',
|
|
|
|
|
|
'mf_8_6': 'Monday - Friday, 8 AM - 6 PM',
|
|
|
|
|
|
'weekends': 'Weekends',
|
|
|
|
|
|
'evenings': 'Evenings',
|
|
|
|
|
|
'flexible': 'Flexible Schedule',
|
|
|
|
|
|
'full_time': 'Full-time',
|
|
|
|
|
|
'part_time': 'Part-time',
|
|
|
|
|
|
'24_7': '24/7 Available',
|
|
|
|
|
|
'by_appointment': 'By Appointment Only',
|
|
|
|
|
|
'monday': 'Monday',
|
|
|
|
|
|
'tuesday': 'Tuesday',
|
|
|
|
|
|
'wednesday': 'Wednesday',
|
|
|
|
|
|
'thursday': 'Thursday',
|
|
|
|
|
|
'friday': 'Friday',
|
|
|
|
|
|
'saturday': 'Saturday',
|
|
|
|
|
|
'sunday': 'Sunday',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Maps field values to availability data
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function mapFieldValuesToAvailability(fieldValues: FieldValueResponse[]): AvailabilityData {
|
|
|
|
|
|
const availabilityValues = getFieldValue(fieldValues, 'availability') as string[] | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
if (!availabilityValues || availabilityValues.length === 0) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
type: '',
|
|
|
|
|
|
schedule: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Map values to human-readable labels
|
|
|
|
|
|
const schedule = availabilityValues.map(val =>
|
|
|
|
|
|
availabilityLabels[val] || val.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Determine type based on values
|
|
|
|
|
|
let type = 'Available';
|
|
|
|
|
|
if (availabilityValues.includes('full_time') || availabilityValues.includes('mf_9_5') || availabilityValues.includes('mf_8_6')) {
|
|
|
|
|
|
type = 'Full-time';
|
|
|
|
|
|
} else if (availabilityValues.includes('part_time')) {
|
|
|
|
|
|
type = 'Part-time';
|
|
|
|
|
|
} else if (availabilityValues.includes('flexible')) {
|
|
|
|
|
|
type = 'Flexible';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
type,
|
|
|
|
|
|
schedule,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Maps field values to profile card data (bio, expertise, service areas)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function mapFieldValuesToProfileCard(fieldValues: FieldValueResponse[]): ProfileCardData {
|
|
|
|
|
|
// Get bio/description from various possible fields
|
|
|
|
|
|
const description = getFieldValue(fieldValues, 'description') as string | undefined;
|
|
|
|
|
|
const descrption = getFieldValue(fieldValues, 'descrption') as string | undefined; // typo in field slug
|
|
|
|
|
|
const biography = getFieldValue(fieldValues, 'biography') as string | undefined;
|
|
|
|
|
|
const bio = getFieldValue(fieldValues, 'bio') as string | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
// Get expertise from various possible fields
|
|
|
|
|
|
const aboutMeExpertise = getFieldValue(fieldValues, 'about_me_expertise') as string[] | undefined;
|
|
|
|
|
|
const expertiseAreas = getFieldValue(fieldValues, 'expertise_areas') as string[] | undefined;
|
|
|
|
|
|
const specializations = getFieldValue(fieldValues, 'specializations') as string[] | undefined;
|
|
|
|
|
|
const areasOfExpertise = getFieldValue(fieldValues, 'areas_of_expertise') as string[] | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
// Get service areas from various possible fields
|
|
|
|
|
|
const serviceAreas = getFieldValue(fieldValues, 'service_areas') as string[] | undefined;
|
|
|
|
|
|
const licensedAreas = getFieldValue(fieldValues, 'licensed_areas') as string[] | undefined;
|
|
|
|
|
|
const coverageAreas = getFieldValue(fieldValues, 'coverage_areas') as string[] | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
bio: description || descrption || biography || bio || '',
|
|
|
|
|
|
expertise: aboutMeExpertise || expertiseAreas || specializations || areasOfExpertise || [],
|
|
|
|
|
|
serviceAreas: serviceAreas || licensedAreas || coverageAreas || [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-01-25 01:15:02 +05:30
|
|
|
|
|
|
|
|
|
|
// Field slug to icon mapping for specialization fields
|
|
|
|
|
|
const fieldIconMapping: Record<string, string> = {
|
|
|
|
|
|
'client_specialization': '/assets/icons/home-icon.svg',
|
|
|
|
|
|
'property_type': '/assets/icons/chart-icon.svg',
|
|
|
|
|
|
'loan_type': '/assets/icons/wallet-icon.svg',
|
|
|
|
|
|
'areas_expertise_years': '/assets/icons/certification-icon.svg',
|
|
|
|
|
|
'licensing_areas': '/assets/icons/location-icon.svg',
|
|
|
|
|
|
'hobbies': '/assets/icons/heart-icon.svg',
|
|
|
|
|
|
'hobbies_interests': '/assets/icons/heart-icon.svg',
|
|
|
|
|
|
'price_points': '/assets/icons/wallet-icon.svg',
|
|
|
|
|
|
'price_point': '/assets/icons/wallet-icon.svg',
|
|
|
|
|
|
'default': '/assets/icons/home-icon.svg',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get icon for a field based on its slug
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getFieldIcon(fieldSlug: string): string {
|
|
|
|
|
|
const normalizedSlug = fieldSlug.toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
// Check for exact match first
|
|
|
|
|
|
if (fieldIconMapping[normalizedSlug]) {
|
|
|
|
|
|
return fieldIconMapping[normalizedSlug];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for partial matches
|
|
|
|
|
|
if (normalizedSlug.includes('loan')) return fieldIconMapping['loan_type'];
|
|
|
|
|
|
if (normalizedSlug.includes('property')) return fieldIconMapping['property_type'];
|
|
|
|
|
|
if (normalizedSlug.includes('hobby') || normalizedSlug.includes('interest')) return fieldIconMapping['hobbies'];
|
|
|
|
|
|
if (normalizedSlug.includes('price')) return fieldIconMapping['price_points'];
|
|
|
|
|
|
if (normalizedSlug.includes('client') || normalizedSlug.includes('special')) return fieldIconMapping['client_specialization'];
|
|
|
|
|
|
if (normalizedSlug.includes('license') || normalizedSlug.includes('area')) return fieldIconMapping['licensing_areas'];
|
|
|
|
|
|
if (normalizedSlug.includes('expertise') || normalizedSlug.includes('certification')) return fieldIconMapping['areas_expertise_years'];
|
|
|
|
|
|
|
|
|
|
|
|
return fieldIconMapping['default'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Convert field value to string array for display
|
|
|
|
|
|
*/
|
|
|
|
|
|
function valueToStringArray(value: unknown): string[] {
|
|
|
|
|
|
if (!value) return [];
|
|
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
|
return value.map(v => String(v)).filter(v => v.trim() !== '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof value === 'string' && value.trim()) {
|
|
|
|
|
|
return [value];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
|
|
|
|
return [String(value)];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Maps field values from the "Specialization" section to individual field cards
|
|
|
|
|
|
* Each field within the Specialization section becomes its own card
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function mapFieldValuesToSpecializationFields(fieldValues: FieldValueResponse[]): SpecializationFieldsData {
|
|
|
|
|
|
// Filter only fields from the "Specialization" section
|
|
|
|
|
|
const specializationFields = fieldValues.filter(field => {
|
|
|
|
|
|
const sectionSlug = field.sectionSlug?.toLowerCase() || '';
|
|
|
|
|
|
return sectionSlug === 'specialization' || sectionSlug.includes('specialization');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Create a card for each field
|
|
|
|
|
|
const fields: SpecializationFieldItem[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
for (const field of specializationFields) {
|
|
|
|
|
|
const { fieldSlug, fieldName, value } = field;
|
|
|
|
|
|
|
|
|
|
|
|
// Skip if no field info or no value
|
|
|
|
|
|
if (!fieldSlug || !fieldName) continue;
|
|
|
|
|
|
|
|
|
|
|
|
const values = valueToStringArray(value);
|
|
|
|
|
|
if (values.length === 0) continue;
|
|
|
|
|
|
|
|
|
|
|
|
fields.push({
|
|
|
|
|
|
fieldSlug,
|
|
|
|
|
|
fieldName,
|
|
|
|
|
|
icon: getFieldIcon(fieldSlug),
|
|
|
|
|
|
values,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { fields };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Check if specialization fields data has any meaningful content
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function hasSpecializationFieldsData(data: SpecializationFieldsData): boolean {
|
|
|
|
|
|
return data.fields.length > 0 && data.fields.some(f => f.values.length > 0);
|
|
|
|
|
|
}
|