- volume-aware deletion: read-only volumes remove DB only, writable volumes soft-delete to trash with configurable grace period - trash page with restore, worker purge sweep (TRASH_RETENTION_DAYS) - album delete endpoint + sidebar trash icon - asset delete from timeline selection toolbar - all listing queries exclude trashed assets (deleted_at IS NULL) - timeline ordered by EXIF capture date, date-summary endpoint - README rewritten with features, setup, full env var table
56 lines
1.6 KiB
TypeScript
56 lines
1.6 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo } from "react"
|
|
import { useQueryClient } from "@tanstack/react-query"
|
|
import { useTimeline, useDateSummary } from "@/hooks/use-timeline"
|
|
import { groupByDate } from "@/lib/timeline"
|
|
import { PhotoGrid } from "@/components/photo-grid"
|
|
import { DateScrubber } from "@/components/date-scrubber"
|
|
import api from "@/lib/api"
|
|
import { toast } from "sonner"
|
|
|
|
export default function TimelinePage() {
|
|
const qc = useQueryClient()
|
|
const { assets, isLoading, hasMore, loadMore, total } = useTimeline()
|
|
const { data: dateSummary } = useDateSummary()
|
|
const groups = useMemo(() => groupByDate(assets), [assets])
|
|
|
|
const handleDeleteAssets = async (ids: string[]) => {
|
|
let deleted = 0
|
|
for (const id of ids) {
|
|
try {
|
|
await api.delete(`/assets/${id}`)
|
|
deleted++
|
|
} catch { /* skip */ }
|
|
}
|
|
if (deleted > 0) {
|
|
toast.success(`Deleted ${deleted} photo(s)`)
|
|
qc.invalidateQueries({ queryKey: ["timeline"] })
|
|
qc.invalidateQueries({ queryKey: ["date-summary"] })
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-lg font-semibold">Timeline</h1>
|
|
{total > 0 && (
|
|
<span className="text-sm text-muted-foreground">
|
|
{total} photos
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-1">
|
|
<PhotoGrid
|
|
groups={groups}
|
|
isLoading={isLoading}
|
|
hasMore={hasMore}
|
|
onLoadMore={() => loadMore()}
|
|
onDeleteAssets={handleDeleteAssets}
|
|
/>
|
|
<DateScrubber dates={dateSummary ?? []} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|