feat: UI/UX improvements pass + fix photo 404 in Docker
Photos: - Serve uploads via /api/photos/[filename] from UPLOAD_DIR env var - Default UPLOAD_DIR=/data/uploads (mount as Docker volume for persistence) - Upload route creates dir on demand, no root-permission issue - Dockerfile creates /data/uploads owned by nextjs user Growth: - Show chart with single measurement (was hidden until 2+ points) - PC label expanded to "PC — Périmètre crânien (cm)" - WHO percentile ⓘ tooltip explaining P50/P3-P97 - Date field defaults to today in add-measurement form Stats: - Heatmap legend shows actual counts (0 1 2 3 4+) not just Moins/Plus - Sleep timeline labeled "Fenêtre 19h – 11h" Timeline: - Filter type persisted in URL param (?type=...) — survives navigation - Inline note save shows ✓ flash after blur - Photo thumbnails open in new tab Photos: - Lightbox wraps: next on last → first, prev on first → last Search: - Result count header "X événements · Y notes" - Event results link to /timeline?date=...&type=... - Note results link to /notes?date=... Notes: - Unsaved indicator (amber dot) while textarea dirty, green ✓ after save Milestones: - Sort by date ascending within each month group Settings: - Pushover key field has eye toggle (show/hide) - API key delete shows confirmation with key name - All sections wrapped in collapsible SectionCard accordion Dashboard: - Empty state callout for new users with no events today Event modal: - Milk lot selection shows "Sélectionnez un lot — il sera marqué comme utilisé" - Photo upload validates 5MB client-side before sending - Timer resume shows "Reprise du chronomètre" when loaded from localStorage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,11 +58,11 @@ export default function PhotosPage() {
|
||||
}
|
||||
|
||||
function prev() {
|
||||
setLightbox((lb) => lb && lb.index > 0 ? { ...lb, index: lb.index - 1 } : lb);
|
||||
setLightbox((lb) => lb ? { ...lb, index: (lb.index - 1 + lb.events.length) % lb.events.length } : lb);
|
||||
}
|
||||
|
||||
function next() {
|
||||
setLightbox((lb) => lb && lb.index < lb.events.length - 1 ? { ...lb, index: lb.index + 1 } : lb);
|
||||
setLightbox((lb) => lb ? { ...lb, index: (lb.index + 1) % lb.events.length } : lb);
|
||||
}
|
||||
|
||||
const current = lightbox ? lightbox.events[lightbox.index] : null;
|
||||
@@ -150,16 +150,14 @@ export default function PhotosPage() {
|
||||
<div className="flex items-center justify-between px-4 py-4 flex-shrink-0" onClick={(e) => e.stopPropagation()}>
|
||||
<button
|
||||
onClick={prev}
|
||||
disabled={lightbox.index === 0}
|
||||
className="w-10 h-10 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 transition disabled:opacity-30"
|
||||
className="w-10 h-10 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 transition"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5 text-white" />
|
||||
</button>
|
||||
<span className="text-white/60 text-xs">{lightbox.index + 1} / {lightbox.events.length}</span>
|
||||
<button
|
||||
onClick={next}
|
||||
disabled={lightbox.index === lightbox.events.length - 1}
|
||||
className="w-10 h-10 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 transition disabled:opacity-30"
|
||||
className="w-10 h-10 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 transition"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5 text-white" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user