feat: Introduce dynamic profile filtering with a new service and update user and agent profiles to use separate first and last name fields.

This commit is contained in:
pradeepkumar
2026-02-01 15:04:51 +05:30
parent 35ae58c0df
commit 09cf44f418
5 changed files with 167 additions and 333 deletions

View File

@@ -2,61 +2,40 @@
import { useState } from 'react';
// Filter options data
export const clientSpecializations = [
'Buyers', 'Sellers', 'Divorce', 'First time buyers', 'First time sellers', 'Estate'
];
export const loanTypes = [
'Conventional', 'FHA', 'VA', 'USDA', 'Downpayment Assistance Programs'
];
export const propertyTypes = [
'SFR', 'Townhome', 'condo', 'Manufactured', 'Apartment / Multifamily',
'Rural', 'Luxury', 'Retirement', 'Investment/ Income Producing',
'Duplex / Tri-plex / 4-plex', 'Historical', 'Mountain Property'
];
export const pricePoints = [
'Under $100K', '$100K - $300K', '$300K - $500K', '$500K - $1M', '$1M and above'
];
export const specialInterests = [
'Workshop / Trade / Craftspace', 'Gardening/Landscape', 'Boating',
'Automotive', 'Hobby farm', 'Home theater'
];
export const popularFilters = [
'Sellers Agent', 'First-Time Buyer', 'Buyers Agent', 'Buyers Agent',
'Buyers Agent', 'First-Time Seller', 'Buyers Agent'
];
export interface FilterState {
clientSpecialization: string[];
loanType: string[];
propertyType: string[];
pricePoint: string[];
specialInterests: string[];
popularFilters: string[];
// Types for dynamic filter data
export interface FilterOption {
label: string;
value: string;
}
export interface FilterField {
slug: string;
name: string;
fieldType: string;
options: FilterOption[];
}
// Dynamic filter state - keys are field slugs
export type FilterState = Record<string, string[]>;
interface FilterModalProps {
isOpen: boolean;
onClose: () => void;
filters: FilterState;
onToggleFilter: (category: string, option: string) => void;
filterFields: FilterField[];
onToggleFilter: (fieldSlug: string, optionValue: string) => void;
onClearAll: () => void;
onApply: () => void;
}
export function FilterModal({ isOpen, onClose, filters, onToggleFilter, onClearAll, onApply }: FilterModalProps) {
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({
popularFilters: true,
clientSpecialization: true,
loanType: true,
propertyType: true,
specialInterests: true,
pricePoint: true,
export function FilterModal({ isOpen, onClose, filters, filterFields, onToggleFilter, onClearAll, onApply }: FilterModalProps) {
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>(() => {
// Initialize all sections as expanded
const initial: Record<string, boolean> = {};
filterFields.forEach(field => {
initial[field.slug] = true;
});
return initial;
});
const toggleSection = (section: string) => {
@@ -84,217 +63,43 @@ export function FilterModal({ isOpen, onClose, filters, onToggleFilter, onClearA
{/* Scrollable Content */}
<div className="overflow-y-auto max-h-[calc(90vh-180px)] px-8 py-5">
{/* Popular Filters */}
<div className="mb-6">
<button
onClick={() => toggleSection('popularFilters')}
className="flex items-center gap-2 font-fractul text-[20px] text-[#00293d] mb-4"
>
Popular Filters
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className={`transition-transform ${expandedSections.popularFilters ? 'rotate-180' : ''}`}>
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
{expandedSections.popularFilters && (
<div className="flex flex-wrap gap-2">
{popularFilters.map((filter, index) => (
<button
key={index}
onClick={() => onToggleFilter('popularFilters', filter)}
className={`px-4 py-2.5 rounded-[15px] border border-[#00293d] font-serif text-[14px] transition-colors ${
filters.popularFilters?.includes(filter)
? 'bg-[#00293d] text-white'
: 'bg-white text-[#00293d] hover:bg-[#00293d]/5'
}`}
>
{filter}
</button>
))}
{filterFields.map((field, index) => (
<div key={field.slug}>
<div className="mb-6">
<button
onClick={() => toggleSection(field.slug)}
className="flex items-center gap-2 font-fractul text-[20px] text-[#00293d] mb-4"
>
{field.name}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className={`transition-transform ${expandedSections[field.slug] ? 'rotate-180' : ''}`}>
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
{expandedSections[field.slug] && (
<div className={`grid ${field.options.length > 6 ? 'grid-cols-3' : 'grid-cols-4'} gap-x-4 gap-y-3`}>
{field.options.map((option) => (
<label key={option.value} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={(filters[field.slug] || []).includes(option.value)}
onChange={() => onToggleFilter(field.slug, option.value)}
className="w-[17px] h-[17px] rounded-[5px] border-[#00293d]/20 text-[#e58625] focus:ring-[#e58625]"
/>
<span className="font-serif text-[15px] text-[#00293d]">{option.label}</span>
</label>
))}
</div>
)}
</div>
)}
</div>
{index < filterFields.length - 1 && <div className="border-t border-[#d9d9d9] my-4" />}
</div>
))}
<div className="border-t border-[#d9d9d9] my-4" />
{/* Client Specialization */}
<div className="mb-6">
<button
onClick={() => toggleSection('clientSpecialization')}
className="flex items-center gap-2 font-fractul text-[20px] text-[#00293d] mb-4"
>
Client Specialization
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className={`transition-transform ${expandedSections.clientSpecialization ? 'rotate-180' : ''}`}>
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
{expandedSections.clientSpecialization && (
<>
<div className="grid grid-cols-4 gap-x-4 gap-y-3">
{clientSpecializations.slice(0, 5).map((option) => (
<label key={option} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={filters.clientSpecialization.includes(option)}
onChange={() => onToggleFilter('clientSpecialization', option)}
className="w-[17px] h-[17px] rounded-[5px] border-[#00293d]/20 text-[#e58625] focus:ring-[#e58625]"
/>
<span className="font-serif text-[15px] text-[#00293d]">{option}</span>
</label>
))}
</div>
<button className="mt-3 font-serif text-[15px] text-[#00293d] font-light flex items-center gap-1">
Show More
<svg width="10" height="10" viewBox="0 0 12 12" fill="none">
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</>
)}
</div>
<div className="border-t border-[#d9d9d9] my-4" />
{/* Loan Type */}
<div className="mb-6">
<button
onClick={() => toggleSection('loanType')}
className="flex items-center gap-2 font-fractul text-[20px] text-[#00293d] mb-4"
>
Loan Type
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className={`transition-transform ${expandedSections.loanType ? 'rotate-180' : ''}`}>
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
{expandedSections.loanType && (
<>
<div className="grid grid-cols-4 gap-x-4 gap-y-3">
{loanTypes.map((option) => (
<label key={option} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={filters.loanType.includes(option)}
onChange={() => onToggleFilter('loanType', option)}
className="w-[17px] h-[17px] rounded-[5px] border-[#00293d]/20 text-[#e58625] focus:ring-[#e58625]"
/>
<span className="font-serif text-[15px] text-[#00293d]">{option}</span>
</label>
))}
</div>
<button className="mt-3 font-serif text-[15px] text-[#00293d] font-light flex items-center gap-1">
Show More
<svg width="10" height="10" viewBox="0 0 12 12" fill="none">
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</>
)}
</div>
<div className="border-t border-[#d9d9d9] my-4" />
{/* Property Type */}
<div className="mb-6">
<button
onClick={() => toggleSection('propertyType')}
className="flex items-center gap-2 font-fractul text-[20px] text-[#00293d] mb-4"
>
Property Type
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className={`transition-transform ${expandedSections.propertyType ? 'rotate-180' : ''}`}>
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
{expandedSections.propertyType && (
<>
<div className="grid grid-cols-3 gap-x-4 gap-y-3">
{propertyTypes.map((option) => (
<label key={option} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={filters.propertyType.includes(option)}
onChange={() => onToggleFilter('propertyType', option)}
className="w-[17px] h-[17px] rounded-[5px] border-[#00293d]/20 text-[#e58625] focus:ring-[#e58625]"
/>
<span className="font-serif text-[15px] text-[#00293d]">{option}</span>
</label>
))}
</div>
<button className="mt-3 font-serif text-[15px] text-[#00293d] font-light flex items-center gap-1">
Show More
<svg width="10" height="10" viewBox="0 0 12 12" fill="none">
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</>
)}
</div>
<div className="border-t border-[#d9d9d9] my-4" />
{/* Special Interests and Hobbies */}
<div className="mb-6">
<button
onClick={() => toggleSection('specialInterests')}
className="flex items-center gap-2 font-fractul text-[20px] text-[#00293d] mb-4"
>
Special Interests and Hobbies
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className={`transition-transform ${expandedSections.specialInterests ? 'rotate-180' : ''}`}>
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
{expandedSections.specialInterests && (
<>
<div className="grid grid-cols-3 gap-x-4 gap-y-3">
{specialInterests.map((option) => (
<label key={option} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={filters.specialInterests?.includes(option) || false}
onChange={() => onToggleFilter('specialInterests', option)}
className="w-[17px] h-[17px] rounded-[5px] border-[#00293d]/20 text-[#e58625] focus:ring-[#e58625]"
/>
<span className="font-serif text-[15px] text-[#00293d]">{option}</span>
</label>
))}
</div>
<button className="mt-3 font-serif text-[15px] text-[#00293d] font-light flex items-center gap-1">
Show More
<svg width="10" height="10" viewBox="0 0 12 12" fill="none">
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</>
)}
</div>
<div className="border-t border-[#d9d9d9] my-4" />
{/* Price Point */}
<div className="mb-6">
<button
onClick={() => toggleSection('pricePoint')}
className="flex items-center gap-2 font-fractul text-[20px] text-[#00293d] mb-4"
>
Price Point
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className={`transition-transform ${expandedSections.pricePoint ? 'rotate-180' : ''}`}>
<path d="M2 4L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
{expandedSections.pricePoint && (
<div className="grid grid-cols-3 gap-x-4 gap-y-3">
{pricePoints.map((option) => (
<label key={option} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={filters.pricePoint.includes(option)}
onChange={() => onToggleFilter('pricePoint', option)}
className="w-[17px] h-[17px] rounded-[5px] border-[#00293d]/20 text-[#e58625] focus:ring-[#e58625]"
/>
<span className="font-serif text-[15px] text-[#00293d]">{option}</span>
</label>
))}
</div>
)}
</div>
{filterFields.length === 0 && (
<div className="text-center py-8">
<p className="font-serif text-[15px] text-[#00293d]/60">Loading filters...</p>
</div>
)}
</div>
{/* Footer */}