From 2ec629cb5b7e333470d70e6967bd07b614094b44 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Sat, 4 Jul 2026 18:23:24 +0000 Subject: [PATCH] Controls: separate Categories filter toggle, standalone buttons, full-width category headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FilterBar split into CategoryFilterBar / StatesFilterBar / StatFilterBar — each independently toggleable from the controls bar (Categories Filter / States Filter / Stats Filter), visibility persisted to localStorage - Control buttons are now individual rounded buttons with a 6px gap (removed the merged/segmented styling that left a missing border) - Category group headers span the full width with top/bottom borders only; 37px side padding keeps the header text aligned with room card content Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/FilterBar.tsx | 36 ++++++++++++++++----------- frontend/src/index.css | 35 ++++---------------------- frontend/src/pages/Planner.tsx | 21 +++++++++++----- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/frontend/src/components/FilterBar.tsx b/frontend/src/components/FilterBar.tsx index c572609..67b8e28 100644 --- a/frontend/src/components/FilterBar.tsx +++ b/frontend/src/components/FilterBar.tsx @@ -4,15 +4,6 @@ import type { FilterMode, FilterState, StatFilters, StatFilterMode, SemanticFlow import { SEMANTIC_FLOW_MAP } from '../types' import type { RoomData } from '../types' -interface FilterBarProps { - filters: FilterState - categories: Array<{ id: string; name: string }> - rooms: RoomData[] - viewDate: string - onToggleCategory: (id: string) => void - onToggleFlow: (flow: SemanticFlowKey) => void -} - const SEMANTIC_KEYS: SemanticFlowKey[] = ['arriving', 'departing', 'back-to-back', 'staying', 'vacant', 'blocked'] const SEMANTIC_LABELS: Record = { arriving: 'Arriving', departing: 'Departing', 'back-to-back': 'B2B', @@ -34,7 +25,14 @@ function countBySemantic(rooms: RoomData[], key: SemanticFlowKey) { return rooms.filter(r => flowTypes.includes(r.flow_type)).length } -export function FilterBar({ filters, categories, rooms, viewDate, onToggleCategory, onToggleFlow }: FilterBarProps) { +interface CategoryFilterBarProps { + filters: FilterState + categories: Array<{ id: string; name: string }> + rooms: RoomData[] + onToggleCategory: (id: string) => void +} + +export function CategoryFilterBar({ filters, categories, rooms, onToggleCategory }: CategoryFilterBarProps) { const scrollRef = useRef(null) const scroll = (dir: 'left' | 'right') => { @@ -43,7 +41,6 @@ export function FilterBar({ filters, categories, rooms, viewDate, onToggleCatego return (
- {/* Category filter row */}
+
+ ) +} - {/* Flow type filter row */} -
+interface StatesFilterBarProps { + filters: FilterState + rooms: RoomData[] + onToggleFlow: (flow: SemanticFlowKey) => void +} + +export function StatesFilterBar({ filters, rooms, onToggleFlow }: StatesFilterBarProps) { + return ( +
+
{SEMANTIC_KEYS.map(key => { const mode = filters.flowTypes[key] ?? 'off' @@ -111,7 +119,7 @@ const STAT_LABELS: Record = { export function StatFilterBar({ statFilters, onChange }: StatFilterBarProps) { return ( -
+
{(Object.keys(statFilters) as Array).map(key => { diff --git a/frontend/src/index.css b/frontend/src/index.css index d91fac9..b292cca 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -215,7 +215,7 @@ html, body, #root { } .control-group { display: flex; - gap: 2px; + gap: 6px; } .control-btn { display: flex; @@ -238,9 +238,6 @@ html, body, #root { color: var(--app-primary); font-weight: 600; } -.control-group .control-btn:not(:first-child) { border-left: none; border-radius: 0 6px 6px 0; } -.control-group .control-btn:first-child:not(:last-child) { border-radius: 6px 0 0 6px; } - /* ── Filter bars ─────────────────────────────────────────────── */ .filter-section { background: var(--card-bg); @@ -347,38 +344,16 @@ html, body, #root { align-items: center; gap: 8px; background: #e5e7eb; - border: 2px solid #d1d5db; - border-radius: 8px; - margin: 8px 23px 5px; - padding: 6px 12px; + border-top: 2px solid #d1d5db; + border-bottom: 2px solid #d1d5db; + margin: 8px 0 5px; + padding: 6px 37px; cursor: pointer; user-select: none; position: sticky; top: 0; z-index: 10; } -.category-header::before, -.category-header::after { - content: ''; - position: absolute; - top: -2px; - bottom: -2px; - width: 16px; - background: #e5e7eb; - border-top: 2px solid #d1d5db; - border-bottom: 2px solid #d1d5db; - pointer-events: none; -} -.category-header::before { - left: -16px; - border-right: 2px solid #d1d5db; - border-radius: 0 6px 6px 0; -} -.category-header::after { - right: -16px; - border-left: 2px solid #d1d5db; - border-radius: 6px 0 0 6px; -} .category-name { flex: 1; font-size: 12px; diff --git a/frontend/src/pages/Planner.tsx b/frontend/src/pages/Planner.tsx index 6c27af6..0577995 100644 --- a/frontend/src/pages/Planner.tsx +++ b/frontend/src/pages/Planner.tsx @@ -1,11 +1,11 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react' -import { RefreshCw, Settings, List, LayoutList, Filter, BarChart2, RotateCcw } from 'lucide-react' +import { RefreshCw, Settings, List, LayoutList, Filter, BarChart2, RotateCcw, Tag } from 'lucide-react' import type { RoomData, AppConfig, Category, FilterState, StatFilters, SemanticFlowKey, ActivityEntry } from '../types' import { SEMANTIC_FLOW_MAP } from '../types' import { useAuth } from '../components/AuthGate' import CategoryGroup from '../components/CategoryGroup' import RoomCard from '../components/RoomCard' -import { FilterBar, StatFilterBar } from '../components/FilterBar' +import { CategoryFilterBar, StatesFilterBar, StatFilterBar } from '../components/FilterBar' import ActivityPanel from '../components/ActivityPanel' import RoomModal from '../components/RoomModal' import CheckoutNotification, { useToasts } from '../components/CheckoutNotification' @@ -49,6 +49,9 @@ export default function Planner() { const [viewMode, setViewMode] = useState<'flat' | 'grouped'>(() => (localStorage.getItem('rp-view-mode') ?? 'grouped') as 'flat' | 'grouped' ) + const [categoriesFilterVisible, setCategoriesFilterVisible] = useState(() => + JSON.parse(localStorage.getItem('rp-categories-visible') ?? 'true') + ) const [statesFilterVisible, setStatesFilterVisible] = useState(() => JSON.parse(localStorage.getItem('rp-states-visible') ?? 'true') ) @@ -57,6 +60,7 @@ export default function Planner() { ) useEffect(() => { localStorage.setItem('rp-controls-visible', JSON.stringify(controlsVisible)) }, [controlsVisible]) useEffect(() => { localStorage.setItem('rp-view-mode', viewMode) }, [viewMode]) + useEffect(() => { localStorage.setItem('rp-categories-visible', JSON.stringify(categoriesFilterVisible)) }, [categoriesFilterVisible]) useEffect(() => { localStorage.setItem('rp-states-visible', JSON.stringify(statesFilterVisible)) }, [statesFilterVisible]) useEffect(() => { localStorage.setItem('rp-stats-visible', JSON.stringify(statsFilterVisible)) }, [statsFilterVisible]) @@ -178,6 +182,7 @@ export default function Planner() { setFilters({ categories: {}, flowTypes: {} }) setStatFilters({ newbookTasks: 'off', cleanDirty: 'off' }) setViewMode('grouped') + setCategoriesFilterVisible(true) setStatesFilterVisible(true) setStatsFilterVisible(true) }, []) @@ -312,6 +317,9 @@ export default function Planner() {
+ @@ -328,16 +336,17 @@ export default function Planner() { )} {/* Filters */} - {statesFilterVisible && ( - )} + {statesFilterVisible && ( + + )} {statsFilterVisible && } {/* Main area */}