New foundations: - src/types/index.ts — shared domain types (Child, Log, GrowthRecord, Medicine, Dose, Allergy, Visit, Illness, Vaccination, AIChat, ChatSession, Goal) - src/lib/formatting.ts — calculateAge, formatAge, formatTimeAgo (eliminates 3 duplicate implementations spread across page.tsx and growth/page.tsx) - src/lib/api.ts — typed fetch helpers (api.get/post/patch/delete) with consistent error handling; replaces manual fetch boilerplate New shared components: - src/components/PageHeader.tsx — reusable back-link + title header - src/components/TabBar.tsx — horizontal pill tab bar - src/components/CalendarView.tsx — extracted from activity/page.tsx (was ~170 inline lines) - src/components/medical/ — medical page split into 5 focused tab components: VaccineTab, MedicineTab, AllergyTab, VisitTab, IllnessTab Pages updated: - medical/page.tsx: 1029 → 42 lines (thin shell wiring the 5 tab components) - activity/page.tsx: uses CalendarView + shared Log type + api.ts - growth/page.tsx: uses shared GrowthRecord/Goal types + formatAge; fixes `any` catch clauses; fixes undefined → null in Chart.js dataset values - page.tsx (home): uses shared Log/AIChat/ChatSession types + formatTimeAgo/ calculateAge from formatting.ts; removes inline type definitions - ai/page.tsx: uses shared AIChat/ChatSession types - FamilyProvider.tsx: uses shared Child type; fixes `c: any` mapping Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
42 lines
1.7 KiB
TypeScript
42 lines
1.7 KiB
TypeScript
export function calculateAge(birthDate: string): string {
|
|
if (!birthDate) return "";
|
|
const birth = new Date(birthDate);
|
|
const now = new Date();
|
|
let years = now.getFullYear() - birth.getFullYear();
|
|
let months = now.getMonth() - birth.getMonth();
|
|
let days = now.getDate() - birth.getDate();
|
|
if (days < 0) {
|
|
months--;
|
|
days += new Date(now.getFullYear(), now.getMonth(), 0).getDate();
|
|
}
|
|
if (months < 0) { years--; months += 12; }
|
|
const parts: string[] = [];
|
|
if (years > 0) parts.push(`${years} year${years > 1 ? "s" : ""}`);
|
|
if (months > 0) parts.push(`${months} month${months > 1 ? "s" : ""}`);
|
|
if (days > 0) parts.push(`${days} day${days > 1 ? "s" : ""}`);
|
|
return parts.length > 0 ? parts.join(", ") : "Newborn";
|
|
}
|
|
|
|
export function formatAge(birthDate: string, measurementDate?: string): string {
|
|
const birth = new Date(birthDate);
|
|
const ref = measurementDate ? new Date(measurementDate) : new Date();
|
|
const totalDays = (ref.getTime() - birth.getTime()) / (1000 * 60 * 60 * 24);
|
|
const years = Math.floor(totalDays / 365);
|
|
const months = Math.floor((totalDays % 365) / 30);
|
|
if (years > 0 && months > 0) return `${years}y ${months}mo`;
|
|
if (years > 0) return `${years}y`;
|
|
if (months > 0) return `${months}mo`;
|
|
return "Newborn";
|
|
}
|
|
|
|
export function formatTimeAgo(dateStr: string | null | undefined): string | null {
|
|
if (!dateStr) return null;
|
|
const diffMs = Date.now() - new Date(dateStr).getTime();
|
|
const mins = Math.floor(diffMs / 60_000);
|
|
const hours = Math.floor(mins / 60);
|
|
const days = Math.floor(hours / 24);
|
|
if (mins < 1) return "just now";
|
|
if (mins < 60) return `${mins}m ago`;
|
|
if (hours < 24) return `${hours}h ago`;
|
|
return `${days}d ago`;
|
|
}
|