ux: dashboard grouping, responsive grids, grouped nav drawer, modal polish
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 <noreply@anthropic.com>
This commit is contained in:
@@ -109,7 +109,7 @@ export default function CalendarPage() {
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setSelectedDay(isSameDay(day, selectedDay ?? new Date(0)) ? null : day)}
|
||||
className={`min-h-[64px] p-1.5 text-left border-b border-r border-slate-100 dark:border-slate-800 last:border-r-0 transition ${
|
||||
className={`min-h-[52px] sm:min-h-[64px] p-1 sm:p-1.5 text-left border-b border-r border-slate-100 dark:border-slate-800 last:border-r-0 transition ${
|
||||
isSelected
|
||||
? "bg-indigo-50 dark:bg-indigo-950"
|
||||
: "hover:bg-slate-50 dark:hover:bg-slate-800/50"
|
||||
@@ -148,30 +148,44 @@ export default function CalendarPage() {
|
||||
{/* Selected day detail */}
|
||||
{selectedDay && (
|
||||
<div className="mt-4">
|
||||
<h2 className="text-sm font-semibold text-slate-700 dark:text-slate-200 mb-3 capitalize">
|
||||
{format(selectedDay, "EEEE d MMMM", { locale: fr })}
|
||||
<span className="ml-2 text-slate-400 dark:text-slate-500 font-normal">· {selectedDayEvents.length} événement{selectedDayEvents.length !== 1 ? "s" : ""}</span>
|
||||
</h2>
|
||||
<div className="flex items-baseline justify-between mb-3">
|
||||
<h2 className="text-sm font-semibold text-slate-700 dark:text-slate-200 capitalize">
|
||||
{format(selectedDay, "EEEE d MMMM", { locale: fr })}
|
||||
</h2>
|
||||
<span className="text-xs text-slate-400 dark:text-slate-500">
|
||||
{selectedDayEvents.length} événement{selectedDayEvents.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
{selectedDayEvents.length === 0 ? (
|
||||
<p className="text-sm text-slate-400 dark:text-slate-500 py-4 text-center">Aucun événement</p>
|
||||
<div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl py-8 text-center text-slate-400 dark:text-slate-500">
|
||||
<p className="text-sm">Aucun événement ce jour</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl divide-y divide-slate-100 dark:divide-slate-800">
|
||||
{selectedDayEvents.map((ev) => {
|
||||
const cfg = EVENT_CONFIG[ev.type];
|
||||
return (
|
||||
<div key={ev.id} className="flex items-center gap-3 px-4 py-3">
|
||||
<div className={`w-8 h-8 rounded-lg ${cfg.bgClass} flex items-center justify-center flex-shrink-0`}>
|
||||
<EventIcon name={cfg.icon} className={`w-4 h-4 ${cfg.colorClass}`} />
|
||||
{selectedDayEvents
|
||||
.sort((a, b) => new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime())
|
||||
.map((ev) => {
|
||||
const cfg = EVENT_CONFIG[ev.type];
|
||||
const hasDuration = ev.endedAt;
|
||||
return (
|
||||
<div key={ev.id} className="flex items-center gap-3 px-4 py-3">
|
||||
<div className={`w-8 h-8 rounded-lg ${cfg.bgClass} flex items-center justify-center flex-shrink-0`}>
|
||||
<EventIcon name={cfg.icon} className={`w-4 h-4 ${cfg.colorClass}`} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-slate-700 dark:text-slate-200">{cfg.label}</p>
|
||||
{hasDuration && (
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500">
|
||||
{format(new Date(ev.startedAt), "HH:mm")} → {format(new Date(ev.endedAt!), "HH:mm")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-slate-400 dark:text-slate-500 font-mono flex-shrink-0">
|
||||
{format(new Date(ev.startedAt), "HH:mm")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-slate-700 dark:text-slate-200">{cfg.label}</p>
|
||||
</div>
|
||||
<span className="text-xs text-slate-400 dark:text-slate-500 font-mono flex-shrink-0">
|
||||
{format(new Date(ev.startedAt), "HH:mm")}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,19 +11,15 @@ import { format, isToday } from "date-fns";
|
||||
import { fr } from "date-fns/locale";
|
||||
import { AlertTriangle, Clock, CheckCircle2, Pin, PinOff, Pencil, Check, X, Plus, Trash2 } from "lucide-react";
|
||||
|
||||
const QUICK_LOG_TYPES: EventType[] = [
|
||||
"BREASTFEED",
|
||||
"BOTTLE",
|
||||
"PUMP",
|
||||
"DIAPER_WET",
|
||||
"DIAPER_STOOL",
|
||||
"NAP",
|
||||
"NIGHT_SLEEP",
|
||||
"MEDICATION",
|
||||
"TEMPERATURE",
|
||||
"BATH",
|
||||
"WALK",
|
||||
"TUMMY_TIME",
|
||||
const QUICK_LOG_GROUPS: { label: string; types: EventType[] }[] = [
|
||||
{
|
||||
label: "Alimentation & Soins",
|
||||
types: ["BREASTFEED", "BOTTLE", "PUMP", "DIAPER_WET", "DIAPER_STOOL", "MEDICATION", "TEMPERATURE"],
|
||||
},
|
||||
{
|
||||
label: "Sommeil & Activités",
|
||||
types: ["NAP", "NIGHT_SLEEP", "BATH", "WALK", "TUMMY_TIME"],
|
||||
},
|
||||
];
|
||||
|
||||
interface Event {
|
||||
@@ -405,27 +401,31 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* Quick log */}
|
||||
<div>
|
||||
<h2 className="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3">Enregistrer</h2>
|
||||
<div className="grid grid-cols-4 md:grid-cols-4 gap-2">
|
||||
{QUICK_LOG_TYPES.map((type) => {
|
||||
const cfg = EVENT_CONFIG[type];
|
||||
return (
|
||||
<button
|
||||
key={type}
|
||||
onClick={() => setActiveModal(type)}
|
||||
className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 hover:border-slate-300 hover:shadow-sm rounded-xl p-3 flex flex-col items-center gap-2 transition active:scale-95"
|
||||
>
|
||||
<div className={`w-9 h-9 rounded-lg ${cfg.bgClass} flex items-center justify-center`}>
|
||||
<EventIcon name={cfg.icon} className={`w-5 h-5 ${cfg.colorClass}`} />
|
||||
</div>
|
||||
<span className="text-[10px] font-medium text-slate-600 dark:text-slate-300 text-center leading-tight">
|
||||
{cfg.label}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{QUICK_LOG_GROUPS.map((group) => (
|
||||
<div key={group.label}>
|
||||
<h2 className="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-2">{group.label}</h2>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{group.types.map((type) => {
|
||||
const cfg = EVENT_CONFIG[type];
|
||||
return (
|
||||
<button
|
||||
key={type}
|
||||
onClick={() => setActiveModal(type)}
|
||||
className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 hover:border-indigo-200 dark:hover:border-indigo-700 hover:shadow-sm rounded-xl p-2.5 flex flex-col items-center gap-1.5 transition active:scale-95"
|
||||
>
|
||||
<div className={`w-9 h-9 rounded-lg ${cfg.bgClass} flex items-center justify-center`}>
|
||||
<EventIcon name={cfg.icon} className={`w-5 h-5 ${cfg.colorClass}`} />
|
||||
</div>
|
||||
<span className="text-[10px] font-medium text-slate-600 dark:text-slate-300 text-center leading-tight">
|
||||
{cfg.label}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -483,9 +483,14 @@ export default function DashboardPage() {
|
||||
{/* Today's timeline */}
|
||||
{todayEvents.length > 0 && (
|
||||
<div className="mt-6">
|
||||
<h2 className="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3">
|
||||
Aujourd'hui · {todayEvents.length} événement{todayEvents.length > 1 ? "s" : ""}
|
||||
</h2>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h2 className="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider">
|
||||
Aujourd'hui · {todayEvents.length} événement{todayEvents.length > 1 ? "s" : ""}
|
||||
</h2>
|
||||
<a href="/timeline" className="text-xs text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
Voir tout →
|
||||
</a>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden divide-y divide-slate-100 dark:divide-slate-700">
|
||||
{todayEvents.slice(0, 10).map((ev) => {
|
||||
const cfg = EVENT_CONFIG[ev.type];
|
||||
|
||||
@@ -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 && (
|
||||
<div className="text-center py-16 text-slate-400 dark:text-slate-500">
|
||||
<p className="text-sm">Aucun profil médicament</p>
|
||||
<div className="w-12 h-12 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center mx-auto mb-3">
|
||||
<Pill className="w-6 h-6 opacity-30" />
|
||||
</div>
|
||||
<p className="text-sm font-medium">Aucun profil médicament</p>
|
||||
<p className="text-xs mt-1 opacity-70">Créez un profil pour suivre les intervalles de prise</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -340,6 +340,11 @@ export default function MilkPage() {
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{used.length > 20 && (
|
||||
<p className="text-center text-xs text-slate-400 dark:text-slate-500 py-2">
|
||||
… et {used.length - 20} lot{used.length - 20 > 1 ? "s" : ""} de plus
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -187,15 +187,15 @@ export default function StatsPage() {
|
||||
</div>
|
||||
|
||||
{/* Today summary */}
|
||||
<div className="grid grid-cols-3 gap-3 mb-6">
|
||||
<div className="grid grid-cols-3 gap-2 md:gap-3 mb-6">
|
||||
{[
|
||||
{ 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 }) => (
|
||||
<div key={label} className={`${bg} border ${border} rounded-xl p-4 text-center`}>
|
||||
<div className={`text-2xl font-bold ${color}`}>{value}</div>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 mt-1">{label} · aujourd'hui</div>
|
||||
<div key={label} className={`${bg} border ${border} rounded-xl p-3 md:p-4 text-center`}>
|
||||
<div className={`text-xl md:text-2xl font-bold ${color}`}>{value}</div>
|
||||
<div className="text-[10px] md:text-xs text-slate-500 dark:text-slate-400 mt-1 leading-tight">{label}<br className="md:hidden" /><span className="hidden md:inline"> · </span>aujourd'hui</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -213,7 +213,7 @@ export default function StatsPage() {
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-4 mb-4">
|
||||
<p className="text-sm font-medium text-slate-700 dark:text-slate-200 mb-3">Résumé du sommeil</p>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 mb-4">{periodSubtitle(days)}</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-3 gap-2 md:gap-4">
|
||||
<div>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Total</p>
|
||||
<p className="text-lg font-bold text-violet-600 leading-tight">
|
||||
@@ -239,7 +239,7 @@ export default function StatsPage() {
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-4 mb-4">
|
||||
<p className="text-sm font-medium text-slate-700 dark:text-slate-200 mb-3">Résumé des repas</p>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 mb-4">{periodSubtitle(days)}</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-3 gap-2 md:gap-4">
|
||||
<div>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Durée allaitement</p>
|
||||
<p className="text-lg font-bold text-rose-600 leading-tight">
|
||||
|
||||
Reference in New Issue
Block a user