fix: UX audit — code-review findings

- Photo client-side limit: 5MB → 30MB (matches server)
- OTHER event shows notes preview in dashboard + timeline summaries
- Stats: add "Activités par jour" chart (Bain/Balade/Plat ventre/Autre)
- Combined diaper (wet→stool): show color/amount pickers, pass metadata
- Quick-add FAB: 4-col → 5-col grid (14 types in 3 rows instead of 4)
- Stool color buttons: force dark text, readable on light bg in dark mode
- Reminders: mark taken (✓) sets lastSentAt, ↺ clears it; shows elapsed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 11:06:54 +02:00
parent da60efd7c1
commit 6dc09fb696
7 changed files with 111 additions and 16 deletions
+44 -1
View File
@@ -3,7 +3,9 @@
import { useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useBaby } from "@/contexts/baby-context";
import { Plus, Trash2, Bell, BellOff, Clock } from "lucide-react";
import { Plus, Trash2, Bell, BellOff, Clock, CheckCircle2, RotateCcw } from "lucide-react";
import { formatDistanceToNow } from "date-fns";
import { fr } from "date-fns/locale";
interface Reminder {
id: string;
@@ -72,6 +74,24 @@ export default function RemindersPage() {
qc.invalidateQueries({ queryKey: ["reminders"] });
}
async function markTaken(id: string) {
await fetch(`/api/reminders/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ lastSentAt: new Date().toISOString() }),
});
qc.invalidateQueries({ queryKey: ["reminders"] });
}
async function unmarkTaken(id: string) {
await fetch(`/api/reminders/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ lastSentAt: null }),
});
qc.invalidateQueries({ queryKey: ["reminders"] });
}
async function deleteReminder(id: string) {
if (!confirm("Supprimer ce rappel ?")) return;
await fetch(`/api/reminders/${id}`, { method: "DELETE" });
@@ -175,8 +195,31 @@ export default function RemindersPage() {
<span className="text-xs text-indigo-600 dark:text-indigo-400 ml-2">{nextDueLabel(r)}</span>
)}
</div>
{r.lastSentAt && (
<p className="text-xs text-green-600 dark:text-green-400 mt-0.5 flex items-center gap-1">
<CheckCircle2 className="w-3 h-3" />
Pris {formatDistanceToNow(new Date(r.lastSentAt), { locale: fr, addSuffix: true })}
</p>
)}
</div>
<div className="flex items-center gap-1.5 flex-shrink-0">
{r.lastSentAt ? (
<button
onClick={() => unmarkTaken(r.id)}
title="Annuler la prise"
className="w-8 h-8 flex items-center justify-center rounded-lg text-green-500 hover:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 transition"
>
<RotateCcw className="w-3.5 h-3.5" />
</button>
) : (
<button
onClick={() => markTaken(r.id)}
title="Marquer comme pris"
className="w-8 h-8 flex items-center justify-center rounded-lg text-slate-400 hover:text-green-600 hover:bg-green-50 dark:hover:bg-green-950 transition"
>
<CheckCircle2 className="w-4 h-4" />
</button>
)}
<button
onClick={() => toggleEnabled(r.id, r.enabled)}
className={`w-9 h-5 rounded-full transition relative overflow-hidden ${r.enabled ? "bg-green-500" : "bg-slate-200 dark:bg-slate-700"}`}