mirror of
https://github.com/Oak-and-Sprout/sprout-track.git
synced 2026-05-05 14:39:56 -05:00
fixed hydration error
This commit is contained in:
+13
-8
@@ -1,19 +1,24 @@
|
||||
import type { Metadata } from 'next'
|
||||
import './globals.css'
|
||||
'use client';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Baby Tracker',
|
||||
description: 'Track your baby\'s activities, milestones, and development',
|
||||
}
|
||||
import { useEffect, useState } from 'react';
|
||||
import './globals.css'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body suppressHydrationWarning>
|
||||
{mounted ? children : null}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Baby Tracker',
|
||||
description: 'Track your baby\'s activities, milestones, and development',
|
||||
}
|
||||
+96
-68
@@ -40,68 +40,100 @@ export default function Home() {
|
||||
const [activities, setActivities] = useState<ActivityType[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// Fetch babies on mount
|
||||
useEffect(() => {
|
||||
const fetchBabies = async () => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/baby');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setBabies(data.data);
|
||||
// If there's only one baby, select it automatically
|
||||
if (data.data.length === 1) {
|
||||
setSelectedBabyId(data.data[0].id);
|
||||
setSelectedBaby(data.data[0]);
|
||||
}
|
||||
}
|
||||
// Fetch babies
|
||||
const babyResponse = await fetch('/api/baby');
|
||||
if (!babyResponse.ok) return;
|
||||
|
||||
const babyData = await babyResponse.json();
|
||||
if (!babyData.success) return;
|
||||
|
||||
setBabies(babyData.data);
|
||||
|
||||
// If there's only one baby, select it automatically
|
||||
if (babyData.data.length === 1) {
|
||||
const baby = babyData.data[0];
|
||||
setSelectedBabyId(baby.id);
|
||||
setSelectedBaby(baby);
|
||||
|
||||
// Fetch activities for the selected baby
|
||||
const [sleepResponse, feedResponse, diaperResponse] = await Promise.all([
|
||||
fetch(`/api/sleep-log?babyId=${baby.id}`),
|
||||
fetch(`/api/feed-log?babyId=${baby.id}`),
|
||||
fetch(`/api/diaper-log?babyId=${baby.id}`)
|
||||
]);
|
||||
|
||||
const [sleepData, feedData, diaperData] = await Promise.all([
|
||||
sleepResponse.json(),
|
||||
feedResponse.json(),
|
||||
diaperResponse.json()
|
||||
]);
|
||||
|
||||
const allActivities = [
|
||||
...(sleepData.success ? sleepData.data : []),
|
||||
...(feedData.success ? feedData.data : []),
|
||||
...(diaperData.success ? diaperData.data : [])
|
||||
].map(activity => ({
|
||||
...activity,
|
||||
time: activity.time ? new Date(activity.time).toISOString() : undefined,
|
||||
startTime: activity.startTime ? new Date(activity.startTime).toISOString() : undefined,
|
||||
endTime: activity.endTime ? new Date(activity.endTime).toISOString() : undefined,
|
||||
}));
|
||||
|
||||
setActivities(allActivities);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching babies:', error);
|
||||
console.error('Error fetching data:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchBabies();
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
// Fetch activities when selected baby changes
|
||||
useEffect(() => {
|
||||
const fetchActivities = async () => {
|
||||
if (!selectedBaby) return;
|
||||
|
||||
const handleBabySelect = async (babyId: string) => {
|
||||
setIsLoading(true);
|
||||
setSelectedBabyId(babyId);
|
||||
const selected = babies.find(b => b.id === babyId);
|
||||
setSelectedBaby(selected || null);
|
||||
|
||||
if (selected) {
|
||||
try {
|
||||
// Fetch sleep logs
|
||||
const sleepResponse = await fetch(`/api/sleep-log?babyId=${selectedBaby.id}`);
|
||||
const sleepData = await sleepResponse.json();
|
||||
const sleepLogs = sleepData.success ? sleepData.data : [];
|
||||
|
||||
// Fetch feed logs
|
||||
const feedResponse = await fetch(`/api/feed-log?babyId=${selectedBaby.id}`);
|
||||
const feedData = await feedResponse.json();
|
||||
const feedLogs = feedData.success ? feedData.data : [];
|
||||
|
||||
// Fetch diaper logs
|
||||
const diaperResponse = await fetch(`/api/diaper-log?babyId=${selectedBaby.id}`);
|
||||
const diaperData = await diaperResponse.json();
|
||||
const diaperLogs = diaperData.success ? diaperData.data : [];
|
||||
|
||||
// Combine all activities
|
||||
const [sleepResponse, feedResponse, diaperResponse] = await Promise.all([
|
||||
fetch(`/api/sleep-log?babyId=${babyId}`),
|
||||
fetch(`/api/feed-log?babyId=${babyId}`),
|
||||
fetch(`/api/diaper-log?babyId=${babyId}`)
|
||||
]);
|
||||
|
||||
const [sleepData, feedData, diaperData] = await Promise.all([
|
||||
sleepResponse.json(),
|
||||
feedResponse.json(),
|
||||
diaperResponse.json()
|
||||
]);
|
||||
|
||||
const allActivities = [
|
||||
...sleepLogs,
|
||||
...feedLogs,
|
||||
...diaperLogs,
|
||||
];
|
||||
|
||||
...(sleepData.success ? sleepData.data : []),
|
||||
...(feedData.success ? feedData.data : []),
|
||||
...(diaperData.success ? diaperData.data : [])
|
||||
].map(activity => ({
|
||||
...activity,
|
||||
time: activity.time ? new Date(activity.time).toISOString() : undefined,
|
||||
startTime: activity.startTime ? new Date(activity.startTime).toISOString() : undefined,
|
||||
endTime: activity.endTime ? new Date(activity.endTime).toISOString() : undefined,
|
||||
}));
|
||||
|
||||
setActivities(allActivities);
|
||||
} catch (error) {
|
||||
console.error('Error fetching activities:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchActivities();
|
||||
}, [selectedBaby]);
|
||||
} else {
|
||||
setActivities([]);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
// Quick action buttons configuration
|
||||
const quickActions = [
|
||||
@@ -126,12 +158,6 @@ export default function Home() {
|
||||
},
|
||||
];
|
||||
|
||||
const handleBabySelect = (babyId: string) => {
|
||||
setSelectedBabyId(babyId);
|
||||
const baby = babies.find(b => b.id === babyId);
|
||||
setSelectedBaby(baby || null);
|
||||
};
|
||||
|
||||
const handleBabyModalClose = async () => {
|
||||
setShowBabyModal(false);
|
||||
// Refresh babies list
|
||||
@@ -193,25 +219,27 @@ export default function Home() {
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{quickActions.map((action, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
|
||||
action.active ? 'bg-primary text-primary-foreground' : ''
|
||||
} ${action.disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
onClick={action.disabled ? undefined : action.onClick}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center space-y-2">
|
||||
{action.icon}
|
||||
<span className="text-sm font-medium">{action.label}</span>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
{!isLoading && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{quickActions.map((action, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className={`p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
|
||||
action.active ? 'bg-primary text-primary-foreground' : ''
|
||||
} ${action.disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
onClick={action.disabled ? undefined : action.onClick}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center space-y-2">
|
||||
{action.icon}
|
||||
<span className="text-sm font-medium">{action.label}</span>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Timeline */}
|
||||
{selectedBaby && <Timeline activities={activities} />}
|
||||
{!isLoading && selectedBaby && <Timeline activities={activities} />}
|
||||
|
||||
{/* Modals */}
|
||||
<BabyModal
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { metadata } from './metadata'
|
||||
|
||||
export default function Template({ children }: { children: React.ReactNode }) {
|
||||
return children;
|
||||
}
|
||||
+41
-32
@@ -55,14 +55,14 @@ const getActivityDescription = (activity: ActivityType) => {
|
||||
return 'Activity logged';
|
||||
};
|
||||
|
||||
const getActivityTime = (activity: ActivityType) => {
|
||||
const getActivityTime = (activity: ActivityType): Date => {
|
||||
if ('time' in activity) {
|
||||
return activity.time;
|
||||
return new Date(activity.time);
|
||||
}
|
||||
if ('startTime' in activity) {
|
||||
return activity.startTime;
|
||||
return new Date(activity.startTime);
|
||||
}
|
||||
return new Date();
|
||||
return new Date(); // This should never happen as all activities should have a timestamp
|
||||
};
|
||||
|
||||
export default function Timeline({ activities }: TimelineProps) {
|
||||
@@ -71,6 +71,12 @@ export default function Timeline({ activities }: TimelineProps) {
|
||||
getActivityTime(b).getTime() - getActivityTime(a).getTime()
|
||||
);
|
||||
|
||||
const formatTime = (date: Date) => {
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold">Recent Activity</h2>
|
||||
@@ -80,36 +86,39 @@ export default function Timeline({ activities }: TimelineProps) {
|
||||
No activities logged yet
|
||||
</Card>
|
||||
) : (
|
||||
sortedActivities.map((activity) => (
|
||||
<Card key={activity.id} className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="p-2 bg-gray-100 rounded-full">
|
||||
{getActivityIcon(activity)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{getActivityDescription(activity)}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{new Date(getActivityTime(activity)).toLocaleTimeString()}
|
||||
</p>
|
||||
sortedActivities.map((activity) => {
|
||||
const activityTime = getActivityTime(activity);
|
||||
return (
|
||||
<Card key={activity.id} className="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="p-2 bg-gray-100 rounded-full">
|
||||
{getActivityIcon(activity)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{getActivityDescription(activity)}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{formatTime(activityTime)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="p-2 hover:bg-gray-100 rounded-full">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600">
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="p-2 hover:bg-gray-100 rounded-full">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600">
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</Card>
|
||||
))
|
||||
</Card>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user