import { useState, useEffect, useRef } from 'react'; const EU_SIZES = ['36', '37', '37.5', '38', '38.5', '39', '40', '40.5', '41', '42', '42.5', '43', '44', '44.5', '45', '46', '47', '48']; interface SizePreference { brand_slug: string; size: string; subtypes: { category_slug: string; size: string }[]; } interface BrandResult { name: string; slug: string; count?: number; } interface ModelResult { name: string; slug: string; brand_slug: string; } export default function AdvancedSizesSection() { const [preferences, setPreferences] = useState([]); const [loading, setLoading] = useState(true); const [showBrandSearch, setShowBrandSearch] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [popularBrands, setPopularBrands] = useState([]); const [searching, setSearching] = useState(false); const [expandedBrand, setExpandedBrand] = useState(null); const [subtypeResults, setSubtypeResults] = useState([]); const [loadingSubtypes, setLoadingSubtypes] = useState(false); const debounceRef = useRef>(); useEffect(() => { Promise.all([ fetch('/api/user/size-preferences', { credentials: 'include' }) .then(r => r.json()) .then(data => setPreferences(data.preferences || [])) .catch(() => {}), fetch('/api/search/suggest?popular=1', { credentials: 'include' }) .then(r => r.json()) .then(data => { if (data.brands) { setPopularBrands(data.brands.map((b: any) => ({ name: b.name, slug: b.slug.toLowerCase(), count: b.count, }))); } }) .catch(() => {}), ]).finally(() => setLoading(false)); }, []); // Live search brands as user types useEffect(() => { if (!showBrandSearch) return; if (searchQuery.length < 2) { setSearchResults([]); return; } clearTimeout(debounceRef.current); debounceRef.current = setTimeout(async () => { setSearching(true); try { const res = await fetch(`/api/search/suggest?q=${encodeURIComponent(searchQuery)}`, { credentials: 'include' }); const data = await res.json(); if (data.brands) { setSearchResults( data.brands .map((b: any) => ({ name: b.name, slug: b.slug.toLowerCase(), count: b.count })) .filter((b: any) => !preferences.some(p => p.brand_slug === b.slug)) ); } } catch {} setSearching(false); }, 250); return () => clearTimeout(debounceRef.current); }, [searchQuery, showBrandSearch, preferences]); // Load subtypes when expanding a brand async function loadSubtypes(brandSlug: string) { if (expandedBrand === brandSlug) { setExpandedBrand(null); return; } setExpandedBrand(brandSlug); setLoadingSubtypes(true); try { const res = await fetch(`/api/search/suggest?q=${encodeURIComponent(brandSlug)}`, { credentials: 'include' }); const data = await res.json(); const models = (data.models || []) .filter((m: any) => m.brand_slug === brandSlug) .map((m: any) => ({ name: m.name, slug: m.slug, brand_slug: m.brand_slug })); setSubtypeResults(models); } catch { setSubtypeResults([]); } setLoadingSubtypes(false); } async function addSubtype(brandSlug: string, modelSlug: string, modelName: string) { const pref = preferences.find(p => p.brand_slug === brandSlug); const size = pref?.size || '44'; const categorySlug = `${brandSlug}/${modelSlug}`; const res = await fetch('/api/user/size-preferences', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ brand_slug: brandSlug, category_slug: categorySlug, size }), }); if (res.ok) { setPreferences(preferences.map(p => p.brand_slug === brandSlug ? { ...p, subtypes: [...p.subtypes, { category_slug: categorySlug, size }] } : p )); } } async function addBrand(brandSlug: string) { const size = '44'; const res = await fetch('/api/user/size-preferences', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ brand_slug: brandSlug, size }), }); if (res.ok) { setPreferences([...preferences, { brand_slug: brandSlug, size, subtypes: [] }]); setShowBrandSearch(false); setSearchQuery(''); setSearchResults([]); } } async function updateSize(brandSlug: string, newSize: string, categorySlug?: string) { const res = await fetch('/api/user/size-preferences', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ brand_slug: brandSlug, category_slug: categorySlug || null, size: newSize }), }); if (res.ok) { if (categorySlug) { setPreferences(preferences.map(p => p.brand_slug === brandSlug ? { ...p, subtypes: p.subtypes.map(s => s.category_slug === categorySlug ? { ...s, size: newSize } : s) } : p )); } else { setPreferences(preferences.map(p => p.brand_slug === brandSlug ? { ...p, size: newSize } : p)); } } } async function deleteSize(brandSlug: string, categorySlug?: string) { const res = await fetch('/api/user/size-preferences', { method: 'DELETE', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ brand_slug: brandSlug, category_slug: categorySlug || null }), }); if (res.ok) { if (categorySlug) { setPreferences(preferences.map(p => p.brand_slug === brandSlug ? { ...p, subtypes: p.subtypes.filter(s => s.category_slug !== categorySlug) } : p )); } else { setPreferences(preferences.filter(p => p.brand_slug !== brandSlug)); if (expandedBrand === brandSlug) setExpandedBrand(null); } } } // Brands to show in search: either search results or popular brands (filtered) const displayBrands = searchQuery.length >= 2 ? searchResults : popularBrands.filter(b => !preferences.some(p => p.brand_slug === b.slug)); if (loading) { return
Laden...
; } return (

Geavanceerde maten

Stel per merk (en optioneel per model) je maat in. Dit wordt gebruikt voor alle filters op de site.

{/* Saved preferences */} {preferences.length > 0 && (
{preferences.map(pref => (
{pref.brand_slug}
{/* Saved subtypes */} {pref.subtypes.length > 0 && (
{pref.subtypes.map(subtype => (
{subtype.category_slug.split('/').pop()?.replace(/-/g, ' ')}
))}
)} {/* Subtype picker (expanded) */} {expandedBrand === pref.brand_slug && (

Model toevoegen:

{loadingSubtypes ? (

Laden...

) : subtypeResults.length === 0 ? (

Geen modellen gevonden voor dit merk

) : (
{subtypeResults .filter(m => !pref.subtypes.some(s => s.category_slug === `${pref.brand_slug}/${m.slug}`)) .map(model => ( ))}
)}
)}
))}
)} {/* Add brand */}
{showBrandSearch && (
setSearchQuery(e.target.value)} className="mb-2 w-full rounded border border-border/30 bg-bg-elevated px-3 py-2 text-sm text-text placeholder-text-muted focus:border-accent/50 focus:outline-none" />
{searchQuery.length >= 2 && searching ? (

Zoeken...

) : displayBrands.length === 0 ? (

{searchQuery.length >= 2 ? 'Geen merken gevonden' : 'Typ om te zoeken'}

) : ( <> {searchQuery.length < 2 && (

Populair

)} {displayBrands.map(brand => ( ))} )}
)}

Voor merken die je niet hebt ingesteld, wordt je standaardmaat gebruikt.

); }