diff --git a/frontend/src/index.css b/frontend/src/index.css index dcbf353..7c704de 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -261,7 +261,28 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; } .badge-unusable { background: var(--danger); } /* ── Filter chips ──────────────────────────────────────────── */ -.chip-bar { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; align-items: center; } +.filter-section { margin-bottom: 10px; } +.filter-section-header { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border-radius: 8px; + background: var(--card-bg); + border: 1px solid var(--card-border); + cursor: pointer; + user-select: none; + font-size: 12px; + color: var(--text-mid); + transition: border-color .12s; +} +.filter-section-header:hover { border-color: var(--gold); } +.filter-section-label { font-weight: 600; color: var(--text-dark); white-space: nowrap; } +.filter-section-summary { flex: 1; color: var(--gold); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.filter-section-summary.muted { color: var(--text-mid); font-weight: 400; } +.filter-section-chevron { flex-shrink: 0; transition: transform .18s; } +.filter-section-chevron.open { transform: rotate(180deg); } +.chip-bar { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0 4px; align-items: center; } .chip { display: inline-flex; align-items: center; diff --git a/frontend/src/pages/Summary.tsx b/frontend/src/pages/Summary.tsx index 9dee753..8b25261 100644 --- a/frontend/src/pages/Summary.tsx +++ b/frontend/src/pages/Summary.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { Plus, RefreshCw, Camera, BedDouble, Layers } from 'lucide-react' +import { Plus, RefreshCw, Camera, BedDouble, Layers, ChevronDown } from 'lucide-react' import type { Task, Category, Location, AppConfig, TaskStatus } from '../types' import { STATUS_LABELS, can } from '../types' import { fetchTasks, fetchLocations, fetchConfig, fetchOccupancy } from '../api' @@ -27,6 +27,9 @@ export default function Summary() { const [roomsView, setRoomsView] = useState(false) const [groupByLocation, setGroupByLocation] = useState(false) + const [statusOpen, setStatusOpen] = useState(false) + const [filtersOpen, setFiltersOpen] = useState(false) + const [showNew, setShowNew] = useState(false) const [openTask, setOpenTask] = useState(null) @@ -118,42 +121,68 @@ export default function Summary() { {error &&
{error}
} -
- - {OPEN_STATUSES.map(s => ( - - ))} +
+
setStatusOpen(v => !v)}> + Status + + {statusFilter === null ? 'All open' : `${STATUS_LABELS[statusFilter]}${counts[statusFilter] ? ` (${counts[statusFilter]})` : ''}`} + + +
+ {statusOpen && ( +
+ + {OPEN_STATUSES.map(s => ( + + ))} +
+ )}
-
- {/* Category chips hidden while in rooms-view (rooms filter is the active "category") */} - {!roomsView && categories.map(c => ( - - ))} - - {roomsCategoryExists && ( - +
+
setFiltersOpen(v => !v)}> + Filters + + {[ + !roomsView && categoryFilter ? categories.find(c => c.id === categoryFilter)?.name : null, + roomsView ? 'Rooms view' : null, + mineOnly ? 'Mine only' : null, + groupByLocation ? 'Grouped' : null, + ].filter(Boolean).join(' · ') || 'All categories'} + + +
+ {filtersOpen && ( +
+ {!roomsView && categories.map(c => ( + + ))} + + {roomsCategoryExists && ( + + )} + +
)} -
{tasks.length === 0 && !loading && (