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 = { '<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 ); } // Profile card data structure export interface ProfileCardData { bio: string; expertise: string[]; serviceAreas: string[]; city: string | null; state: string | null; } // Contact info data structure export interface ContactInfoData { email: string | null; phone: string | null; } /** * Maps field values to contact info data (email, phone) */ export function mapFieldValuesToContactInfo(fieldValues: FieldValueResponse[]): ContactInfoData { // Get phone from various possible field slugs const phoneNumber = getFieldValue(fieldValues, 'phone_number') as string | undefined; const phone = getFieldValue(fieldValues, 'phone') as string | undefined; const cellNumber = getFieldValue(fieldValues, 'cell_number') as string | undefined; const officeNumber = getFieldValue(fieldValues, 'office_number') as string | undefined; // Get email from various possible field slugs const emailField = getFieldValue(fieldValues, 'email') as string | undefined; const emailAddress = getFieldValue(fieldValues, 'email_address') as string | undefined; return { email: emailField || emailAddress || null, phone: phoneNumber || phone || cellNumber || officeNumber || null, }; } // 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[]; } // Availability data structure export interface AvailabilityData { type: string; schedule: string[]; } // Map availability option values to human-readable labels const availabilityLabels: Record = { '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, }; } /** * Helper to format snake_case values to Title Case */ function formatSnakeCaseToTitleCase(value: string): string { return value .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' '); } /** * Extract all values from a field that may be an array (multiselect) or string * Returns formatted values joined by comma, or null if empty */ function extractAllValues(value: unknown): string | null { if (Array.isArray(value) && value.length > 0) { return value.map(v => formatSnakeCaseToTitleCase(String(v))).join(', '); } if (typeof value === 'string' && value.trim()) { return formatSnakeCaseToTitleCase(value); } return null; } /** * Maps field values to profile card data (bio, expertise, service areas, location) */ 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; // Get city and state from dynamic fields (multiselect fields) const cityValue = getFieldValue(fieldValues, 'city'); const stateValue = getFieldValue(fieldValues, 'state'); return { bio: description || descrption || biography || bio || '', expertise: aboutMeExpertise || expertiseAreas || specializations || areasOfExpertise || [], serviceAreas: serviceAreas || licensedAreas || coverageAreas || [], city: extractAllValues(cityValue), state: extractAllValues(stateValue), }; } // Field slug to icon mapping for specialization fields const fieldIconMapping: Record = { '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, deduplicating by fieldSlug const fields: SpecializationFieldItem[] = []; const seenSlugs = new Set(); for (const field of specializationFields) { const { fieldSlug, fieldName, value } = field; // Skip if no field info or no value if (!fieldSlug || !fieldName) continue; // Skip duplicate fieldSlugs (API may return duplicates) if (seenSlugs.has(fieldSlug)) continue; seenSlugs.add(fieldSlug); 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); }