Files
k-photos/k-photos-frontend/lib/timeline.ts
Gabriel Kaszewski 957737ac9b feat: frontend MVP — auth, timeline, upload, albums, admin, image viewer
Backend:
- user roles (DB + JWT + first-user-is-admin)
- volume-aware file resolver (multi-volume asset serving)
- directory scanner uses volume URI directly
- date-summary endpoint (capture date from EXIF)
- timeline ordered by capture date
- list endpoints: volumes, plugins, pipelines, library paths
- delete endpoints: volumes, library paths
- configurable upload body limit (MAX_UPLOAD_BYTES)

Frontend:
- auth: login/register, token refresh, role-based admin gate
- timeline: date-grouped grid, infinite scroll, date scrubber
- image viewer: fullscreen zoom/pan/pinch, metadata sidebar
- upload: drag-drop, sequential upload, progress tracking
- albums: create, add/remove photos, asset picker dialog
- admin: storage (import library), jobs (pagination, error details),
  plugins (list + toggle), pipelines, sidecars, duplicates
- multi-select mode with add-to-album action
- TanStack Query for all data fetching
2026-06-01 01:35:43 +02:00

36 lines
929 B
TypeScript

import { format, parseISO } from "date-fns"
import type { AssetResponse } from "./types"
export interface DateGroup {
date: string
label: string
assets: AssetResponse[]
}
export function getPhotoDate(asset: AssetResponse): Date {
const dto = asset.metadata?.DateTimeOriginal as string | undefined
if (dto) {
const parsed = new Date(dto.replace(" ", "T"))
if (!isNaN(parsed.getTime())) return parsed
}
return parseISO(asset.created_at)
}
export function groupByDate(assets: AssetResponse[]): DateGroup[] {
const map = new Map<string, AssetResponse[]>()
for (const asset of assets) {
const d = getPhotoDate(asset)
const key = format(d, "yyyy-MM-dd")
const group = map.get(key)
if (group) group.push(asset)
else map.set(key, [asset])
}
return Array.from(map.entries()).map(([date, assets]) => ({
date,
label: format(parseISO(date), "MMMM d, yyyy"),
assets,
}))
}