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:
@@ -159,7 +159,7 @@ export default function GrowthPage() {
|
||||
function openAddForm() {
|
||||
const last = logs.at(-1);
|
||||
setForm({
|
||||
date: "",
|
||||
date: format(new Date(), "yyyy-MM-dd"),
|
||||
weight: last?.weight ? (last.weight / 1000).toFixed(3) : "",
|
||||
height: last?.height ? String(last.height) : "",
|
||||
headCirc: last?.headCirc ? String(last.headCirc) : "",
|
||||
@@ -343,7 +343,7 @@ export default function GrowthPage() {
|
||||
step="0.1" min="30" max="120" placeholder="50" className={inputCls} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-slate-600 dark:text-slate-300 mb-1.5">PC (cm)</label>
|
||||
<label className="block text-xs font-medium text-slate-600 dark:text-slate-300 mb-1.5">PC — Périmètre crânien (cm)</label>
|
||||
<input type="number" value={form.headCirc} onChange={(e) => setForm((f) => ({ ...f, headCirc: e.target.value }))}
|
||||
step="0.1" min="20" max="60" placeholder="34" className={inputCls} />
|
||||
</div>
|
||||
@@ -377,7 +377,7 @@ export default function GrowthPage() {
|
||||
)}
|
||||
{latest.headCirc && (
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-4 text-center">
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 font-medium mb-1">Périmètre crânien</p>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 font-medium mb-1">Périmètre crânien (PC)</p>
|
||||
<p className="text-xl font-bold text-slate-900 dark:text-slate-100">{latest.headCirc} cm</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -405,13 +405,17 @@ export default function GrowthPage() {
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Weight + WHO */}
|
||||
{weightData.length > 1 && (
|
||||
{weightData.length > 0 && (
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-5">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-200">Poids (kg)</h3>
|
||||
<div className="flex items-center gap-3 text-xs text-slate-400 dark:text-slate-500">
|
||||
<span className="flex items-center gap-1"><span className="inline-block w-3 h-0.5 bg-indigo-600 rounded" />Bébé</span>
|
||||
<span className="flex items-center gap-1"><span className="inline-block w-3 h-0.5 bg-slate-300 rounded" />OMS P3–P97</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="inline-block w-3 h-0.5 bg-slate-300 rounded" />
|
||||
OMS P3–P97
|
||||
<span className="text-slate-300 dark:text-slate-600 text-[10px]" title="P50 = médiane : 50% des bébés ont un poids inférieur. P3–P97 représente la plage normale OMS.">ⓘ</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
@@ -431,7 +435,7 @@ export default function GrowthPage() {
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{/* Height */}
|
||||
{heightData.length > 1 && (
|
||||
{heightData.length > 0 && (
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-5">
|
||||
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-200 mb-4">Taille (cm)</h3>
|
||||
<ResponsiveContainer width="100%" height={160}>
|
||||
@@ -447,7 +451,7 @@ export default function GrowthPage() {
|
||||
)}
|
||||
|
||||
{/* Head circumference */}
|
||||
{headData.length > 1 && (
|
||||
{headData.length > 0 && (
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-5">
|
||||
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-200 mb-4">Périmètre crânien (cm)</h3>
|
||||
<ResponsiveContainer width="100%" height={160}>
|
||||
|
||||
Reference in New Issue
Block a user