From 63effbf85bb1892a612031267b55df140c7b93a4 Mon Sep 17 00:00:00 2001 From: Arnaud Nelissen Date: Sat, 13 Jun 2026 15:36:54 +0200 Subject: [PATCH] ux: dashboard grouping, responsive grids, grouped nav drawer, modal polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dashboard: - Split 12-item quick-log into two labeled groups (Alimentation & Soins / Sommeil & Activités) - "Voir tout →" link on today's timeline when events present - Quick-log button: hover highlight indigo instead of grey for clearer affordance Event modal: - Timer: text-3xl on mobile, text-4xl on sm+ (prevents overflow on narrow screens) - Date/time inputs: CalendarDays + Clock icons inline in each field for clarity Stats: - Today summary 3-col: gap-2 md:gap-3, p-3 md:p-4, text-xl md:text-2xl (no text clipping on mobile) - Sleep/feeding summary grids: gap-2 md:gap-4 Calendar: - Cell min-height: 52px mobile, 64px sm+ (reduces grid height on small screens) - Day detail: events sorted by time, duration shown as HH:mm → HH:mm for ranged events - Empty day state uses card container instead of bare text Medications: - Empty state: Pill icon + subtitle copy, centred card Milk: - Used/archived section: "… et N lots de plus" indicator when > 20 items Nav drawer (mobile): - Grouped secondary links: Suivi / Santé / Vie du bébé sections with section headers - Medications: correct Pill icon (was duplicating Bell/Stethoscope) - Ungrouped items fall through to a full-width list row Co-Authored-By: Claude Sonnet 4.6 --- src/app/(app)/calendar/page.tsx | 56 +++++++++++++-------- src/app/(app)/dashboard/page.tsx | 79 ++++++++++++++++-------------- src/app/(app)/medications/page.tsx | 8 ++- src/app/(app)/milk/page.tsx | 5 ++ src/app/(app)/stats/page.tsx | 12 ++--- src/components/event-modal.tsx | 40 +++++++++------ src/components/nav.tsx | 56 ++++++++++++++++++--- 7 files changed, 168 insertions(+), 88 deletions(-) diff --git a/src/app/(app)/calendar/page.tsx b/src/app/(app)/calendar/page.tsx index a5aeb9f..27794e8 100644 --- a/src/app/(app)/calendar/page.tsx +++ b/src/app/(app)/calendar/page.tsx @@ -109,7 +109,7 @@ export default function CalendarPage() { - ); - })} - +
+ {QUICK_LOG_GROUPS.map((group) => ( +
+

{group.label}

+
+ {group.types.map((type) => { + const cfg = EVENT_CONFIG[type]; + return ( + + ); + })} +
+
+ ))}
@@ -483,9 +483,14 @@ export default function DashboardPage() { {/* Today's timeline */} {todayEvents.length > 0 && (
-

- Aujourd'hui · {todayEvents.length} événement{todayEvents.length > 1 ? "s" : ""} -

+
+

+ Aujourd'hui · {todayEvents.length} événement{todayEvents.length > 1 ? "s" : ""} +

+ + Voir tout → + +
{todayEvents.slice(0, 10).map((ev) => { const cfg = EVENT_CONFIG[ev.type]; diff --git a/src/app/(app)/medications/page.tsx b/src/app/(app)/medications/page.tsx index e0f1f46..23c5176 100644 --- a/src/app/(app)/medications/page.tsx +++ b/src/app/(app)/medications/page.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { Plus, Pencil, Trash2, Check, X } from "lucide-react"; +import { Plus, Pencil, Trash2, Check, X, Pill } from "lucide-react"; interface Profile { id: string; @@ -224,7 +224,11 @@ export default function MedicationsPage() { {!isLoading && profiles.length === 0 && !showForm && (
-

Aucun profil médicament

+
+ +
+

Aucun profil médicament

+

Créez un profil pour suivre les intervalles de prise

)}
diff --git a/src/app/(app)/milk/page.tsx b/src/app/(app)/milk/page.tsx index be86325..7fb8a1a 100644 --- a/src/app/(app)/milk/page.tsx +++ b/src/app/(app)/milk/page.tsx @@ -340,6 +340,11 @@ export default function MilkPage() {
))} + {used.length > 20 && ( +

+ … et {used.length - 20} lot{used.length - 20 > 1 ? "s" : ""} de plus +

+ )} )} diff --git a/src/app/(app)/stats/page.tsx b/src/app/(app)/stats/page.tsx index 554ee66..ada6c04 100644 --- a/src/app/(app)/stats/page.tsx +++ b/src/app/(app)/stats/page.tsx @@ -187,15 +187,15 @@ export default function StatsPage() { {/* Today summary */} -
+
{[ { label: "Repas", value: todayFeeds, color: "text-rose-600", bg: "bg-rose-50 dark:bg-rose-950", border: "border-rose-100 dark:border-rose-900" }, { label: "Couches", value: todayDiapers, color: "text-amber-600", bg: "bg-amber-50 dark:bg-amber-950", border: "border-amber-100 dark:border-amber-900" }, { label: "Sommeil", value: `${Math.round(todaySleepMins / 60 * 10) / 10}h`, color: "text-violet-600", bg: "bg-violet-50 dark:bg-violet-950", border: "border-violet-100 dark:border-violet-900" }, ].map(({ label, value, color, bg, border }) => ( -
-
{value}
-
{label} · aujourd'hui
+
+
{value}
+
{label}
· aujourd'hui
))}
@@ -213,7 +213,7 @@ export default function StatsPage() {

Résumé du sommeil

{periodSubtitle(days)}

-
+

Total

@@ -239,7 +239,7 @@ export default function StatsPage() {

Résumé des repas

{periodSubtitle(days)}

-
+

Durée allaitement

diff --git a/src/components/event-modal.tsx b/src/components/event-modal.tsx index 57ca2e8..359ece7 100644 --- a/src/components/event-modal.tsx +++ b/src/components/event-modal.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useRef } from "react"; import { EVENT_CONFIG, EventType } from "@/lib/event-config"; import { EventIcon } from "@/components/event-icon"; -import { X, Camera, Loader2, AlertTriangle, Zap, Clock } from "lucide-react"; +import { X, Camera, Loader2, AlertTriangle, Zap, Clock, CalendarDays } from "lucide-react"; import Image from "next/image"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; @@ -327,7 +327,7 @@ export function EventModal({ type, babyId, onClose, onSaved, initialEvent }: Pro {/* Timer */} {config.hasTimer && (

-
+
{formatTimer(timerSeconds)}
{config.hasDuration && (
- setForm((f) => ({ ...f, endedAt: `${e.target.value}T${f.endedAt.slice(11, 16) || "00:00"}` }))} - className={inputCls} /> - setForm((f) => ({ ...f, endedAt: `${f.endedAt.slice(0, 10) || new Date().toISOString().slice(0, 10)}T${e.target.value}` }))} - className={inputCls} /> +
+ + setForm((f) => ({ ...f, endedAt: `${e.target.value}T${f.endedAt.slice(11, 16) || "00:00"}` }))} + className={`${inputCls} pl-8`} /> +
+
+ + setForm((f) => ({ ...f, endedAt: `${f.endedAt.slice(0, 10) || new Date().toISOString().slice(0, 10)}T${e.target.value}` }))} + className={`${inputCls} pl-8`} /> +
)} diff --git a/src/components/nav.tsx b/src/components/nav.tsx index e29f78f..1db9b12 100644 --- a/src/components/nav.tsx +++ b/src/components/nav.tsx @@ -26,6 +26,7 @@ import { Bell, CalendarDays, Milk, + Pill, } from "lucide-react"; const LINKS = [ @@ -38,12 +39,19 @@ const LINKS = [ { href: "/milestones", label: "Étapes", Icon: Star }, { href: "/notes", label: "Notes", Icon: NotebookPen }, { href: "/doctor-notes", label: "Médecin", Icon: Stethoscope }, - { href: "/vaccinations", label: "Vaccinations", Icon: Syringe }, - { href: "/medications", label: "Médic.", Icon: Stethoscope }, + { href: "/vaccinations", label: "Vaccins", Icon: Syringe }, + { href: "/medications", label: "Médicaments", Icon: Pill }, { href: "/reminders", label: "Rappels", Icon: Bell }, { href: "/settings", label: "Réglages", Icon: Settings }, ]; +// Groups shown in the mobile "More" drawer +const DRAWER_GROUPS: { label: string; hrefs: string[] }[] = [ + { label: "Suivi", hrefs: ["/calendar", "/stats", "/milk"] }, + { label: "Santé", hrefs: ["/medications", "/reminders", "/vaccinations", "/doctor-notes"] }, + { label: "Vie du bébé", hrefs: ["/milestones", "/notes", "/growth"] }, +]; + const PRIMARY_HREFS = ["/dashboard", "/timeline", "/growth", "/settings"]; const PRIMARY_LINKS = LINKS.filter((l) => PRIMARY_HREFS.includes(l.href)); @@ -253,23 +261,55 @@ export function BottomNav() {
- {/* Grid of secondary links */} -
- {secondaryLinks.map(({ href, label, Icon }) => { + {/* Grouped secondary links */} +
+ {DRAWER_GROUPS.map((group) => { + const groupLinks = group.hrefs + .map((href) => secondaryLinks.find((l) => l.href === href)) + .filter(Boolean) as typeof secondaryLinks; + if (groupLinks.length === 0) return null; + return ( +
+

{group.label}

+
+ {groupLinks.map(({ href, label, Icon }) => { + const active = pathname === href; + return ( + setDrawerOpen(false)} + className={`flex flex-col items-center gap-1.5 px-2 py-2.5 rounded-xl transition-colors ${ + active + ? "bg-indigo-50 dark:bg-indigo-950 text-indigo-600 dark:text-indigo-400" + : "text-slate-500 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800" + }`} + > + + {label} + + ); + })} +
+
+ ); + })} + {/* Any admin link not in groups */} + {secondaryLinks.filter((l) => !DRAWER_GROUPS.flatMap((g) => g.hrefs).includes(l.href)).map(({ href, label, Icon }) => { const active = pathname === href; return ( setDrawerOpen(false)} - className={`flex flex-col items-center gap-1.5 px-2 py-3 rounded-xl transition-colors ${ + className={`flex items-center gap-3 px-3 py-2.5 rounded-xl transition-colors ${ active ? "bg-indigo-50 dark:bg-indigo-950 text-indigo-600 dark:text-indigo-400" : "text-slate-500 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800" }`} > - - {label} + + {label} ); })}