feat: editable taken_at + folder-based date repair and filter

Lets operators fix corrupted capture dates at scale. Adds an editable
Date Taken field with a folder/filename-derived suggestion hint, a bulk
Date Taken section in the multi-select sidebar that either applies one
date to the whole selection or infers a per-photo date from each path,
a warning badge on thumbnails whose stored date disagrees with the
path, and a "Date issues" filter pill so suspicious photos can be
surfaced and fixed as a group. Edits are written back to EXIF on disk
so rescans don't clobber the fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 11:48:55 +02:00
parent 339e1be510
commit 30d03d8d4d
19 changed files with 1281 additions and 26 deletions

View File

@@ -1,5 +1,13 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { Star, ShoppingBasket, Trash2, RefreshCw, Check, Copy } from 'lucide-react'
import {
Star,
ShoppingBasket,
Trash2,
RefreshCw,
Check,
Copy,
AlertTriangle,
} from 'lucide-react'
import clsx from 'clsx'
import { photos as photosApi } from '../../services/api'
import type { Photo } from '../../types/photo'
@@ -92,6 +100,13 @@ export function PhotoThumbnail({
const baseUrl = photosApi.getThumbnailUrl(photo.id, 'medium')
const thumbnailUrl = retryCount > 0 ? `${baseUrl}?retry=${retryCount}` : baseUrl
// "Capture date probably wrong" — read straight from the stored
// `has_date_warning` flag rather than recomputing the heuristic
// client-side. The backend sets this column at scan time and
// refreshes it on any taken_at edit, so the UI, the filter, and the
// thumbnail badge all read from one source of truth.
const dateWarning = photo.has_date_warning === true
// Square cells (Lightroom-style grid). Variable-aspect cells previously
// overflowed their row because TanStack Virtual estimates row height as a
// single fixed value — portraits in a landscape row would overlap the row
@@ -323,6 +338,21 @@ export function PhotoThumbnail({
<Trash2 className={THUMB_BADGE_ICON} strokeWidth={2.5} />
</div>
)}
{dateWarning && (
<div
className={clsx(
THUMB_BADGE_BASE,
THUMB_BADGE_SQUARE,
// Amber fill with the same chiseled frame as other affirmative
// badges so it reads as a first-class warning rather than a
// neutral info chip.
'bg-amber-500 shadow-[0_0_0_1px_rgba(0,0,0,0.55),inset_0_1px_0_rgba(255,255,255,0.28),0_1px_2px_rgba(0,0,0,0.5)]'
)}
title="Capture date may be wrong — folder/filename suggests a different date"
>
<AlertTriangle className={THUMB_BADGE_ICON} strokeWidth={2.5} />
</div>
)}
</div>
{/* TR — file-type metadata (RAW / VIDEO) */}