diff --git a/frontend/src/components/layout/LeftSidebar.tsx b/frontend/src/components/layout/LeftSidebar.tsx index dd5a210..f87f3e0 100644 --- a/frontend/src/components/layout/LeftSidebar.tsx +++ b/frontend/src/components/layout/LeftSidebar.tsx @@ -10,6 +10,7 @@ import { HardDrive, RefreshCw, Copy, + Tag as TagIcon, } from 'lucide-react' import clsx from 'clsx' import { sourceFolders, library, photos as photosApi, type FolderTreeNode } from '../../services/api' @@ -19,6 +20,7 @@ import { useFilterStore } from '../../store/filterStore' import { HeapsPanel } from '../heaps/HeapsPanel' import { PHOTO_DRAG_MIME } from '../timeline/PhotoThumbnail' import { useFolderTreeQuery } from '../../hooks/useFolderTreeQuery' +import { useTagsQuery } from '../../hooks/useTagsQuery' interface TreeItem { id: string @@ -44,8 +46,11 @@ export function LeftSidebar() { const setFlag = useFilterStore((s) => s.setFlag) const setFolderId = useFilterStore((s) => s.setFolderId) const setDuplicates = useFilterStore((s) => s.setDuplicates) + const setTagIds = useFilterStore((s) => s.setTagIds) const filterFolderId = useFilterStore((s) => s.folderId) const filterDuplicates = useFilterStore((s) => s.duplicates) + const filterTagIds = useFilterStore((s) => s.tagIds) + const { data: allTags = [] } = useTagsQuery() const [dropTargetId, setDropTargetId] = useState(null) // Bulk discard mutation for the drag-onto-Discarded interaction. @@ -138,6 +143,14 @@ export function LeftSidebar() { setDuplicates(true) break default: + if (id.startsWith('tag-')) { + // Tag rows: filter to that single tag. Multi-tag filtering is + // available via the FilterBar. + const tagId = id.slice('tag-'.length) + clearAllFilters() + setTagIds([tagId]) + return + } if (id.startsWith('folder-')) { // Folder rows: filter to that folder, clear other filters that // would compete (heap, discarded, etc.) so the user sees what they @@ -219,6 +232,17 @@ export function LeftSidebar() { { id: 'rated', label: 'Rated', icon: , count: 0 }, { id: 'duplicates', label: 'Duplicates', icon: , count: 0 }, { id: 'discarded', label: 'Discarded', icon: , count: 0 }, + { + id: 'tags', + label: 'Tags', + icon: , + children: allTags.map((tag) => ({ + id: `tag-${tag.id}`, + label: tag.name, + icon: , + count: tag.photo_count, + })), + }, ], }, { @@ -237,6 +261,10 @@ export function LeftSidebar() { if (id.startsWith('folder-')) { return filterFolderId === id.slice('folder-'.length) } + if (id.startsWith('tag-')) { + const tagId = id.slice('tag-'.length) + return filterTagIds.length === 1 && filterTagIds[0] === tagId + } if (id === 'all-photos') { return filterFolderId === null && selectedItem === 'all-photos' }