mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 06:41:46 +00:00
feat: add uncategorized filter option to category dropdown
Add a "No Category" option to the category filter dropdown in the places sidebar, allowing users to filter for places without an assigned category. The filter is synced with the map view. Closes #607
This commit is contained in:
@@ -147,7 +147,11 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
|
|||||||
|
|
||||||
const filtered = useMemo(() => places.filter(p => {
|
const filtered = useMemo(() => places.filter(p => {
|
||||||
if (filter === 'unplanned' && plannedIds.has(p.id)) return false
|
if (filter === 'unplanned' && plannedIds.has(p.id)) return false
|
||||||
if (categoryFilters.size > 0 && !categoryFilters.has(String(p.category_id))) return false
|
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()) &&
|
if (search && !p.name.toLowerCase().includes(search.toLowerCase()) &&
|
||||||
!(p.address || '').toLowerCase().includes(search.toLowerCase())) return false
|
!(p.address || '').toLowerCase().includes(search.toLowerCase())) return false
|
||||||
return true
|
return true
|
||||||
@@ -257,7 +261,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
|
|||||||
const label = categoryFilters.size === 0
|
const label = categoryFilters.size === 0
|
||||||
? t('places.allCategories')
|
? t('places.allCategories')
|
||||||
: categoryFilters.size === 1
|
: categoryFilters.size === 1
|
||||||
? categories.find(c => categoryFilters.has(String(c.id)))?.name || t('places.allCategories')
|
? (categoryFilters.has('uncategorized') ? t('places.noCategory') : categories.find(c => categoryFilters.has(String(c.id)))?.name || t('places.allCategories'))
|
||||||
: `${categoryFilters.size} ${t('places.categoriesSelected')}`
|
: `${categoryFilters.size} ${t('places.categoriesSelected')}`
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: 6, position: 'relative' }}>
|
<div style={{ marginTop: 6, position: 'relative' }}>
|
||||||
@@ -300,6 +304,29 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
{places.some(p => p.category_id == null) && (() => {
|
||||||
|
const active = categoryFilters.has('uncategorized')
|
||||||
|
return (
|
||||||
|
<button onClick={() => toggleCategoryFilter('uncategorized')} style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 8, width: '100%',
|
||||||
|
padding: '6px 10px', borderRadius: 6, border: 'none', cursor: 'pointer',
|
||||||
|
background: active ? 'var(--bg-hover)' : 'transparent',
|
||||||
|
fontFamily: 'inherit', fontSize: 12, color: 'var(--text-muted)',
|
||||||
|
textAlign: 'left', borderTop: '1px solid var(--border-faint)', marginTop: 2,
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: 16, height: 16, borderRadius: 4, flexShrink: 0,
|
||||||
|
border: active ? 'none' : '1.5px solid var(--border-primary)',
|
||||||
|
background: active ? 'var(--text-faint)' : 'transparent',
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
}}>
|
||||||
|
{active && <Check size={10} strokeWidth={3} color="white" />}
|
||||||
|
</div>
|
||||||
|
<MapPin size={12} strokeWidth={2} color="var(--text-faint)" />
|
||||||
|
<span style={{ flex: 1 }}>{t('places.noCategory')}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
{categoryFilters.size > 0 && (
|
{categoryFilters.size > 0 && (
|
||||||
<button onClick={() => { setCategoryFiltersLocal(new Set()); onCategoryFilterChange?.(new Set()) }} style={{
|
<button onClick={() => { setCategoryFiltersLocal(new Set()); onCategoryFilterChange?.(new Set()) }} style={{
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4,
|
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4,
|
||||||
|
|||||||
@@ -246,7 +246,11 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
|||||||
|
|
||||||
return places.filter(p => {
|
return places.filter(p => {
|
||||||
if (!p.lat || !p.lng) return false
|
if (!p.lat || !p.lng) return false
|
||||||
if (mapCategoryFilter.size > 0 && !mapCategoryFilter.has(String(p.category_id))) return false
|
if (mapCategoryFilter.size > 0) {
|
||||||
|
if (p.category_id == null) {
|
||||||
|
if (!mapCategoryFilter.has('uncategorized')) return false
|
||||||
|
} else if (!mapCategoryFilter.has(String(p.category_id))) return false
|
||||||
|
}
|
||||||
if (hiddenPlaceIds.has(p.id)) return false
|
if (hiddenPlaceIds.has(p.id)) return false
|
||||||
if (plannedIds && plannedIds.has(p.id)) return false
|
if (plannedIds && plannedIds.has(p.id)) return false
|
||||||
return true
|
return true
|
||||||
|
|||||||
Reference in New Issue
Block a user