import { Search, Plus, X, Upload, FileDown, ChevronDown, Check, MapPin, Star } from 'lucide-react'
import { getCategoryIcon } from '../shared/categoryIcons'
import Tooltip from '../shared/Tooltip'
import type { SidebarState } from './usePlacesSidebar'
export function PlacesDropOverlay({ t }: SidebarState) {
return (
{canEditPlaces &&
{t('places.addPlace')}
}
{canEditPlaces && <>
setFileImportOpen(true)}
className="border border-dashed border-edge text-content-faint"
style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
flex: 1, padding: '5px 12px', borderRadius: 8,
background: 'none', fontSize: 'calc(11px * var(--fs-scale-caption, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit',
}}
>
{t('places.importFile')}
setListImportOpen(true)}
className="border border-dashed border-edge text-content-faint"
style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
flex: 1, padding: '5px 12px', borderRadius: 8,
background: 'none', fontSize: 'calc(11px * var(--fs-scale-caption, 1))', fontWeight: 500,
cursor: 'pointer', fontFamily: 'inherit',
}}
>
{t(hasMultipleListImportProviders ? 'places.importList' : 'places.importGoogleList')}
>}
{/* Filter-Tabs */}
{(() => {
const baseFiltered = places.filter(p => {
if (categoryFilters.size > 0) {
if (p.category_id == null) {
if (!categoryFilters.has('uncategorized')) return false
} else if (!categoryFilters.has(String(p.category_id))) return false
}
if (search && !p.name.toLowerCase().includes(search.toLowerCase()) &&
!(p.address || '').toLowerCase().includes(search.toLowerCase())) return false
return true
})
const counts = {
all: baseFiltered.length,
unplanned: baseFiltered.filter(p => !plannedIds.has(p.id)).length,
planned: baseFiltered.filter(p => plannedIds.has(p.id)).length,
tracks: baseFiltered.filter(p => p.route_geometry).length,
}
const tabs = ([
{ id: 'all', label: t('places.all') },
{ id: 'unplanned', label: t('places.unplanned') },
{ id: 'planned', label: t('places.planned') },
hasTracks ? { id: 'tracks', label: t('places.filterTracks') } : null,
] as const).filter(Boolean) as Array<{ id: 'all' | 'unplanned' | 'planned' | 'tracks'; label: string }>
return (
{tabs.map(f => {
const active = filter === f.id
return (
{ setFilter(f.id); setSelectedIds(new Set()) }}
className={active ? 'bg-accent text-accent-text' : 'bg-surface-card text-content'}
style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 5,
padding: '4px 9px', borderRadius: 99,
fontSize: 'calc(11px * var(--fs-scale-caption, 1))', fontWeight: 500, whiteSpace: 'nowrap',
boxShadow: active ? 'none' : '0 1px 2px rgba(0,0,0,0.06)',
transition: 'background 0.15s, color 0.15s, box-shadow 0.15s',
}}
>
{f.label}
{counts[f.id]}
)
})}
)
})()}
{/* Suchfeld */}
{ setSearch(e.target.value); if (selectMode) setSelectedIds(new Set()) }}
placeholder={t('places.search')}
className="bg-surface-tertiary text-content"
style={{
width: '100%', padding: '7px 30px 7px 30px', borderRadius: 10,
border: 'none', fontSize: 'calc(12px * var(--fs-scale-body, 1))',
outline: 'none', fontFamily: 'inherit', boxSizing: 'border-box',
}}
/>
{search && (
setSearch('')} style={{ position: 'absolute', right: 8, top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', cursor: 'pointer', padding: 0, display: 'flex' }}>
)}
{/* Category multi-select plus the star filter. Rendered even without any
category, because the rating filter does not depend on one and a fresh
trip is exactly where someone collects places to rate. */}
{(() => {
const label = categoryFilters.size === 0
? t('places.allCategories')
: categoryFilters.size === 1
? (categoryFilters.has('uncategorized') ? t('places.noCategory') : categories.find(c => categoryFilters.has(String(c.id)))?.name || t('places.allCategories'))
: `${categoryFilters.size} ${t('places.categoriesSelected')}`
return (
{categories.length > 0 &&
setCatDropOpen(v => !v)} className="bg-surface-card text-content" style={{
flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '6px 10px', borderRadius: 8, border: '1px solid var(--border-primary)',
fontSize: 'calc(12px * var(--fs-scale-body, 1))',
cursor: 'pointer', fontFamily: 'inherit',
}}>
{label}
}
{/* Minimum-stars filter (#1435), same floors as the collections bar. */}
{ setStarDropOpen(v => !v); setCatDropOpen(false) }}
aria-label={t('places.filterByRating')}
aria-expanded={starDropOpen}
className={ratingFilter !== 'all' ? 'text-accent' : 'text-content-faint'}
style={{
height: 30, flexShrink: 0, borderRadius: 8, gap: 3,
padding: ratingFilter === 'all' ? 0 : '0 7px',
width: ratingFilter === 'all' ? 30 : undefined,
border: `1px solid ${ratingFilter !== 'all' ? 'var(--accent)' : 'var(--border-primary)'}`,
background: ratingFilter !== 'all' ? 'color-mix(in srgb, var(--accent) 14%, transparent)' : 'var(--bg-card)',
cursor: 'pointer', fontFamily: 'inherit',
fontSize: 'calc(12px * var(--fs-scale-body, 1))', fontWeight: 600,
display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'background 0.18s, color 0.18s, border-color 0.18s',
}}
>
{ratingFilter !== 'all' && {ratingFilter}+ }
{starDropOpen && (
{(['all', 5, 4, 3, 2, 1] as const).map(opt => {
const active = ratingFilter === opt
return (
{ setRatingFilter(opt as number | 'all'); setStarDropOpen(false) }}
className={`text-content ${active ? 'bg-surface-hover' : 'bg-transparent'}`}
style={{
display: 'flex', alignItems: 'center', gap: 7, width: '100%',
padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-body, 1))',
textAlign: 'left',
}}
>
{opt === 'all'
? {t('common.all')}
: (
<>
{opt}+
>
)}
{active && }
)
})}
)}
{canEditPlaces && (
{ setSelectMode(v => !v); setSelectedIds(new Set()) }}
aria-label={t('common.select')}
aria-pressed={selectMode}
className={selectMode ? 'text-accent' : 'text-content-faint'}
style={{
position: 'relative', width: 30, flexShrink: 0, borderRadius: 8,
border: `1px solid ${selectMode ? 'var(--accent)' : 'var(--border-primary)'}`,
background: selectMode ? 'color-mix(in srgb, var(--accent) 14%, transparent)' : 'var(--bg-card)',
cursor: 'pointer', fontFamily: 'inherit', padding: 0,
transition: 'background 0.18s, color 0.18s, border-color 0.18s',
overflow: 'hidden',
}}
>
)}
{catDropOpen && categories.length > 0 && (
{categories.map(c => {
const active = categoryFilters.has(String(c.id))
const CatIcon = getCategoryIcon(c.icon)
return (
toggleCategoryFilter(String(c.id))} className={`text-content ${active ? 'bg-surface-hover' : 'bg-transparent'}`} style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-body, 1))',
textAlign: 'left',
}}>
{active && }
{c.name}
)
})}
{places.some(p => p.category_id == null) && (() => {
const active = categoryFilters.has('uncategorized')
return (
toggleCategoryFilter('uncategorized')} className={`text-content-muted ${active ? 'bg-surface-hover' : 'bg-transparent'}`} style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 'calc(12px * var(--fs-scale-body, 1))',
textAlign: 'left', borderTop: '1px solid var(--border-faint)', marginTop: 2,
}}>
{active && }
{t('places.noCategory')}
)
})()}
{categoryFilters.size > 0 && (
setCategoryFilters(new Set())} className="bg-transparent text-content-faint" style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4,
width: '100%', padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer',
fontFamily: 'inherit', fontSize: 'calc(11px * var(--fs-scale-caption, 1))',
marginTop: 2, borderTop: '1px solid var(--border-faint)',
}}>
{t('places.clearFilter')}
)}
)}
)
})()}
)
}