diff --git a/backend/app/routers/library.py b/backend/app/routers/library.py index 9ccf783..887b4fe 100644 --- a/backend/app/routers/library.py +++ b/backend/app/routers/library.py @@ -47,50 +47,6 @@ async def trigger_scan(): return {"status": "success", "message": "Library scan started"} -@router.get("/date_buckets") -async def get_date_buckets(db: AsyncSession = Depends(get_db)): - """Aggregate non-discarded photos by year and month based on taken_at. - Used by the LeftSidebar 'By Date' subtree to render a year-then-month - drilldown without fetching every photo. Returns: - [{ year: 2026, count: 5, months: [{ month: 4, count: 5 }] }] - """ - year_expr = func.strftime('%Y', Photo.taken_at) - month_expr = func.strftime('%m', Photo.taken_at) - - result = await db.execute( - select( - year_expr.label('year'), - month_expr.label('month'), - func.count(Photo.id).label('count'), - ) - .where( - Photo.is_discarded == False, # noqa: E712 - Photo.taken_at.is_not(None), - ) - .group_by(year_expr, month_expr) - .order_by(year_expr.desc(), month_expr.desc()) - ) - rows = result.all() - - years: dict[int, dict] = {} - for year_str, month_str, count in rows: - if not year_str or not month_str: - continue - try: - year = int(year_str) - month = int(month_str) - except ValueError: - continue - bucket = years.setdefault( - year, {'year': year, 'count': 0, 'months': []} - ) - bucket['count'] += int(count) - bucket['months'].append({'month': month, 'count': int(count)}) - - # Already ordered by the SQL ORDER BY (newest first), preserve insertion. - return list(years.values()) - - @router.get("/scan/status") async def get_scan_status(db: AsyncSession = Depends(get_db)): """Get current scan status""" diff --git a/frontend/src/components/layout/LeftSidebar.tsx b/frontend/src/components/layout/LeftSidebar.tsx index ca4d6c8..0ff13b1 100644 --- a/frontend/src/components/layout/LeftSidebar.tsx +++ b/frontend/src/components/layout/LeftSidebar.tsx @@ -4,7 +4,6 @@ import { ChevronDown, Folder, Image, - Calendar, Star, Trash2, Plus, @@ -16,12 +15,10 @@ import clsx from 'clsx' import { AddSourceFolderDialog } from '../dialogs/AddSourceFolderDialog' import { sourceFolders, library, photos as photosApi } from '../../services/api' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' -import { format } from 'date-fns' import { toast } from '../ToastContainer' import { useFilterStore } from '../../store/filterStore' import { HeapsPanel } from '../heaps/HeapsPanel' import { PHOTO_DRAG_MIME } from '../timeline/PhotoThumbnail' -import { useDateBucketsQuery } from '../../hooks/useDateBucketsQuery' interface TreeItem { id: string @@ -43,12 +40,7 @@ export function LeftSidebar() { const setRatingMin = useFilterStore((s) => s.setRatingMin) const setFlag = useFilterStore((s) => s.setFlag) const setFolderId = useFilterStore((s) => s.setFolderId) - const setDateFrom = useFilterStore((s) => s.setDateFrom) - const setDateTo = useFilterStore((s) => s.setDateTo) const filterFolderId = useFilterStore((s) => s.folderId) - const filterDateFrom = useFilterStore((s) => s.dateFrom) - const filterDateTo = useFilterStore((s) => s.dateTo) - const { data: dateBuckets = [] } = useDateBucketsQuery() const [dropTargetId, setDropTargetId] = useState(null) // Bulk discard mutation for the drag-onto-Discarded interaction. @@ -122,26 +114,6 @@ export function LeftSidebar() { const folderId = id.slice('folder-'.length) clearAllFilters() setFolderId(folderId) - } else if (id.startsWith('date-year-')) { - const year = parseInt(id.slice('date-year-'.length), 10) - if (Number.isFinite(year)) { - clearAllFilters() - setDateFrom(`${year}-01-01`) - setDateTo(`${year}-12-31`) - } - } else if (id.startsWith('date-month-')) { - // Format: date-month-YYYY-MM - const parts = id.slice('date-month-'.length).split('-') - const year = parseInt(parts[0], 10) - const month = parseInt(parts[1], 10) - if (Number.isFinite(year) && Number.isFinite(month)) { - const lastDay = new Date(year, month, 0).getDate() - const mm = String(month).padStart(2, '0') - const dd = String(lastDay).padStart(2, '0') - clearAllFilters() - setDateFrom(`${year}-${mm}-01`) - setDateTo(`${year}-${mm}-${dd}`) - } } } } @@ -211,22 +183,6 @@ export function LeftSidebar() { setExpandedItems(newExpanded) } - // Build the dynamic By Date subtree from the date_buckets aggregation. - // Year nodes contain month nodes; clicking either sets the date range - // filter (handled in applyLibraryNode). Sorted newest-first by the - // backend already. - const byDateChildren: TreeItem[] = dateBuckets.map((year) => ({ - id: `date-year-${year.year}`, - label: String(year.year), - icon: , - count: year.count, - children: year.months.map((m) => ({ - id: `date-month-${year.year}-${m.month}`, - label: format(new Date(year.year, m.month - 1, 1), 'MMMM'), - count: m.count, - })), - })) - const libraryTree: TreeItem[] = [ { id: 'library', @@ -234,12 +190,6 @@ export function LeftSidebar() { icon: , children: [ { id: 'all-photos', label: 'All Photos', icon: , count: 0 }, - { - id: 'by-date', - label: 'By Date', - icon: , - children: byDateChildren, - }, { id: 'rated', label: 'Rated', icon: , count: 0 }, { id: 'discarded', label: 'Discarded', icon: , count: 0 }, ], @@ -261,7 +211,7 @@ export function LeftSidebar() { // Derive whether a tree item is currently the "active" filter target. // Folder rows are selected when the filter store's folderId matches; the // library "All Photos" virtual node is selected when no folder/heap filter - // is set; year/month nodes when their date range matches. + // is set. const isItemActive = (id: string): boolean => { if (id.startsWith('folder-')) { return filterFolderId === id.slice('folder-'.length) @@ -269,18 +219,6 @@ export function LeftSidebar() { if (id === 'all-photos') { return filterFolderId === null && selectedItem === 'all-photos' } - if (id.startsWith('date-year-')) { - const year = id.slice('date-year-'.length) - return filterDateFrom === `${year}-01-01` && filterDateTo === `${year}-12-31` - } - if (id.startsWith('date-month-')) { - const parts = id.slice('date-month-'.length).split('-') - const year = parseInt(parts[0], 10) - const month = parseInt(parts[1], 10) - if (!Number.isFinite(year) || !Number.isFinite(month)) return false - const mm = String(month).padStart(2, '0') - return filterDateFrom === `${year}-${mm}-01` && filterDateTo?.startsWith(`${year}-${mm}-`) === true - } return selectedItem === id } diff --git a/frontend/src/hooks/useDateBucketsQuery.ts b/frontend/src/hooks/useDateBucketsQuery.ts deleted file mode 100644 index b2281b4..0000000 --- a/frontend/src/hooks/useDateBucketsQuery.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { library, type DateBucketYear } from '../services/api' - -export const DATE_BUCKETS_QUERY_KEY = ['library', 'date_buckets'] as const - -export function useDateBucketsQuery() { - return useQuery({ - queryKey: DATE_BUCKETS_QUERY_KEY, - queryFn: library.dateBuckets, - staleTime: 60_000, - }) -} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index d70610d..b534b58 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -113,16 +113,6 @@ export const photos = { } // Library API -export interface DateBucketMonth { - month: number - count: number -} -export interface DateBucketYear { - year: number - count: number - months: DateBucketMonth[] -} - export const library = { scan: async () => { const response = await api.post('/library/scan') @@ -138,12 +128,6 @@ export const library = { const response = await api.get('/library/stats') return response.data }, - - /** Year/month aggregation for the sidebar By Date drilldown. */ - dateBuckets: async (): Promise => { - const response = await api.get('/library/date_buckets') - return response.data - }, } // Heaps API