import { useState, useMemo } from 'react' import { X, Star, MapPin, Camera, Aperture, Info, ChevronDown, ChevronRight, Check, Trash2, } from 'lucide-react' import clsx from 'clsx' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { format } from 'date-fns' import { usePhotoStore } from '../../store/photoStore' import { photos as photosApi } from '../../services/api' interface PhotoDetails { id: string filename: string filepath: string width: number | null height: number | null file_size: number | null taken_at: string | null rating: number is_picked: boolean is_trashed: boolean exif_json: string | null } interface ExifData { Make?: string Model?: string LensModel?: string Lens?: string ISO?: number | string FNumber?: number | string ApertureValue?: number | string ExposureTime?: string ShutterSpeedValue?: string FocalLength?: string FocalLengthIn35mmFormat?: string GPSLatitude?: number | string GPSLongitude?: number | string [key: string]: unknown } function formatFileSize(bytes: number | null): string { if (bytes == null) return '—' if (bytes < 1024) return `${bytes} B` if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB` return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB` } function formatExifValue(v: unknown): string { if (v == null || v === '') return '—' return String(v) } function pickFirst(exif: ExifData, ...keys: string[]): string { for (const k of keys) { const v = exif[k] if (v != null && v !== '') return String(v) } return '—' } function parseExif(json: string | null): ExifData { if (!json) return {} try { const parsed = JSON.parse(json) return typeof parsed === 'object' && parsed !== null ? (parsed as ExifData) : {} } catch { return {} } } export function RightSidebar() { const { selectedPhotos, activePhotoId, clearSelection } = usePhotoStore() const queryClient = useQueryClient() const [expandedSections, setExpandedSections] = useState>( new Set(['basic', 'camera', 'location']) ) const toggleSection = (section: string) => { const newExpanded = new Set(expandedSections) if (newExpanded.has(section)) newExpanded.delete(section) else newExpanded.add(section) setExpandedSections(newExpanded) } // Fetch the active photo's full record (with EXIF) on demand. const { data: photo } = useQuery({ queryKey: ['photo', activePhotoId], queryFn: () => photosApi.get(activePhotoId!), enabled: !!activePhotoId, staleTime: 60_000, }) // Mutations for rating / pick / reject. Optimistic-ish: invalidate the // photo query and the timeline list query so the grid re-renders too. const updateMutation = useMutation({ mutationFn: (data: { rating?: number is_picked?: boolean is_trashed?: boolean }) => photosApi.update(activePhotoId!, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['photo', activePhotoId] }) queryClient.invalidateQueries({ queryKey: ['photos'] }) }, }) const exif = useMemo(() => parseExif(photo?.exif_json ?? null), [photo?.exif_json]) if (selectedPhotos.length === 0) { return (

Select photos to view details

) } const multipleSelected = selectedPhotos.length > 1 const rating = photo?.rating ?? 0 const isPicked = photo?.is_picked ?? false const isTrashed = photo?.is_trashed ?? false return (
{/* Header */}

{multipleSelected ? `${selectedPhotos.length} Photos Selected` : 'Photo Details'}

{/* Quick Actions — operate on the active photo */} {photo && !multipleSelected && (
{[1, 2, 3, 4, 5].map((value) => ( ))}
)} {/* Metadata */}
{photo && !multipleSelected && ( <> {/* Basic Info */}
toggleSection('basic')} >
{/* Camera */}
toggleSection('camera')} >
{pickFirst(exif, 'Make', 'Model') === '—' ? '—' : `${formatExifValue(exif.Make)} ${formatExifValue(exif.Model)}`.trim()}
{pickFirst(exif, 'LensModel', 'Lens')}
{/* Location */}
toggleSection('location')} > {exif.GPSLatitude && exif.GPSLongitude ? (
{String(exif.GPSLatitude)}, {String(exif.GPSLongitude)}
) : (
No GPS data
)}
)} {!photo && !multipleSelected && (
Loading…
)}
{/* Footer Actions for multi-select */} {multipleSelected && (
)}
) } function Section({ title, expanded, onToggle, children, }: { title: string expanded: boolean onToggle: () => void children: React.ReactNode }) { return (
{expanded &&
{children}
}
) } function Field({ label, value }: { label: string; value: string }) { return (
{label}:

{value}

) }