From 1142eb93f2e9ec75d296d3de6d63d2f81ebbb149 Mon Sep 17 00:00:00 2001 From: ilia Date: Wed, 5 Aug 2026 14:50:28 -0400 Subject: [PATCH] Restyle admin Home as a quiet ops dashboard. --- admin-frontend/src/pages/Dashboard.tsx | 398 ++++++++++--------------- docs/ADMIN_UX_REVIEW.md | 1 + docs/FACE_ACCURACY_STATUS.md | 4 +- 3 files changed, 158 insertions(+), 245 deletions(-) diff --git a/admin-frontend/src/pages/Dashboard.tsx b/admin-frontend/src/pages/Dashboard.tsx index a286889..e341777 100644 --- a/admin-frontend/src/pages/Dashboard.tsx +++ b/admin-frontend/src/pages/Dashboard.tsx @@ -1,265 +1,177 @@ import { useEffect, useState } from 'react' +import { Link } from 'react-router-dom' import { useAuth } from '../context/AuthContext' import { photosApi, PhotoSearchResult } from '../api/photos' import apiClient from '../api/client' +type Shortcut = { + to: string + title: string + blurb: string + featureKey?: string +} + +const SHORTCUTS: Shortcut[] = [ + { + to: '/scan', + title: 'Scan', + blurb: 'Find new photos on disk', + featureKey: 'scan', + }, + { + to: '/process', + title: 'Process', + blurb: 'Detect faces in scanned photos', + featureKey: 'process', + }, + { + to: '/people/identify', + title: 'Identify', + blurb: 'Name unknown faces', + featureKey: 'identify_people', + }, + { + to: '/people/auto-match', + title: 'Auto-Match', + blurb: 'Review suggested matches', + featureKey: 'auto_match', + }, + { + to: '/search', + title: 'Search', + blurb: 'Browse by person, date, or folder', + featureKey: 'search_photos', + }, + { + to: '/tags', + title: 'Tag photos', + blurb: 'Organize with tags', + featureKey: 'tag_photos', + }, +] + export default function Dashboard() { - const { username: _username } = useAuth() + const { username, hasPermission } = useAuth() const [samplePhotos, setSamplePhotos] = useState([]) const [loadingPhotos, setLoadingPhotos] = useState(true) + const shortcuts = SHORTCUTS.filter( + (s) => !s.featureKey || hasPermission(s.featureKey) + ) + useEffect(() => { - loadSamplePhotos() + let cancelled = false + ;(async () => { + try { + setLoadingPhotos(true) + const result = await photosApi.searchPhotos({ + search_type: 'processed', + page: 1, + page_size: 8, + }) + if (!cancelled) setSamplePhotos(result.items || []) + } catch { + if (!cancelled) setSamplePhotos([]) + } finally { + if (!cancelled) setLoadingPhotos(false) + } + })() + return () => { + cancelled = true + } }, []) - const loadSamplePhotos = async () => { - try { - setLoadingPhotos(true) - // Try to get some recent photos to display - const result = await photosApi.searchPhotos({ - search_type: 'processed', - page: 1, - page_size: 6, - }) - setSamplePhotos(result.items || []) - } catch (error) { - console.error('Failed to load sample photos:', error) - setSamplePhotos([]) - } finally { - setLoadingPhotos(false) - } - } - - const getPhotoImageUrl = (photoId: number): string => { - return `${apiClient.defaults.baseURL}/api/v1/photos/${photoId}/image` - } + const getPhotoImageUrl = (photoId: number): string => + `${apiClient.defaults.baseURL}/api/v1/photos/${photoId}/image` return ( -
- {/* Hero Section */} -
-
-
-

- Welcome to PunimTag -

-

- Your Intelligent Photo Management System -

-

- Organize, identify, and search through your photo collection like never before. -

-
-
+
+
+

+ JRCC photo admin +

+

+ {username ? `Welcome back, ${username}` : 'Home'} +

+

+ Scan, process, and name faces for the community library. Pick a workflow + below โ€” same destinations as the sidebar. +

+
+ +
+

+ Workflows +

+
    + {shortcuts.map((item) => ( +
  • + + + {item.title} + + {item.blurb} + +
  • + ))} +
- {/* Feature Showcase 1 - AI Recognition */} -
-
-
-
-
๐Ÿค–
-

- Recognize Faces Automatically -

-

- Never lose track of who's in your photos again. Our smart system - automatically finds and recognizes faces in all your pictures. Just - tell it who someone is once, and it will find them in thousands of - photosโ€”even from years ago. -

-
    -
  • - โœ“ - Automatically finds faces in all your photos -
  • -
  • - โœ“ - Recognizes the same person across different photos -
  • -
  • - โœ“ - Works even with photos taken years apart -
  • -
-
-
-
-
-
-
๐Ÿ‘ฅ
-

- Face recognition in action -

-
-
-
-
-
-
-
- - {/* Feature Showcase 2 - Smart Search */} -
-
-
-
-
-
-
-
๐Ÿ”
-

- Powerful search interface -

-
-
-
-
-
-
๐Ÿ”
-

- Find Anything, Instantly -

-

- Search your entire photo collection by people, dates, tags, or - folders. Our advanced filtering system makes it easy to find - exactly what you're looking for, no matter how large your - collection grows. -

-
    -
  • - โœ“ - Search by person name across all photos -
  • -
  • - โœ“ - Filter by date ranges and folders -
  • -
  • - โœ“ - Tag-based organization and filtering -
  • -
-
-
-
-
- - {/* Feature Showcase 3 - Batch Processing */} -
-
-
-
-
โšก
-

- Process Thousands at Once -

-

- Don't let a large photo collection overwhelm you. Our batch - processing system efficiently handles thousands of photos with - real-time progress tracking. Watch as your photos are organized - automatically. -

-
    -
  • - โœ“ - Batch face detection and recognition -
  • -
  • - โœ“ - Real-time progress updates -
  • -
  • - โœ“ - Background job processing -
  • -
-
-
-
-
-
-
๐Ÿ“Š
-

- Batch processing dashboard -

-
-
-
-
-
-
-
- - {/* Visual Gallery Section */} -
-
-

- Organize Your Memories +
+
+

+ Recent processed

-

- Transform your photo collection into an organized, searchable library - of memories. Find any moment, any person, any time. -

- {loadingPhotos ? ( -
- {[1, 2, 3].map((i) => ( -
-
-
๐Ÿ“ธ
-

Loading...

-
-
- ))} -
- ) : samplePhotos.length > 0 ? ( -
- {samplePhotos.slice(0, 6).map((photo) => ( -
- {photo.filename} { - const target = e.target as HTMLImageElement - target.style.display = 'none' - const parent = target.parentElement - if (parent && !parent.querySelector('.error-fallback')) { - const fallback = document.createElement('div') - fallback.className = - 'w-full h-full flex items-center justify-center error-fallback' - fallback.innerHTML = - '
๐Ÿ“ธ

Photo

' - parent.appendChild(fallback) - } - }} - /> -
- ))} -
- ) : ( -
- {[1, 2, 3].map((i) => ( -
-
-
๐Ÿ“ธ
-

Your photos

-
-
- ))} -
+ {hasPermission('search_photos') && ( + + Open search + )}
+ + {loadingPhotos ? ( +
+ {[1, 2, 3, 4].map((i) => ( +
+ ))} +
+ ) : samplePhotos.length > 0 ? ( +
    + {samplePhotos.slice(0, 8).map((photo) => ( +
  • + {photo.filename +
  • + ))} +
+ ) : ( +

+ No processed photos yet. Start with Scan, then Process. +

+ )}

) diff --git a/docs/ADMIN_UX_REVIEW.md b/docs/ADMIN_UX_REVIEW.md index d82380a..b3b7908 100644 --- a/docs/ADMIN_UX_REVIEW.md +++ b/docs/ADMIN_UX_REVIEW.md @@ -20,6 +20,7 @@ Snapshot after Chabad Blue chrome parity (navy sidebar, theme toggle, shared tok | P2 | Empty / error states often plain text | Consistent empty cards + primary CTA (โ€œScan a folderโ€, โ€œRun Auto-Matchโ€) | | P2 | iOS hamburger only โ€” no desktop collapse | Optional collapsed icon rail for large monitors | | P3 | Viewer filter comboboxes lack accessible names (axe `button-name`) | Label each Select trigger; then restore full-page axe on gallery home | +| Done | Admin Home was a SaaS marketing splash (orange/blue hero, emoji placeholders) | Quiet ops home: welcome + workflow links + recent processed strip | | P3 | Emoji leftovers in Help / some toasts | Plain language + lucide/SVG icons over time | ## Accessibility status diff --git a/docs/FACE_ACCURACY_STATUS.md b/docs/FACE_ACCURACY_STATUS.md index b3442fd..ef02fac 100644 --- a/docs/FACE_ACCURACY_STATUS.md +++ b/docs/FACE_ACCURACY_STATUS.md @@ -11,8 +11,8 @@ plus Immich-inspired precision gates. | **Same vs different embedding distance** | **Yes โ€” working** | Same-person pairs mean **0.44** (p50 0.45); different-person mean **0.84** (p50 0.92). Clear ArcFace separation. | | **Quality score spread** | **Slightly better** | After Phase 1 fix + Phase 3 re-score: max **0.78** (was ~0.72), stddev still **~0.05**, mean ~**0.62**. Sharpness now real, but size/brightness/contrast still dominate โ€” scores stay clustered. | | **Quality gate (โ‰ฅ0.5 refs)** | **Useful** | **38** faces below 0.5; all **29** identified faces remain eligible. | -| **Fitted confidence knots** | **Not applied** | Auto-fit still noisy on small DEV set. Legacy empirical curve still in use. | -| **`match_decisions` log** | **Empty** | Need Auto-Match Saves on DEV. | +| **Fitted confidence knots** | **Not applied** | Dry-run cliff (~31% @ 0.49 โ†’ ~1%) reflects sharp same/diff separation on a tiny identified set โ€” **not** a live bug. Legacy empirical curve still in use. | +| **`match_decisions` log** | **Seeded only** | Seed script filled rows from identified pairs; real Auto-Match Saves still needed before `--apply`. | **Bottom line:** Matching geometry is healthy (same โ‰ช different). Precision gates from Immich cut false accepts; person-merge is live; cluster-first naming still open.