'use client'; import { useState, useEffect } from 'react'; import { Box, Typography, Button, Paper, TextField, FormControl, InputLabel, Select, MenuItem, IconButton, Alert, Tabs, Tab, CircularProgress, Card, CardContent, Divider, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Chip, Snackbar, } from '@mui/material'; import { ArrowBack, PlayArrow, Stop, Refresh, Save, Restaurant, LocalCafe, Fastfood, Delete, Edit, 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 { withErrorBoundary } from '@/components/common/ErrorFallbacks'; import { useAuth } from '@/lib/auth/AuthContext'; import { trackingApi, Activity } from '@/lib/api/tracking'; import { childrenApi, Child } from '@/lib/api/children'; import { VoiceInputButton } from '@/components/voice/VoiceInputButton'; import { FormSkeleton, ActivityListSkeleton } from '@/components/common/LoadingSkeletons'; import { motion } from 'framer-motion'; import { useLocalizedDate } from '@/hooks/useLocalizedDate'; import { useTranslation } from '@/hooks/useTranslation'; import { UnitInput } from '@/components/forms/UnitInput'; import { convertVolume, getUnitSymbol } from '@/lib/utils/unitConversion'; import { MeasurementSystem } from '@/hooks/useLocale'; 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 FeedingData { feedingType: 'breast' | 'bottle' | 'solid'; side?: 'left' | 'right' | 'both'; duration?: number; amount?: number; bottleType?: 'formula' | 'breastmilk' | 'other'; foodDescription?: string; amountDescription?: string; } function FeedingTrackPage() { const router = useRouter(); const { user } = useAuth(); const { t } = useTranslation('tracking'); const { formatDistanceToNow } = 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([]); const [feedingType, setFeedingType] = useState<'breast' | 'bottle' | 'solid'>('breast'); // Breastfeeding state const [side, setSide] = useState<'left' | 'right' | 'both'>('left'); const [duration, setDuration] = useState(0); const [isTimerRunning, setIsTimerRunning] = useState(false); const [timerSeconds, setTimerSeconds] = useState(0); // Bottle feeding state const [amount, setAmount] = useState(0); // Stored in ml (metric) const [bottleType, setBottleType] = useState<'formula' | 'breastmilk' | 'other'>('formula'); // Solid food state const [foodDescription, setFoodDescription] = useState(''); const [amountDescription, setAmountDescription] = useState(''); // Common state const [notes, setNotes] = useState(''); const [recentFeedings, setRecentFeedings] = useState([]); const [loading, setLoading] = useState(false); const [feedingsLoading, setFeedingsLoading] = 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 feedings when child is selected useEffect(() => { if (selectedChild?.id) { loadRecentFeedings(); } }, [selectedChild?.id]); // Timer effect useEffect(() => { let interval: NodeJS.Timeout; if (isTimerRunning) { interval = setInterval(() => { setTimerSeconds((prev) => prev + 1); }, 1000); } return () => clearInterval(interval); }, [isTimerRunning]); const loadRecentFeedings = async () => { if (!selectedChild?.id) return; try { setFeedingsLoading(true); const activities = await trackingApi.getActivities(selectedChild.id, 'feeding'); // 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); setRecentFeedings(sorted); } catch (err: any) { console.error('Failed to load recent feedings:', err); } finally { setFeedingsLoading(false); } }; const formatDuration = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; const startTimer = () => { setIsTimerRunning(true); }; const stopTimer = () => { setIsTimerRunning(false); setDuration(Math.floor(timerSeconds / 60)); }; const resetTimer = () => { setIsTimerRunning(false); setTimerSeconds(0); setDuration(0); }; const handleSubmit = async () => { if (!selectedChild?.id) { setError(t('common.selectChild')); return; } // Validation if (feedingType === 'breast' && duration === 0 && timerSeconds === 0) { setError(t('feeding.validation.durationRequired')); return; } if (feedingType === 'bottle' && !amount) { setError(t('feeding.validation.amountRequired')); return; } if (feedingType === 'solid' && !foodDescription) { setError(t('feeding.validation.foodRequired')); return; } try { setLoading(true); setError(null); const data: FeedingData = { feedingType, }; if (feedingType === 'breast') { data.side = side; data.duration = duration || Math.floor(timerSeconds / 60); } else if (feedingType === 'bottle') { data.amount = amount; // Already in ml (metric) data.bottleType = bottleType; } else if (feedingType === 'solid') { data.foodDescription = foodDescription; data.amountDescription = amountDescription; } await trackingApi.createActivity(selectedChild.id, { type: 'feeding', timestamp: new Date().toISOString(), data, notes: notes || undefined, }); setSuccessMessage(t('feeding.success')); // Reset form resetForm(); // Reload recent feedings await loadRecentFeedings(); } catch (err: any) { console.error('Failed to save feeding:', err); setError(err.response?.data?.message || t('feeding.error.saveFailed')); } finally { setLoading(false); } }; const resetForm = () => { setSide('left'); setDuration(0); setTimerSeconds(0); setIsTimerRunning(false); setAmount(0); setBottleType('formula'); setFoodDescription(''); setAmountDescription(''); setNotes(''); }; const handleDeleteClick = (activityId: string) => { setActivityToDelete(activityId); setDeleteDialogOpen(true); }; const handleDeleteConfirm = async () => { if (!activityToDelete) return; try { setLoading(true); await trackingApi.deleteActivity(activityToDelete); setSuccessMessage(t('feeding.deleted')); setDeleteDialogOpen(false); setActivityToDelete(null); await loadRecentFeedings(); } catch (err: any) { console.error('Failed to delete feeding:', err); setError(err.response?.data?.message || t('feeding.error.deleteFailed')); } finally { setLoading(false); } }; const getFeedingTypeIcon = (type: string) => { switch (type) { case 'breast': return ; case 'bottle': return ; case 'solid': return ; default: return ; } }; const getFeedingDetails = (activity: Activity) => { const data = activity.data as FeedingData; if (data.feedingType === 'breast') { return `${data.side?.toUpperCase()} - ${data.duration || 0} min`; } else if (data.feedingType === 'bottle') { // Convert amount based on user preference const measurementSystem: MeasurementSystem = (user?.preferences?.measurementUnit as MeasurementSystem) || 'metric'; const converted = convertVolume(data.amount || 0, measurementSystem); const roundedValue = Math.round(converted.value * 10) / 10; // Round to 1 decimal return `${roundedValue} ${converted.unit} - ${data.bottleType}`; } else if (data.feedingType === 'solid') { return `${data.foodDescription}${data.amountDescription ? ` - ${data.amountDescription}` : ''}`; } return ''; }; const childrenLoading = useSelector((state: RootState) => state.children.loading); if (childrenLoading && children.length === 0) { return ( {t('feeding.title')} {t('feeding.title')} ); } if (!familyId || children.length === 0) { return ( {t('common.noChildrenAdded')} {t('common.noChildrenMessage')} ); } return ( router.back()} sx={{ mr: 2 }}> {t('feeding.title')} { console.log('[Feeding] Voice transcript:', transcript); }} onClassifiedIntent={(result) => { if (result.intent === 'feeding' && result.structuredData) { const data = result.structuredData; // Auto-fill form with voice data if (data.type === 'bottle' && data.amount) { setFeedingType('bottle'); setAmount(typeof data.amount === 'number' ? data.amount : parseFloat(data.amount)); } else if (data.type?.includes('breast')) { setFeedingType('breast'); if (data.side) setSide(data.side); if (data.duration) setDuration(data.duration.toString()); } else if (data.type === 'solid') { setFeedingType('solid'); } } }} size="medium" /> {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 */} {/* Feeding Type Tabs */} setFeedingType(newValue)} sx={{ mb: 3 }} variant="fullWidth" > } iconPosition="start" /> } iconPosition="start" /> } iconPosition="start" /> {/* Breastfeeding Form */} {feedingType === 'breast' && ( {/* Timer Display */} {formatDuration(timerSeconds)} {!isTimerRunning ? ( ) : ( )} {/* Side Selector */} {t('feeding.side')} {/* Manual Duration Input */} setDuration(parseInt(e.target.value) || 0)} sx={{ mb: 3 }} helperText={t('feeding.placeholders.duration')} inputProps={{ 'aria-describedby': 'duration-helper', min: 0, }} FormHelperTextProps={{ id: 'duration-helper', }} /> )} {/* Bottle Form */} {feedingType === 'bottle' && ( setAmount(metricValue)} sx={{ mb: 3 }} /> {t('feeding.bottleType')} )} {/* Solid Food Form */} {feedingType === 'solid' && ( setFoodDescription(e.target.value)} sx={{ mb: 3 }} placeholder={t('feeding.placeholders.foodDescription')} required inputProps={{ 'aria-required': 'true', 'aria-invalid': !foodDescription && error ? 'true' : 'false', }} /> setAmountDescription(e.target.value)} sx={{ mb: 3 }} placeholder={t('feeding.placeholders.amountDescription')} inputProps={{ 'aria-describedby': 'amount-description-helper', }} FormHelperTextProps={{ id: 'amount-description-helper', }} /> )} {/* Common Notes Field */} setNotes(e.target.value)} sx={{ mb: 3 }} placeholder={t('feeding.placeholders.notes')} inputProps={{ 'aria-describedby': 'notes-helper', }} FormHelperTextProps={{ id: 'notes-helper', }} /> {/* Submit Button */} {/* Recent Feedings */} {t('feeding.recentFeedings')} {feedingsLoading ? ( ) : recentFeedings.length === 0 ? ( {t('noEntries')} ) : ( {recentFeedings.map((activity, index) => { const data = activity.data as FeedingData; // Skip activities with invalid data structure if (!data || !data.feedingType) { console.warn('[Feeding] Activity missing feedingType:', activity); return null; } return ( {getFeedingTypeIcon(data.feedingType)} {data.feedingType.charAt(0).toUpperCase() + data.feedingType.slice(1)} {getFeedingDetails(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} /> ); } export default withErrorBoundary(FeedingTrackPage, 'form');