feat(filters): move date picker to sidebar, track visible photo order
Pulls the date range picker out of the filter-bar pill into a dedicated always-visible section at the top of the left sidebar, and teaches the timeline to publish its visible photo sequence so "open first photo" shortcuts respect the on-screen order. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
@@ -25,7 +25,9 @@ import {
|
||||
Clock,
|
||||
Upload as UploadIcon,
|
||||
Download as DownloadIcon,
|
||||
CalendarRange,
|
||||
} from 'lucide-react'
|
||||
import { DateRangePicker } from '../filter/DateRangePicker'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { sourceFolders, photos as photosApi, downloads, type FolderTreeNode } from '../../services/api'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
@@ -851,6 +853,11 @@ export function LeftSidebar() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Date range filter — always visible, collapsible. Drives the
|
||||
* global dateFrom/dateTo on the filter store, so it applies to
|
||||
* every section regardless of which tree item is selected. */}
|
||||
<DateFilterSection />
|
||||
|
||||
{/* Active heap card — pinned just below the Library header so
|
||||
* toasts (bottom-left fixed) can't cover it. Returns null when
|
||||
* no heap is active, so the layout collapses cleanly. */}
|
||||
@@ -972,3 +979,71 @@ export function LeftSidebar() {
|
||||
)
|
||||
}
|
||||
|
||||
/** Collapsible date-range filter block. Lives at the top of the left
|
||||
* sidebar and drives the global dateFrom/dateTo filter-store fields,
|
||||
* so it applies across every section. The calendar component itself
|
||||
* supports range selection and decorates days that have photos.
|
||||
* Open state persists across sessions via localStorage. */
|
||||
const DATE_FILTER_OPEN_KEY = 'mulita:dateFilterOpen'
|
||||
function DateFilterSection() {
|
||||
const dateFrom = useFilterStore((s) => s.dateFrom)
|
||||
const dateTo = useFilterStore((s) => s.dateTo)
|
||||
const setDateFrom = useFilterStore((s) => s.setDateFrom)
|
||||
const setDateTo = useFilterStore((s) => s.setDateTo)
|
||||
const active = dateFrom !== null || dateTo !== null
|
||||
// Default open; remembered across sessions. localStorage is read
|
||||
// lazily inside the initialiser so SSR / disabled-storage fall back
|
||||
// cleanly to the default.
|
||||
const [open, setOpen] = useState<boolean>(() => {
|
||||
try {
|
||||
const v = localStorage.getItem(DATE_FILTER_OPEN_KEY)
|
||||
if (v === null) return true
|
||||
return v === '1'
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(DATE_FILTER_OPEN_KEY, open ? '1' : '0')
|
||||
} catch {
|
||||
// Ignore — quota / disabled storage is non-fatal.
|
||||
}
|
||||
}, [open])
|
||||
const summary = active
|
||||
? dateFrom && dateTo && dateFrom === dateTo
|
||||
? dateFrom
|
||||
: `${dateFrom ?? '…'} → ${dateTo ?? '…'}`
|
||||
: 'Any date'
|
||||
return (
|
||||
<div className="flex-shrink-0 border-b border-border">
|
||||
<button
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="flex w-full items-center gap-2 px-3 py-2 text-left text-xs text-text-muted hover:bg-surface-2"
|
||||
aria-expanded={open}
|
||||
>
|
||||
<CalendarRange className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="truncate">{summary}</span>
|
||||
<span
|
||||
className={cn(
|
||||
'ml-auto shrink-0 text-[10px] uppercase tracking-wider',
|
||||
active && 'text-primary',
|
||||
)}
|
||||
>
|
||||
{open ? 'Hide' : active ? 'Filtering' : 'Filter'}
|
||||
</span>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="px-3 pb-3">
|
||||
<DateRangePicker
|
||||
from={dateFrom}
|
||||
to={dateTo}
|
||||
onFromChange={setDateFrom}
|
||||
onToChange={setDateTo}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ export function RightSidebar() {
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
if (selectedPhotos.length === 0) {
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user