feat: implement dynamic work environment section and improve availability data mapping with backend-resolved labels
This commit is contained in:
@@ -21,11 +21,12 @@ import { connectionRequestsService } from '@/services/connection-requests.servic
|
|||||||
import { uploadService } from '@/services/upload.service';
|
import { uploadService } from '@/services/upload.service';
|
||||||
import { usersService } from '@/services/users.service';
|
import { usersService } from '@/services/users.service';
|
||||||
import { testimonialsService, Testimonial } from '@/services/testimonials.service';
|
import { testimonialsService, Testimonial } from '@/services/testimonials.service';
|
||||||
import { mapFieldValuesToExperience, mapFieldValuesToProfileCard, mapFieldValuesToAvailability, mapFieldValuesToSpecializationFields, mapFieldValuesToContactInfo, ExperienceData, ProfileCardData, AvailabilityData, SpecializationFieldsData, ContactInfoData } from '@/utils/profileDataMapper';
|
import { mapFieldValuesToExperience, mapFieldValuesToProfileCard, mapFieldValuesToAvailability, mapFieldValuesToSpecializationFields, mapFieldValuesToContactInfo, mapFieldValuesToWorkEnvironment, ExperienceData, ProfileCardData, AvailabilityData, SpecializationFieldsData, ContactInfoData, WorkEnvironmentData } from '@/utils/profileDataMapper';
|
||||||
|
|
||||||
// Mock data for sections not yet available from API
|
// Default work environment data
|
||||||
const mockData = {
|
const defaultWorkEnvironmentData: WorkEnvironmentData = {
|
||||||
preferredWorkEnvironment: 'Preferred work environment includes on-site property visits, in-depth market research, client consultations, property tours, and active involvement in negotiations to ensure the best outcomes',
|
label: 'Preferred Work Environment',
|
||||||
|
content: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default experience data when no field values are available
|
// Default experience data when no field values are available
|
||||||
@@ -59,6 +60,7 @@ const defaultProfileCardData: ProfileCardData = {
|
|||||||
const defaultAvailabilityData: AvailabilityData = {
|
const defaultAvailabilityData: AvailabilityData = {
|
||||||
type: '',
|
type: '',
|
||||||
schedule: [],
|
schedule: [],
|
||||||
|
label: 'Availability',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default specialization fields data when no field values are available
|
// Default specialization fields data when no field values are available
|
||||||
@@ -79,6 +81,7 @@ export default function AgentDashboard() {
|
|||||||
const [experienceData, setExperienceData] = useState<ExperienceData>(defaultExperience);
|
const [experienceData, setExperienceData] = useState<ExperienceData>(defaultExperience);
|
||||||
const [profileCardData, setProfileCardData] = useState<ProfileCardData>(defaultProfileCardData);
|
const [profileCardData, setProfileCardData] = useState<ProfileCardData>(defaultProfileCardData);
|
||||||
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
||||||
|
const [workEnvironmentData, setWorkEnvironmentData] = useState<WorkEnvironmentData>(defaultWorkEnvironmentData);
|
||||||
const [specializationFieldsData, setSpecializationFieldsData] = useState<SpecializationFieldsData>(defaultSpecializationFieldsData);
|
const [specializationFieldsData, setSpecializationFieldsData] = useState<SpecializationFieldsData>(defaultSpecializationFieldsData);
|
||||||
const [contactInfoData, setContactInfoData] = useState<ContactInfoData>(defaultContactInfoData);
|
const [contactInfoData, setContactInfoData] = useState<ContactInfoData>(defaultContactInfoData);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -150,6 +153,10 @@ export default function AgentDashboard() {
|
|||||||
const mappedAvailability = mapFieldValuesToAvailability(fieldValuesResponse.fieldValues);
|
const mappedAvailability = mapFieldValuesToAvailability(fieldValuesResponse.fieldValues);
|
||||||
setAvailabilityData(mappedAvailability);
|
setAvailabilityData(mappedAvailability);
|
||||||
|
|
||||||
|
// Map field values to work environment data
|
||||||
|
const mappedWorkEnv = mapFieldValuesToWorkEnvironment(fieldValuesResponse.fieldValues);
|
||||||
|
setWorkEnvironmentData(mappedWorkEnv);
|
||||||
|
|
||||||
// Map field values to specialization fields data (only fields from "Specialization" section)
|
// Map field values to specialization fields data (only fields from "Specialization" section)
|
||||||
const mappedSpecializationFields = mapFieldValuesToSpecializationFields(fieldValuesResponse.fieldValues);
|
const mappedSpecializationFields = mapFieldValuesToSpecializationFields(fieldValuesResponse.fieldValues);
|
||||||
setSpecializationFieldsData(mappedSpecializationFields);
|
setSpecializationFieldsData(mappedSpecializationFields);
|
||||||
@@ -452,11 +459,11 @@ export default function AgentDashboard() {
|
|||||||
{/* Info Cards Section */}
|
{/* Info Cards Section */}
|
||||||
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6">
|
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6">
|
||||||
<InfoCard
|
<InfoCard
|
||||||
title="Availability"
|
title={availabilityData.label}
|
||||||
icon={
|
icon={
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icons/availability-clock-icon.svg"
|
src="/assets/icons/availability-clock-icon.svg"
|
||||||
alt="Availability"
|
alt={availabilityData.label}
|
||||||
width={28}
|
width={28}
|
||||||
height={31}
|
height={31}
|
||||||
/>
|
/>
|
||||||
@@ -478,18 +485,20 @@ export default function AgentDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InfoCard
|
{workEnvironmentData.content && (
|
||||||
title=""
|
<InfoCard
|
||||||
icon={
|
title={workEnvironmentData.label}
|
||||||
<Image
|
icon={
|
||||||
src="/assets/icons/work-environment-icon.svg"
|
<Image
|
||||||
alt="Work environment"
|
src="/assets/icons/work-environment-icon.svg"
|
||||||
width={28}
|
alt={workEnvironmentData.label}
|
||||||
height={28}
|
width={28}
|
||||||
/>
|
height={28}
|
||||||
}
|
/>
|
||||||
content={<p className="font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]">{mockData.preferredWorkEnvironment}</p>}
|
}
|
||||||
/>
|
content={<p className="font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]">{workEnvironmentData.content}</p>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{testimonials.length > 0 && (
|
{testimonials.length > 0 && (
|
||||||
<InfoCard
|
<InfoCard
|
||||||
title=""
|
title=""
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ import { agentsService, AgentProfile, FieldValueResponse } from '@/services/agen
|
|||||||
import { connectionRequestsService, ConnectionStatus } from '@/services/connection-requests.service';
|
import { connectionRequestsService, ConnectionStatus } from '@/services/connection-requests.service';
|
||||||
import { uploadService } from '@/services/upload.service';
|
import { uploadService } from '@/services/upload.service';
|
||||||
import { testimonialsService, Testimonial } from '@/services/testimonials.service';
|
import { testimonialsService, Testimonial } from '@/services/testimonials.service';
|
||||||
import { mapFieldValuesToExperience, mapFieldValuesToSpecializationFields, mapFieldValuesToProfileCard, mapFieldValuesToContactInfo, mapFieldValuesToAvailability, ExperienceData, SpecializationFieldsData, ProfileCardData, ContactInfoData, AvailabilityData } from '@/utils/profileDataMapper';
|
import { mapFieldValuesToExperience, mapFieldValuesToSpecializationFields, mapFieldValuesToProfileCard, mapFieldValuesToContactInfo, mapFieldValuesToAvailability, mapFieldValuesToWorkEnvironment, ExperienceData, SpecializationFieldsData, ProfileCardData, ContactInfoData, AvailabilityData, WorkEnvironmentData } from '@/utils/profileDataMapper';
|
||||||
|
|
||||||
// Mock data for sections not yet available from API
|
// Default work environment data
|
||||||
const mockData = {
|
const defaultWorkEnvironmentData: WorkEnvironmentData = {
|
||||||
preferredWorkEnvironment: 'Preferred work environment includes on-site property visits, in-depth market research, client consultations, property tours, and active involvement in negotiations to ensure the best outcomes',
|
label: 'Preferred Work Environment',
|
||||||
|
content: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default experience data when no field values are available
|
// Default experience data when no field values are available
|
||||||
@@ -70,6 +71,7 @@ const defaultContactInfoData: ContactInfoData = {
|
|||||||
const defaultAvailabilityData: AvailabilityData = {
|
const defaultAvailabilityData: AvailabilityData = {
|
||||||
type: '',
|
type: '',
|
||||||
schedule: [],
|
schedule: [],
|
||||||
|
label: 'Availability',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AgentProfileView() {
|
export default function AgentProfileView() {
|
||||||
@@ -84,6 +86,7 @@ export default function AgentProfileView() {
|
|||||||
const [profileCardData, setProfileCardData] = useState<ProfileCardData>(defaultProfileCardData);
|
const [profileCardData, setProfileCardData] = useState<ProfileCardData>(defaultProfileCardData);
|
||||||
const [contactInfoData, setContactInfoData] = useState<ContactInfoData>(defaultContactInfoData);
|
const [contactInfoData, setContactInfoData] = useState<ContactInfoData>(defaultContactInfoData);
|
||||||
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
||||||
|
const [workEnvironmentData, setWorkEnvironmentData] = useState<WorkEnvironmentData>(defaultWorkEnvironmentData);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [imageError, setImageError] = useState(false);
|
const [imageError, setImageError] = useState(false);
|
||||||
@@ -143,6 +146,10 @@ export default function AgentProfileView() {
|
|||||||
const mappedAvailability = mapFieldValuesToAvailability(fieldValuesResponse.fieldValues);
|
const mappedAvailability = mapFieldValuesToAvailability(fieldValuesResponse.fieldValues);
|
||||||
setAvailabilityData(mappedAvailability);
|
setAvailabilityData(mappedAvailability);
|
||||||
|
|
||||||
|
// Map field values to work environment data
|
||||||
|
const mappedWorkEnv = mapFieldValuesToWorkEnvironment(fieldValuesResponse.fieldValues);
|
||||||
|
setWorkEnvironmentData(mappedWorkEnv);
|
||||||
|
|
||||||
// Map testimonials for TestimonialsSection
|
// Map testimonials for TestimonialsSection
|
||||||
setTestimonials(
|
setTestimonials(
|
||||||
testimonialsData.map((t) => ({
|
testimonialsData.map((t) => ({
|
||||||
@@ -380,11 +387,11 @@ export default function AgentProfileView() {
|
|||||||
{/* Info Cards Section */}
|
{/* Info Cards Section */}
|
||||||
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6">
|
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6">
|
||||||
<InfoCard
|
<InfoCard
|
||||||
title="Availability"
|
title={availabilityData.label}
|
||||||
icon={
|
icon={
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icons/availability-clock-icon.svg"
|
src="/assets/icons/availability-clock-icon.svg"
|
||||||
alt="Availability"
|
alt={availabilityData.label}
|
||||||
width={28}
|
width={28}
|
||||||
height={31}
|
height={31}
|
||||||
/>
|
/>
|
||||||
@@ -406,18 +413,20 @@ export default function AgentProfileView() {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<InfoCard
|
{workEnvironmentData.content && (
|
||||||
title=""
|
<InfoCard
|
||||||
icon={
|
title={workEnvironmentData.label}
|
||||||
<Image
|
icon={
|
||||||
src="/assets/icons/work-environment-icon.svg"
|
<Image
|
||||||
alt="Work environment"
|
src="/assets/icons/work-environment-icon.svg"
|
||||||
width={28}
|
alt={workEnvironmentData.label}
|
||||||
height={28}
|
width={28}
|
||||||
/>
|
height={28}
|
||||||
}
|
/>
|
||||||
content={<p className="font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]">{mockData.preferredWorkEnvironment}</p>}
|
}
|
||||||
/>
|
content={<p className="font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]">{workEnvironmentData.content}</p>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{testimonials.length > 0 && (
|
{testimonials.length > 0 && (
|
||||||
<InfoCard
|
<InfoCard
|
||||||
title=""
|
title=""
|
||||||
|
|||||||
@@ -219,60 +219,83 @@ export interface SpecializationFieldsData {
|
|||||||
export interface AvailabilityData {
|
export interface AvailabilityData {
|
||||||
type: string;
|
type: string;
|
||||||
schedule: string[];
|
schedule: string[];
|
||||||
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map availability option values to human-readable labels
|
// Work environment data structure
|
||||||
const availabilityLabels: Record<string, string> = {
|
export interface WorkEnvironmentData {
|
||||||
'mf_9_5': 'Monday - Friday, 9 AM - 5 PM',
|
label: string;
|
||||||
'mf_8_6': 'Monday - Friday, 8 AM - 6 PM',
|
content: string;
|
||||||
'weekends': 'Weekends',
|
}
|
||||||
'evenings': 'Evenings',
|
|
||||||
'flexible': 'Flexible Schedule',
|
/**
|
||||||
'full_time': 'Full-time',
|
* Maps field values to work environment data (label + content)
|
||||||
'part_time': 'Part-time',
|
*/
|
||||||
'24_7': '24/7 Available',
|
export function mapFieldValuesToWorkEnvironment(fieldValues: FieldValueResponse[]): WorkEnvironmentData {
|
||||||
'by_appointment': 'By Appointment Only',
|
const field = fieldValues.find(f => f.fieldSlug === 'preferred_work_environment');
|
||||||
'monday': 'Monday',
|
return {
|
||||||
'tuesday': 'Tuesday',
|
label: field?.fieldName || 'Preferred Work Environment',
|
||||||
'wednesday': 'Wednesday',
|
content: (field?.value as string | undefined)?.trim() || '',
|
||||||
'thursday': 'Thursday',
|
};
|
||||||
'friday': 'Friday',
|
}
|
||||||
'saturday': 'Saturday',
|
|
||||||
'sunday': 'Sunday',
|
// Type-determining values (business hours → Full-time, etc.)
|
||||||
};
|
const fullTimeKeywords = ['full_time', 'mf_9_5', 'mf_8_6', 'full time', '9-5', '9 am', '9am'];
|
||||||
|
const partTimeKeywords = ['part_time', 'part time'];
|
||||||
|
const flexibleKeywords = ['flexible'];
|
||||||
|
|
||||||
|
function matchesKeyword(value: string, keywords: string[]): boolean {
|
||||||
|
const normalized = value.toLowerCase();
|
||||||
|
return keywords.some(kw => normalized.includes(kw));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps field values to availability data
|
* Maps field values to availability data
|
||||||
|
* Uses backend-resolved `valueLabel` and deduplicates by normalized label.
|
||||||
*/
|
*/
|
||||||
export function mapFieldValuesToAvailability(fieldValues: FieldValueResponse[]): AvailabilityData {
|
export function mapFieldValuesToAvailability(fieldValues: FieldValueResponse[]): AvailabilityData {
|
||||||
const availabilityValues = getFieldValue(fieldValues, 'availability') as string[] | undefined;
|
const field = fieldValues.find(f => f.fieldSlug === 'availability');
|
||||||
|
const label = field?.fieldName || 'Availability';
|
||||||
|
|
||||||
if (!availabilityValues || availabilityValues.length === 0) {
|
const rawValues = (field?.value as string[] | undefined) || [];
|
||||||
return {
|
const labelValues = (field?.valueLabel as string[] | undefined) || [];
|
||||||
type: '',
|
|
||||||
schedule: [],
|
if (rawValues.length === 0) {
|
||||||
};
|
return { type: '', schedule: [], label };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map values to human-readable labels
|
// Prefer backend-resolved labels, fall back to raw value with snake_case formatting
|
||||||
const schedule = availabilityValues.map(val =>
|
const resolvedLabels = rawValues.map((val, i) => {
|
||||||
availabilityLabels[val] || val.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
|
const fromBackend = labelValues[i];
|
||||||
);
|
if (fromBackend && typeof fromBackend === 'string' && fromBackend.trim()) {
|
||||||
|
return fromBackend.trim();
|
||||||
|
}
|
||||||
|
return val.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
||||||
|
});
|
||||||
|
|
||||||
// Determine type based on values
|
// Deduplicate by normalized comparison (case/whitespace insensitive)
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const schedule: string[] = [];
|
||||||
|
for (const item of resolvedLabels) {
|
||||||
|
const key = item.toLowerCase().replace(/[\s,.-]+/g, '');
|
||||||
|
if (!seen.has(key)) {
|
||||||
|
seen.add(key);
|
||||||
|
schedule.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine availability type based on values OR resolved labels
|
||||||
|
const allValues = [...rawValues.map(v => String(v)), ...resolvedLabels];
|
||||||
let type = 'Available';
|
let type = 'Available';
|
||||||
if (availabilityValues.includes('full_time') || availabilityValues.includes('mf_9_5') || availabilityValues.includes('mf_8_6')) {
|
if (allValues.some(v => matchesKeyword(v, fullTimeKeywords))) {
|
||||||
type = 'Full-time';
|
type = 'Full-time';
|
||||||
} else if (availabilityValues.includes('part_time')) {
|
} else if (allValues.some(v => matchesKeyword(v, partTimeKeywords))) {
|
||||||
type = 'Part-time';
|
type = 'Part-time';
|
||||||
} else if (availabilityValues.includes('flexible')) {
|
} else if (allValues.some(v => matchesKeyword(v, flexibleKeywords))) {
|
||||||
type = 'Flexible';
|
type = 'Flexible';
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { type, schedule, label };
|
||||||
type,
|
|
||||||
schedule,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user