'use client'; import { useState, useEffect } from 'react'; import { Box, Typography, Button, Paper, TextField, FormControl, InputLabel, Select, MenuItem, IconButton, Alert, CircularProgress, Card, CardContent, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Chip, Snackbar, } from '@mui/material'; import { FormSkeleton, ActivityListSkeleton } from '@/components/common/LoadingSkeletons'; import { ArrowBack, Refresh, Save, Delete, Bedtime, Hotel, DirectionsCar, Chair, Home, ChildCare, Add, } from '@mui/icons-material'; import { useRouter } from 'next/navigation'; import { AppShell } from '@/components/layouts/AppShell/AppShell'; import { ProtectedRoute } from '@/components/common/ProtectedRoute'; import { useAuth } from '@/lib/auth/AuthContext'; import { trackingApi, Activity } from '@/lib/api/tracking'; import { childrenApi, Child } from '@/lib/api/children'; import { motion } from 'framer-motion'; import { useLocalizedDate } from '@/hooks/useLocalizedDate'; import { useTranslation } from '@/hooks/useTranslation'; import { useDispatch, useSelector } from 'react-redux'; import { fetchChildren, selectChild, selectSelectedChild, childrenSelectors } from '@/store/slices/childrenSlice'; import { AppDispatch, RootState } from '@/store/store'; import ChildSelector from '@/components/common/ChildSelector'; interface SleepData { startTime: string; endTime?: string; quality: 'excellent' | 'good' | 'fair' | 'poor'; location: string; isOngoing?: boolean; } export default function SleepTrackPage() { const router = useRouter(); const { user } = useAuth(); const { t } = useTranslation('tracking'); const { formatDistanceToNow, format } = useLocalizedDate(); const dispatch = useDispatch(); // Redux state const children = useSelector((state: RootState) => childrenSelectors.selectAll(state)); const selectedChild = useSelector(selectSelectedChild); const familyId = user?.families?.[0]?.familyId; // Local state const [selectedChildIds, setSelectedChildIds] = useState([]); // Sleep state const [startTime, setStartTime] = useState( format(new Date(), "yyyy-MM-dd'T'HH:mm") ); const [endTime, setEndTime] = useState( format(new Date(), "yyyy-MM-dd'T'HH:mm") ); const [quality, setQuality] = useState<'excellent' | 'good' | 'fair' | 'poor'>('good'); const [location, setLocation] = useState('crib'); const [isOngoing, setIsOngoing] = useState(false); // Common state const [notes, setNotes] = useState(''); const [recentSleeps, setRecentSleeps] = useState([]); const [loading, setLoading] = useState(false); const [sleepsLoading, setSleepsLoading] = useState(false); const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); // Delete confirmation dialog const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [activityToDelete, setActivityToDelete] = useState(null); // Load children from Redux useEffect(() => { if (familyId && children.length === 0) { dispatch(fetchChildren(familyId)); } }, [familyId, dispatch, children.length]); // Sync selectedChildIds with Redux selectedChild useEffect(() => { if (selectedChild?.id) { setSelectedChildIds([selectedChild.id]); } }, [selectedChild]); // Load recent sleeps when child is selected useEffect(() => { if (selectedChild?.id) { loadRecentSleeps(); } }, [selectedChild?.id]); const loadRecentSleeps = async () => { if (!selectedChild?.id) return; try { setSleepsLoading(true); const activities = await trackingApi.getActivities(selectedChild.id, 'sleep'); // Sort by timestamp descending and take last 10 const sorted = activities.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() ).slice(0, 10); setRecentSleeps(sorted); } catch (err: any) { console.error('Failed to load recent sleeps:', err); } finally { setSleepsLoading(false); } }; const formatDuration = (start: string, end?: string) => { const startDate = new Date(start); const endDate = end ? new Date(end) : new Date(); const diffMs = endDate.getTime() - startDate.getTime(); if (diffMs < 0) return 'Invalid duration'; const hours = Math.floor(diffMs / (1000 * 60 * 60)); const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); if (hours === 0) { return `${minutes} minute${minutes !== 1 ? 's' : ''}`; } else if (minutes === 0) { return `${hours} hour${hours !== 1 ? 's' : ''}`; } else { return `${hours} hour${hours !== 1 ? 's' : ''} ${minutes} minute${minutes !== 1 ? 's' : ''}`; } }; const calculateDuration = () => { if (!startTime) return null; if (isOngoing) { return formatDuration(startTime); } if (!endTime) return null; const start = new Date(startTime); const end = new Date(endTime); if (end <= start) return null; return formatDuration(startTime, endTime); }; const setStartNow = () => { setStartTime(format(new Date(), "yyyy-MM-dd'T'HH:mm")); }; const setEndNow = () => { setEndTime(format(new Date(), "yyyy-MM-dd'T'HH:mm")); }; const handleSubmit = async () => { if (!selectedChild?.id) { setError('Please select a child'); return; } // Validation if (!startTime) { setError('Please enter start time'); return; } if (!isOngoing && !endTime) { setError('Please enter end time or mark as ongoing'); return; } if (!isOngoing && endTime) { const start = new Date(startTime); const end = new Date(endTime); if (end <= start) { setError('End time must be after start time'); return; } } try { setLoading(true); setError(null); const data: SleepData = { startTime, quality, location, isOngoing, }; if (!isOngoing && endTime) { data.endTime = endTime; } await trackingApi.createActivity(selectedChild.id, { type: 'sleep', timestamp: startTime, data, notes: notes || undefined, }); setSuccessMessage(t('sleep.success')); // Reset form resetForm(); // Reload recent sleeps await loadRecentSleeps(); } catch (err: any) { console.error('Failed to save sleep:', err); setError(err.response?.data?.message || 'Failed to save sleep'); } finally { setLoading(false); } }; const resetForm = () => { setStartTime(format(new Date(), "yyyy-MM-dd'T'HH:mm")); setEndTime(format(new Date(), "yyyy-MM-dd'T'HH:mm")); setQuality('good'); setLocation('crib'); setIsOngoing(false); setNotes(''); }; const handleDeleteClick = (activityId: string) => { setActivityToDelete(activityId); setDeleteDialogOpen(true); }; const handleDeleteConfirm = async () => { if (!activityToDelete) return; try { setLoading(true); await trackingApi.deleteActivity(activityToDelete); setSuccessMessage(t('sleep.deleted')); setDeleteDialogOpen(false); setActivityToDelete(null); await loadRecentSleeps(); } catch (err: any) { console.error('Failed to delete sleep:', err); setError(err.response?.data?.message || 'Failed to delete sleep'); } finally { setLoading(false); } }; const getLocationIcon = (loc: string) => { switch (loc) { case 'crib': return ; case 'bed': return ; case 'stroller': return ; case 'carrier': return ; case 'other': return ; default: return ; } }; const getQualityColor = (qual: string) => { switch (qual) { case 'excellent': return 'success'; case 'good': return 'primary'; case 'fair': return 'warning'; case 'poor': return 'error'; default: return 'default'; } }; const getSleepDetails = (activity: Activity) => { const data = activity.data as SleepData; const duration = data.endTime ? formatDuration(data.startTime, data.endTime) : data.isOngoing ? t('sleep.ongoing_duration', { duration: formatDuration(data.startTime) }) : 'No end time'; return `${duration} - ${data.location.charAt(0).toUpperCase() + data.location.slice(1)}`; }; const childrenLoading = useSelector((state: RootState) => state.children.loading); if (childrenLoading && children.length === 0) { return ( {t('sleep.title')} {t('sleep.title')} ); } if (!familyId || children.length === 0) { return ( {t('common.noChildrenAdded')} {t('common.noChildrenMessage')} ); } return ( router.back()} sx={{ mr: 2 }}> {t('sleep.title')} {error && ( setError(null)}> {error} )} {/* Child Selector */} {children.length > 0 && ( { setSelectedChildIds(childIds); if (childIds.length > 0) { dispatch(selectChild(childIds[0])); } }} mode="single" label={t('common.selectChild')} required /> )} {/* Main Form */} {/* Start Time */} {t('sleep.startTime')} setStartTime(e.target.value)} InputLabelProps={{ shrink: true }} required inputProps={{ 'aria-required': 'true', 'aria-labelledby': 'start-time-label', }} /> {/* Ongoing Checkbox */} {t('sleep.status.title')} {/* End Time */} {!isOngoing && ( {t('sleep.endTime')} setEndTime(e.target.value)} InputLabelProps={{ shrink: true }} required inputProps={{ 'aria-required': 'true', 'aria-labelledby': 'end-time-label', }} /> )} {/* Duration Display */} {calculateDuration() && ( )} {/* Sleep Quality */} {t('sleep.quality')} {/* Location */} {t('sleep.location')} {/* Common Notes Field */} setNotes(e.target.value)} sx={{ mb: 3 }} placeholder={t('sleep.placeholders.notes')} inputProps={{ 'aria-describedby': 'sleep-notes-helper', }} FormHelperTextProps={{ id: 'sleep-notes-helper', }} /> {/* Submit Button */} {/* Recent Sleeps */} {t('sleep.recentSleeps')} {sleepsLoading ? ( ) : recentSleeps.length === 0 ? ( {t('noEntries')} ) : ( {recentSleeps.map((activity, index) => { const data = activity.data as SleepData; // Skip activities with invalid data structure if (!data || !data.quality || !data.location) { console.warn('[Sleep] Activity missing required fields:', activity); return null; } return ( {getLocationIcon(data.location)} {t('sleep.title')} {getSleepDetails(activity)} {activity.notes && ( {activity.notes} )} handleDeleteClick(activity.id)} disabled={loading} > ); })} )} {/* Delete Confirmation Dialog */} setDeleteDialogOpen(false)} > {t('deleteEntry')} {t('confirmDelete')} {/* Success Snackbar */} setSuccessMessage(null)} message={successMessage} /> ); }