/opt/canhelp/apps/web/src/components
NameSizeModeActions
AdminRolePreview.tsx49280644editdlrm
BecomeSpecialistButton.tsx7020644editdlrm
CategoryIcon.tsx15760644editdlrm
FaqAccordion.tsx15000644editdlrm
FavoriteSpecialistButton.tsx14640644editdlrm
Footer.tsx30190644editdlrm
Header.tsx304790644editdlrm
HeroFloatingIcons.tsx43780644editdlrm
HeroSearch.tsx16110644editdlrm
HomeCategoryGrid.tsx46250644editdlrm
HomeCityCloud.tsx17900644editdlrm
HomeCityPicker.tsx54900644editdlrm
HomeSectionArt.tsx59120644editdlrm
LanguageSwitcher.tsx24380644editdlrm
PlanBadge.tsx31000644editdlrm
PromoCountdown.tsx34830644editdlrm
ScrollToTopButton.tsx8180644editdlrm
Section.tsx2520644editdlrm
ShareButton.tsx10410644editdlrm
SocialIcons.tsx68710644editdlrm
SpecialistsFiltersClient.tsx64610644editdlrm
SpecialistTaskCard.tsx28370644editdlrm
SupportModal.tsx61950644editdlrm
TaskCategoryPicker.tsx111170644editdlrm
TaskLocationPicker.tsx110380644editdlrm
Turnstile.tsx6510644editdlrm
UpgradePrompt.tsx34270644editdlrm
UsageMeter.tsx16020644editdlrm
Edit: /opt/canhelp/apps/web/src/components/TaskLocationPicker.tsx (11038B)
'use client' import { useRef, useState, useEffect } from 'react' import { useLocale } from '@/context/locale' interface LocationNode { id: number slug: string names: Record children?: LocationNode[] } interface Props { locations: LocationNode[] value: string onChange: (slug: string) => void anyLabel: string allLabel: string } export function TaskLocationPicker({ locations, value, onChange, anyLabel, allLabel }: Props) { const [open, setOpen] = useState(false) const [expandedCity, setExpandedCity] = useState(null) const [search, setSearch] = useState('') const ref = useRef(null) const { locale } = useLocale() useEffect(() => { function handleClick(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { setOpen(false) setExpandedCity(null) } } document.addEventListener('mousedown', handleClick) return () => document.removeEventListener('mousedown', handleClick) }, []) // Auto-expand city of currently selected district useEffect(() => { if (!value) { setExpandedCity(null); return } for (const city of locations) { if (city.slug === value) return // city itself selected — no need to expand for (const d of city.children ?? []) { if (d.slug === value) { setExpandedCity(city.slug); return } } } }, [value, locations]) let selectedLabel = anyLabel outer: for (const city of locations) { if (city.slug === value) { selectedLabel = city.names[locale] ?? city.names.el; break } for (const d of city.children ?? []) { if (d.slug === value) { selectedLabel = d.names[locale] ?? d.names.el; break outer } } } function handleClose() { setOpen(false) setExpandedCity(null) setSearch('') } const q = search.toLowerCase() const filteredLocations = q ? locations.filter( (city) => (city.names[locale] ?? city.names.el).toLowerCase().includes(q) || (city.children ?? []).some((d) => (d.names[locale] ?? d.names.el).toLowerCase().includes(q)) ) : locations return (
{open && (
{/* Search input */}
setSearch(e.target.value)} className="w-full pl-8 pr-7 py-1.5 text-sm border border-gray-200 rounded-md focus:outline-none focus:border-green-400" placeholder="Поиск..." onClick={(e) => e.stopPropagation()} /> {search && ( )}
{/* "Any location" row */} {filteredLocations.map((city) => { const cityName = city.names[locale] ?? city.names.el const districts = city.children ?? [] const matchingDistricts = q ? districts.filter((d) => (d.names[locale] ?? d.names.el).toLowerCase().includes(q)) : districts const hasDistricts = districts.length > 0 const isCitySelected = value === city.slug const isExpanded = expandedCity === city.slug || (!!q && matchingDistricts.length > 0 && !cityName.toLowerCase().includes(q)) return (
{/* City row */} {/* Districts (expanded) */} {hasDistricts && isExpanded && (
{/* City-wide option — only show when city name matches or no search */} {(!q || cityName.toLowerCase().includes(q)) && ( )} {matchingDistricts.map((d) => { const dName = d.names[locale] ?? d.names.el const isSelected = value === d.slug return ( ) })}
)}
) })}
)}
) }