From b0ac2f71df75964fd5153f1530d6fd93a05e76fe Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 6 Oct 2025 11:46:05 +0000 Subject: [PATCH] feat: Add advanced analytics UI components in frontend - Add comprehensive API client methods for all advanced analytics endpoints - Create CircadianRhythmCard component for sleep pattern visualization - Create AnomalyAlertsPanel for anomaly detection and alerts - Create GrowthPercentileChart with WHO/CDC percentiles - Create CorrelationInsights for activity correlations - Create TrendAnalysisChart with predictions - Add advanced analytics page with all new components - Add UI component library (shadcn/ui) setup - Add navigation link to advanced analytics from insights page All advanced analytics features are now accessible from the frontend UI. --- maternal-web/app/analytics/advanced/page.tsx | 365 ++++++++ .../analytics/AnomalyAlertsPanel.tsx | 242 +++++ .../analytics/CircadianRhythmCard.tsx | 197 +++++ .../analytics/CorrelationInsights.tsx | 235 +++++ .../analytics/GrowthPercentileChart.tsx | 307 +++++++ .../analytics/TrendAnalysisChart.tsx | 273 ++++++ .../analytics/UnifiedInsightsDashboard.tsx | 33 +- .../components/layouts/TabBar/TabBar.tsx | 2 +- maternal-web/components/ui/alert.tsx | 59 ++ maternal-web/components/ui/badge.tsx | 36 + maternal-web/components/ui/button.tsx | 56 ++ maternal-web/components/ui/card.tsx | 79 ++ maternal-web/components/ui/progress.tsx | 28 + maternal-web/components/ui/tabs.tsx | 55 ++ maternal-web/lib/api/analytics.ts | 304 +++++++ maternal-web/lib/utils.ts | 6 + maternal-web/package-lock.json | 837 ++++++++++++++++++ maternal-web/package.json | 9 + maternal-web/public/sw.js | 2 +- 19 files changed, 3112 insertions(+), 13 deletions(-) create mode 100644 maternal-web/app/analytics/advanced/page.tsx create mode 100644 maternal-web/components/analytics/AnomalyAlertsPanel.tsx create mode 100644 maternal-web/components/analytics/CircadianRhythmCard.tsx create mode 100644 maternal-web/components/analytics/CorrelationInsights.tsx create mode 100644 maternal-web/components/analytics/GrowthPercentileChart.tsx create mode 100644 maternal-web/components/analytics/TrendAnalysisChart.tsx create mode 100644 maternal-web/components/ui/alert.tsx create mode 100644 maternal-web/components/ui/badge.tsx create mode 100644 maternal-web/components/ui/button.tsx create mode 100644 maternal-web/components/ui/card.tsx create mode 100644 maternal-web/components/ui/progress.tsx create mode 100644 maternal-web/components/ui/tabs.tsx create mode 100644 maternal-web/lib/utils.ts diff --git a/maternal-web/app/analytics/advanced/page.tsx b/maternal-web/app/analytics/advanced/page.tsx new file mode 100644 index 0000000..499f7ac --- /dev/null +++ b/maternal-web/app/analytics/advanced/page.tsx @@ -0,0 +1,365 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useAuth } from '@/lib/auth/AuthContext'; +import { childrenApi, Child } from '@/lib/api/children'; +import { + analyticsApi, + CircadianRhythm, + AnomalyDetection, + GrowthAnalysis, + CorrelationAnalysis, + TrendAnalysis, +} from '@/lib/api/analytics'; +import { CircadianRhythmCard } from '@/components/analytics/CircadianRhythmCard'; +import { AnomalyAlertsPanel } from '@/components/analytics/AnomalyAlertsPanel'; +import { GrowthPercentileChart } from '@/components/analytics/GrowthPercentileChart'; +import { CorrelationInsights } from '@/components/analytics/CorrelationInsights'; +import { TrendAnalysisChart } from '@/components/analytics/TrendAnalysisChart'; +import { + Card, + CardContent, + CardHeader, + Button, + Alert, + AlertTitle, + Tabs, + Tab, + Box, + Select, + MenuItem, + FormControl, + InputLabel, +} from '@mui/material'; +import { Loader2, RefreshCw, Activity, Brain, TrendingUp, Baby, Link } from 'lucide-react'; +import { AppShell } from '@/components/layouts/AppShell/AppShell'; +import { ProtectedRoute } from '@/components/common/ProtectedRoute'; + +export default function AdvancedAnalyticsPage() { + const { user } = useAuth(); + const [children, setChildren] = useState([]); + const [selectedChildId, setSelectedChildId] = useState(''); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + // Analytics data states + const [circadianData, setCircadianData] = useState(null); + const [anomalyData, setAnomalyData] = useState(null); + const [growthData, setGrowthData] = useState(null); + const [correlationData, setCorrelationData] = useState(null); + const [sleepTrendData, setSleepTrendData] = useState(null); + const [feedingTrendData, setFeedingTrendData] = useState(null); + + // Loading states for each component + const [circadianLoading, setCircadianLoading] = useState(false); + const [anomalyLoading, setAnomalyLoading] = useState(false); + const [growthLoading, setGrowthLoading] = useState(false); + const [correlationLoading, setCorrelationLoading] = useState(false); + const [trendLoading, setTrendLoading] = useState(false); + + // Error states for each component + const [circadianError, setCircadianError] = useState(null); + const [anomalyError, setAnomalyError] = useState(null); + const [growthError, setGrowthError] = useState(null); + const [correlationError, setCorrelationError] = useState(null); + const [trendError, setTrendError] = useState(null); + + const familyId = user?.families?.[0]?.familyId; + + useEffect(() => { + if (familyId) { + loadChildren(); + } + }, [familyId]); + + useEffect(() => { + if (selectedChildId && children.length > 0) { + const childExists = children.some(child => child.id === selectedChildId); + if (childExists) { + loadAllAnalytics(); + } else { + console.warn('[AdvancedAnalytics] Selected child not found, resetting'); + setSelectedChildId(children[0].id); + } + } + }, [selectedChildId, children]); + + const loadChildren = async () => { + if (!familyId) { + setLoading(false); + setError('No family found'); + return; + } + + try { + console.log('[AdvancedAnalytics] Loading children for familyId:', familyId); + const data = await childrenApi.getChildren(familyId); + console.log('[AdvancedAnalytics] Loaded children:', data); + setChildren(data); + + if (data.length > 0) { + const existingChildStillValid = data.some(child => child.id === selectedChildId); + if (!selectedChildId || !existingChildStillValid) { + setSelectedChildId(data[0].id); + } + } + setError(''); + } catch (error) { + console.error('[AdvancedAnalytics] Failed to load children:', error); + setError('Failed to load children'); + } finally { + setLoading(false); + } + }; + + const loadAllAnalytics = () => { + loadCircadianRhythm(); + loadAnomalies(); + loadGrowthAnalysis(); + loadCorrelations(); + loadTrends(); + }; + + const loadCircadianRhythm = async () => { + if (!selectedChildId) return; + + setCircadianLoading(true); + setCircadianError(null); + try { + const data = await analyticsApi.getCircadianRhythm(selectedChildId, 14); + setCircadianData(data); + } catch (error) { + console.error('[AdvancedAnalytics] Failed to load circadian rhythm:', error); + setCircadianError(error as Error); + } finally { + setCircadianLoading(false); + } + }; + + const loadAnomalies = async () => { + if (!selectedChildId) return; + + setAnomalyLoading(true); + setAnomalyError(null); + try { + const data = await analyticsApi.getAnomalies(selectedChildId, 30); + setAnomalyData(data); + } catch (error) { + console.error('[AdvancedAnalytics] Failed to load anomalies:', error); + setAnomalyError(error as Error); + } finally { + setAnomalyLoading(false); + } + }; + + const loadGrowthAnalysis = async () => { + if (!selectedChildId) return; + + setGrowthLoading(true); + setGrowthError(null); + try { + const data = await analyticsApi.getGrowthAnalysis(selectedChildId); + setGrowthData(data); + } catch (error) { + console.error('[AdvancedAnalytics] Failed to load growth analysis:', error); + setGrowthError(error as Error); + } finally { + setGrowthLoading(false); + } + }; + + const loadCorrelations = async () => { + if (!selectedChildId) return; + + setCorrelationLoading(true); + setCorrelationError(null); + try { + const data = await analyticsApi.getCorrelations(selectedChildId, 14); + setCorrelationData(data); + } catch (error) { + console.error('[AdvancedAnalytics] Failed to load correlations:', error); + setCorrelationError(error as Error); + } finally { + setCorrelationLoading(false); + } + }; + + const loadTrends = async () => { + if (!selectedChildId) return; + + setTrendLoading(true); + setTrendError(null); + try { + const [sleepTrend, feedingTrend] = await Promise.all([ + analyticsApi.getTrends(selectedChildId, 'sleep'), + analyticsApi.getTrends(selectedChildId, 'feeding'), + ]); + setSleepTrendData(sleepTrend); + setFeedingTrendData(feedingTrend); + } catch (error) { + console.error('[AdvancedAnalytics] Failed to load trends:', error); + setTrendError(error as Error); + } finally { + setTrendLoading(false); + } + }; + + if (loading) { + return ( + + +
+ +
+
+
+ ); + } + + if (children.length === 0) { + return ( + + +
+ + No Children Found + + Add a child to your family to view advanced analytics. + + +
+
+
+ ); + } + + return ( + + +
+ {/* Header */} +
+
+

Advanced Analytics

+

+ AI-powered insights and deep analysis of your child's patterns +

+
+ +
+ + {/* Child Selector */} + + +
+ + +
+
+
+ + {error && ( + + Error + {error} + + )} + + {/* Analytics Tabs */} + + + + + Sleep Rhythm + + + + Anomalies + + + + Growth + + + + Correlations + + + + Trends + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/maternal-web/components/analytics/AnomalyAlertsPanel.tsx b/maternal-web/components/analytics/AnomalyAlertsPanel.tsx new file mode 100644 index 0000000..e2e9ebd --- /dev/null +++ b/maternal-web/components/analytics/AnomalyAlertsPanel.tsx @@ -0,0 +1,242 @@ +'use client'; + +import React, { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Button } from '@/components/ui/button'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { + AlertTriangle, + AlertCircle, + Info, + TrendingUp, + Activity, + Clock, + ChevronRight, +} from 'lucide-react'; +import { AnomalyDetection } from '@/lib/api/analytics'; +import { formatDistanceToNow } from 'date-fns'; + +interface AnomalyAlertsPanelProps { + data: AnomalyDetection | null; + loading?: boolean; + error?: Error | null; +} + +export function AnomalyAlertsPanel({ data, loading, error }: AnomalyAlertsPanelProps) { + const [expandedAnomaly, setExpandedAnomaly] = useState(null); + + if (loading) { + return ( + + +
Analyzing patterns for anomalies...
+
+
+ ); + } + + if (error) { + return ( + + + + Error loading anomaly detection data + + + ); + } + + if (!data) { + return null; + } + + const getSeverityColor = (severity: string) => { + switch (severity) { + case 'high': + case 'critical': + return 'bg-red-100 text-red-800 border-red-300'; + case 'medium': + case 'warning': + return 'bg-yellow-100 text-yellow-800 border-yellow-300'; + case 'low': + case 'info': + return 'bg-blue-100 text-blue-800 border-blue-300'; + default: + return 'bg-gray-100 text-gray-800 border-gray-300'; + } + }; + + const getSeverityIcon = (severity: string) => { + switch (severity) { + case 'high': + case 'critical': + return ; + case 'medium': + case 'warning': + return ; + default: + return ; + } + }; + + const criticalAlerts = data.alerts.filter(a => a.severity === 'critical'); + const warningAlerts = data.alerts.filter(a => a.severity === 'warning'); + const infoAlerts = data.alerts.filter(a => a.severity === 'info'); + + return ( + + + + + + Anomaly Detection + +
+ Confidence + + {Math.round(data.confidenceScore * 100)}% + +
+
+
+ + {/* Confidence Score Bar */} +
+ +

+ Analysis based on {data.anomalies.length} detected patterns +

+
+ + {/* Alert Summary */} + {(criticalAlerts.length > 0 || warningAlerts.length > 0 || infoAlerts.length > 0) && ( +
+ {criticalAlerts.length > 0 && ( +
+ + + {criticalAlerts.length} Critical + +
+ )} + {warningAlerts.length > 0 && ( +
+ + + {warningAlerts.length} Warning + +
+ )} + {infoAlerts.length > 0 && ( +
+ + + {infoAlerts.length} Info + +
+ )} +
+ )} + + + + Alerts ({data.alerts.length}) + Anomalies ({data.anomalies.length}) + + + + {data.alerts.length === 0 ? ( +
+ No alerts detected - everything looks normal! +
+ ) : ( + data.alerts.map((alert, index) => ( + +
+ {getSeverityIcon(alert.severity)} +
+ {alert.type} + {alert.message} + {alert.recommendations && alert.recommendations.length > 0 && ( +
+

Recommendations:

+
    + {alert.recommendations.map((rec, idx) => ( +
  • + + {rec} +
  • + ))} +
+
+ )} +
+
+
+ )) + )} +
+ + + {data.anomalies.length === 0 ? ( +
+ No anomalies detected in recent activities +
+ ) : ( + data.anomalies.map((anomaly) => ( +
setExpandedAnomaly( + expandedAnomaly === anomaly.activityId ? null : anomaly.activityId + )} + > +
+
+ + {anomaly.severity} + +
+

+ {anomaly.type} Activity +

+

+ + {formatDistanceToNow(anomaly.timestamp, { addSuffix: true })} +

+
+
+
+

Deviation

+

+ {anomaly.deviation.toFixed(1)}σ +

+
+
+ + {expandedAnomaly === anomaly.activityId && ( +
+

{anomaly.description}

+
+ + + Statistical deviation: {anomaly.deviation.toFixed(2)} standard deviations from normal + +
+
+ )} +
+ )) + )} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/maternal-web/components/analytics/CircadianRhythmCard.tsx b/maternal-web/components/analytics/CircadianRhythmCard.tsx new file mode 100644 index 0000000..21f988b --- /dev/null +++ b/maternal-web/components/analytics/CircadianRhythmCard.tsx @@ -0,0 +1,197 @@ +'use client'; + +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { Badge } from '@/components/ui/badge'; +import { Moon, Sun, Clock, Brain, AlertCircle, CheckCircle2 } from 'lucide-react'; +import { CircadianRhythm } from '@/lib/api/analytics'; + +interface CircadianRhythmCardProps { + data: CircadianRhythm | null; + loading?: boolean; + error?: Error | null; +} + +export function CircadianRhythmCard({ data, loading, error }: CircadianRhythmCardProps) { + if (loading) { + return ( + + +
Loading circadian rhythm analysis...
+
+
+ ); + } + + if (error) { + return ( + + + + Error loading circadian rhythm data + + + ); + } + + if (!data) { + return null; + } + + const getChronotypeIcon = () => { + switch (data.chronotype) { + case 'early_bird': + return ; + case 'night_owl': + return ; + default: + return ; + } + }; + + const getChronotypeColor = () => { + switch (data.chronotype) { + case 'early_bird': + return 'bg-yellow-100 text-yellow-800'; + case 'night_owl': + return 'bg-indigo-100 text-indigo-800'; + default: + return 'bg-gray-100 text-gray-800'; + } + }; + + const formatTime = (time: string) => { + // Convert HH:MM to 12-hour format + const [hours, minutes] = time.split(':'); + const hour = parseInt(hours); + const ampm = hour >= 12 ? 'PM' : 'AM'; + const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour; + return `${displayHour}:${minutes} ${ampm}`; + }; + + return ( + + + + + + Circadian Rhythm Analysis + + + + {getChronotypeIcon()} + {data.chronotype.replace('_', ' ')} + + + + + + {/* Sleep Consistency Score */} +
+
+ Sleep Consistency + {Math.round(data.consistency * 100)}% +
+ +

+ {data.consistency > 0.8 + ? 'Excellent - Very consistent sleep schedule' + : data.consistency > 0.6 + ? 'Good - Fairly consistent schedule' + : 'Needs improvement - Irregular sleep pattern'} +

+
+ + {/* Optimal Times */} +
+
+
+ + Optimal Bedtime +
+

{formatTime(data.optimalBedtime)}

+
+
+
+ + Optimal Wake Time +
+

{formatTime(data.optimalWakeTime)}

+
+
+ + {/* Sleep Phase Shift */} +
+
+ Sleep Phase Shift + + {data.sleepPhaseShift > 0 ? '+' : ''}{data.sleepPhaseShift.toFixed(1)} hours + +
+
+ {Math.abs(data.sleepPhaseShift) < 1 ? ( + + ) : ( + + )} +

+ {Math.abs(data.sleepPhaseShift) < 1 + ? 'Sleep schedule aligned with typical patterns' + : data.sleepPhaseShift > 0 + ? `Bedtime is ${Math.abs(data.sleepPhaseShift).toFixed(1)} hours later than typical` + : `Bedtime is ${Math.abs(data.sleepPhaseShift).toFixed(1)} hours earlier than typical`} +

+
+
+ + {/* Melatonin Onset */} +
+
+ + Estimated Melatonin Onset +
+

{formatTime(data.melatoninOnset)}

+

+ Natural sleepiness begins around this time +

+
+ + {/* Recommended Schedule */} +
+

Recommended Daily Schedule

+
+
+ Wake Time + {formatTime(data.recommendedSchedule.wakeTime)} +
+ {data.recommendedSchedule.morningNap && ( +
+ Morning Nap + + {formatTime(data.recommendedSchedule.morningNap.start)} ({data.recommendedSchedule.morningNap.duration} min) + +
+ )} + {data.recommendedSchedule.afternoonNap && ( +
+ Afternoon Nap + + {formatTime(data.recommendedSchedule.afternoonNap.start)} ({data.recommendedSchedule.afternoonNap.duration} min) + +
+ )} +
+ Bedtime + {formatTime(data.recommendedSchedule.bedtime)} +
+
+ Daily Sleep Target + {Math.round(data.recommendedSchedule.totalSleepTarget / 60)} hours +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/maternal-web/components/analytics/CorrelationInsights.tsx b/maternal-web/components/analytics/CorrelationInsights.tsx new file mode 100644 index 0000000..b0366aa --- /dev/null +++ b/maternal-web/components/analytics/CorrelationInsights.tsx @@ -0,0 +1,235 @@ +'use client'; + +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; +import { + Link, + Link2Off, + TrendingUp, + TrendingDown, + Moon, + Utensils, + Baby, + Activity, + Info, + CheckCircle, +} from 'lucide-react'; +import { CorrelationAnalysis } from '@/lib/api/analytics'; + +interface CorrelationInsightsProps { + data: CorrelationAnalysis | null; + loading?: boolean; + error?: Error | null; +} + +export function CorrelationInsights({ data, loading, error }: CorrelationInsightsProps) { + if (loading) { + return ( + + +
Analyzing activity correlations...
+
+
+ ); + } + + if (error) { + return ( + + + + Error loading correlation data + + + ); + } + + if (!data) { + return null; + } + + const getCorrelationStrength = (value: number) => { + const absValue = Math.abs(value); + if (absValue > 0.7) return 'Strong'; + if (absValue > 0.4) return 'Moderate'; + if (absValue > 0.2) return 'Weak'; + return 'Negligible'; + }; + + const getCorrelationColor = (value: number) => { + const absValue = Math.abs(value); + if (absValue > 0.7) return 'bg-purple-100 text-purple-800'; + if (absValue > 0.4) return 'bg-blue-100 text-blue-800'; + if (absValue > 0.2) return 'bg-gray-100 text-gray-800'; + return 'bg-gray-50 text-gray-600'; + }; + + const getCorrelationIcon = (value: number) => { + if (value > 0.3) return ; + if (value < -0.3) return ; + return ; + }; + + const formatCorrelation = (value: number) => { + return (value * 100).toFixed(0) + '%'; + }; + + const correlations = [ + { + name: 'Feeding & Sleep', + value: data.feedingSleepCorrelation, + icon1: , + icon2: , + description: data.feedingSleepCorrelation > 0 + ? 'Better feeding patterns correlate with better sleep' + : data.feedingSleepCorrelation < 0 + ? 'More feedings may be disrupting sleep patterns' + : 'No significant relationship detected', + }, + { + name: 'Activity & Diapers', + value: data.activityDiaperCorrelation, + icon1: , + icon2: , + description: data.activityDiaperCorrelation > 0 + ? 'More activity correlates with more diaper changes' + : data.activityDiaperCorrelation < 0 + ? 'Less active periods show more diaper changes' + : 'No clear pattern between activity and diapers', + }, + ...(data.sleepMoodCorrelation !== undefined ? [{ + name: 'Sleep & Mood', + value: data.sleepMoodCorrelation, + icon1: , + icon2: , + description: data.sleepMoodCorrelation > 0 + ? 'Better sleep strongly correlates with better mood' + : data.sleepMoodCorrelation < 0 + ? 'Sleep patterns inversely affect mood' + : 'Sleep and mood appear independent', + }] : []), + ]; + + return ( + + + + + Activity Correlations + + + + {/* Correlation Visualizations */} +
+ {correlations.map((correlation) => ( +
+
+
+
+ {correlation.icon1} + & + {correlation.icon2} +
+ {correlation.name} +
+
+ {getCorrelationIcon(correlation.value)} + + {getCorrelationStrength(correlation.value)} + +
+
+ + {/* Correlation Bar */} +
+
+
+
+
+
0 ? '50%' : `${50 + correlation.value * 50}%`, + width: `${Math.abs(correlation.value) * 50}%`, + }} + > +
0 ? 'bg-green-500' : 'bg-red-500' + }`} + style={{ width: '100%' }} + /> +
+
0 ? `${50 + correlation.value * 50}%` : '50%', + transform: correlation.value > 0 ? 'translateX(-100%)' : 'none', + }} + > + + {formatCorrelation(correlation.value)} + +
+
+ +

{correlation.description}

+
+ ))} +
+ + {/* Correlation Scale Legend */} +
+

+ Correlation Scale +

+
+
+
+ Positive: Activities increase together +
+
+
+ Negative: One increases, other decreases +
+
+
+ + {/* Insights */} + {data.insights.length > 0 && ( +
+

+ + Key Insights +

+
+ {data.insights.map((insight, index) => ( +
+ +

{insight}

+
+ ))} +
+
+ )} + + + ); +} \ No newline at end of file diff --git a/maternal-web/components/analytics/GrowthPercentileChart.tsx b/maternal-web/components/analytics/GrowthPercentileChart.tsx new file mode 100644 index 0000000..592209d --- /dev/null +++ b/maternal-web/components/analytics/GrowthPercentileChart.tsx @@ -0,0 +1,307 @@ +'use client'; + +import React, { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + ReferenceLine, + Area, + AreaChart, +} from 'recharts'; +import { + Ruler, + Weight, + TrendingUp, + Baby, + AlertCircle, + ChevronUp, + ChevronDown, + Minus, +} from 'lucide-react'; +import { GrowthAnalysis, GrowthPercentile } from '@/lib/api/analytics'; +import { format } from 'date-fns'; + +interface GrowthPercentileChartProps { + data: GrowthAnalysis | null; + loading?: boolean; + error?: Error | null; +} + +export function GrowthPercentileChart({ data, loading, error }: GrowthPercentileChartProps) { + const [selectedMetric, setSelectedMetric] = useState<'weight' | 'height' | 'headCircumference'>('weight'); + + if (loading) { + return ( + + +
Loading growth analysis...
+
+
+ ); + } + + if (error) { + return ( + + + + Error loading growth data + + + ); + } + + if (!data) { + return null; + } + + const getPercentileColor = (percentile: number) => { + if (percentile < 5 || percentile > 95) return 'text-red-600'; + if (percentile < 15 || percentile > 85) return 'text-yellow-600'; + return 'text-green-600'; + }; + + const getPercentileBadge = (percentile: number) => { + if (percentile < 5 || percentile > 95) return 'bg-red-100 text-red-800'; + if (percentile < 15 || percentile > 85) return 'bg-yellow-100 text-yellow-800'; + return 'bg-green-100 text-green-800'; + }; + + const getVelocityIcon = (value: number) => { + if (value > 0) return ; + if (value < 0) return ; + return ; + }; + + const formatMeasurement = (value: number | undefined, metric: string) => { + if (!value) return 'N/A'; + switch (metric) { + case 'weight': + return `${value.toFixed(1)} kg`; + case 'height': + return `${value.toFixed(1)} cm`; + case 'headCircumference': + return `${value.toFixed(1)} cm`; + default: + return value.toFixed(1); + } + }; + + // Prepare chart data + const chartData = data.growthCurve.measurements.map(m => ({ + date: format(m.date, 'MMM dd'), + weight: m.weight, + height: m.height, + headCircumference: m.headCircumference, + p3: data.growthCurve.percentileCurves[selectedMetric]?.p3?.[0], + p15: data.growthCurve.percentileCurves[selectedMetric]?.p15?.[0], + p50: data.growthCurve.percentileCurves[selectedMetric]?.p50?.[0], + p85: data.growthCurve.percentileCurves[selectedMetric]?.p85?.[0], + p97: data.growthCurve.percentileCurves[selectedMetric]?.p97?.[0], + })); + + return ( + + + + + + Growth Analysis + + {data.alerts.length > 0 && ( + + {data.alerts.length} Alert{data.alerts.length > 1 ? 's' : ''} + + )} + + + + {/* Current Percentiles */} + {data.currentPercentiles && ( +
+ {data.currentPercentiles.weight && ( +
+
+ + Weight +
+

+ {data.currentPercentiles.weight.value.toFixed(1)} kg +

+ + {Math.round(data.currentPercentiles.weight.percentile)}th percentile + +

+ {data.currentPercentiles.weight.interpretation} +

+
+ )} + {data.currentPercentiles.height && ( +
+
+ + Height +
+

+ {data.currentPercentiles.height.value.toFixed(1)} cm +

+ + {Math.round(data.currentPercentiles.height.percentile)}th percentile + +

+ {data.currentPercentiles.height.interpretation} +

+
+ )} + {data.currentPercentiles.headCircumference && ( +
+
+ + Head Circ. +
+

+ {data.currentPercentiles.headCircumference.value.toFixed(1)} cm +

+ + {Math.round(data.currentPercentiles.headCircumference.percentile)}th percentile + +

+ {data.currentPercentiles.headCircumference.interpretation} +

+
+ )} +
+ )} + + {/* Growth Velocity */} + {data.growthVelocity && ( +
+

Growth Velocity

+
+ {data.growthVelocity.weightVelocity && ( +
+ Weight Gain +
+ {getVelocityIcon(data.growthVelocity.weightVelocity.value)} + + {data.growthVelocity.weightVelocity.value.toFixed(2)} {data.growthVelocity.weightVelocity.unit} + +
+
+ )} + {data.growthVelocity.heightVelocity && ( +
+ Height Growth +
+ {getVelocityIcon(data.growthVelocity.heightVelocity.value)} + + {data.growthVelocity.heightVelocity.value.toFixed(2)} {data.growthVelocity.heightVelocity.unit} + +
+
+ )} +
+

{data.growthVelocity.interpretation}

+
+ )} + + {/* Growth Chart */} + setSelectedMetric(v as any)}> + + Weight + Height + Head Circ. + + + + + + + + + + + + {/* Percentile reference lines */} + + + + + {/* Child's measurements */} + + + + + + + {/* Alerts */} + {data.alerts.length > 0 && ( +
+

Growth Alerts

+ {data.alerts.map((alert, index) => ( + + + +

{alert.message}

+

{alert.action}

+
+
+ ))} +
+ )} + + {/* Recommendations */} + {data.recommendations.length > 0 && ( +
+

Recommendations

+
    + {data.recommendations.map((rec, index) => ( +
  • + + {rec} +
  • + ))} +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/maternal-web/components/analytics/TrendAnalysisChart.tsx b/maternal-web/components/analytics/TrendAnalysisChart.tsx new file mode 100644 index 0000000..8e8ea55 --- /dev/null +++ b/maternal-web/components/analytics/TrendAnalysisChart.tsx @@ -0,0 +1,273 @@ +'use client'; + +import React, { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Progress } from '@/components/ui/progress'; +import { + LineChart, + Line, + Area, + AreaChart, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + ReferenceLine, +} from 'recharts'; +import { + TrendingUp, + TrendingDown, + Minus, + Calendar, + Target, + ChartLine, + Activity, +} from 'lucide-react'; +import { TrendAnalysis } from '@/lib/api/analytics'; +import { format } from 'date-fns'; + +interface TrendAnalysisChartProps { + data: TrendAnalysis | null; + activityType: string; + loading?: boolean; + error?: Error | null; +} + +export function TrendAnalysisChart({ data, activityType, loading, error }: TrendAnalysisChartProps) { + const [selectedTimeframe, setSelectedTimeframe] = useState<'short' | 'medium' | 'long'>('short'); + + if (loading) { + return ( + + +
Analyzing trends...
+
+
+ ); + } + + if (error) { + return ( + + + + Error loading trend analysis + + + ); + } + + if (!data) { + return null; + } + + const getTrendIcon = (direction: string) => { + switch (direction) { + case 'improving': + return ; + case 'declining': + return ; + default: + return ; + } + }; + + const getTrendColor = (direction: string) => { + switch (direction) { + case 'improving': + return 'bg-green-100 text-green-800'; + case 'declining': + return 'bg-red-100 text-red-800'; + default: + return 'bg-gray-100 text-gray-800'; + } + }; + + const getTrendData = () => { + switch (selectedTimeframe) { + case 'short': + return data.shortTermTrend; + case 'medium': + return data.mediumTermTrend; + case 'long': + return data.longTermTrend; + } + }; + + const currentTrend = getTrendData(); + + // Prepare chart data for predictions + const chartData = data.prediction.next7Days.map((point, index) => ({ + day: format(point.date, 'MMM dd'), + predicted: point.predictedValue, + upperBound: point.confidenceInterval.upper, + lowerBound: point.confidenceInterval.lower, + })); + + return ( + + + + + + {activityType} Trend Analysis + + + + {getTrendIcon(currentTrend.direction)} + {currentTrend.direction} + + + + + + {/* Timeframe Tabs */} + setSelectedTimeframe(v as any)}> + + Short (7 days) + Medium (14 days) + Long (30 days) + + + + {/* Trend Metrics */} +
+
+
+ Change + + {currentTrend.changePercent > 0 ? '+' : ''}{currentTrend.changePercent.toFixed(1)}% + +
+ +
+
+
+ Confidence + {(currentTrend.confidence * 100).toFixed(0)}% +
+ +
+
+ + {/* Statistical Details */} +
+
+

Slope

+

{currentTrend.slope.toFixed(3)}

+
+
+

R² Score

+

{currentTrend.r2Score.toFixed(3)}

+
+
+

Trend

+

{currentTrend.direction}

+
+
+
+
+ + {/* Prediction Chart */} +
+

+ + 7-Day Forecast + + {(data.prediction.confidence * 100).toFixed(0)}% confidence + +

+ + + + + + + + + + {/* Confidence interval area */} + + + + {/* Predicted trend line */} + + + + + {/* Prediction Factors */} + {data.prediction.factors.length > 0 && ( +
+

+ Factors Influencing Prediction +

+
+ {data.prediction.factors.map((factor, index) => ( + + {factor} + + ))} +
+
+ )} +
+ + {/* Seasonal Patterns */} + {data.seasonalPatterns && data.seasonalPatterns.length > 0 && ( +
+

+ + Seasonal Patterns Detected +

+
+ {data.seasonalPatterns.map((pattern, index) => ( +
+
+

+ {pattern.type} Pattern +

+

{pattern.pattern}

+
+
+

Strength

+

+ {(pattern.strength * 100).toFixed(0)}% +

+
+
+ ))} +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/maternal-web/components/features/analytics/UnifiedInsightsDashboard.tsx b/maternal-web/components/features/analytics/UnifiedInsightsDashboard.tsx index 5e2746d..8c4fe51 100644 --- a/maternal-web/components/features/analytics/UnifiedInsightsDashboard.tsx +++ b/maternal-web/components/features/analytics/UnifiedInsightsDashboard.tsx @@ -14,6 +14,7 @@ import { Alert, Grid, IconButton, + Button, } from '@mui/material'; import { Timeline, TrendingUp, Assessment, ArrowBack } from '@mui/icons-material'; import { useRouter } from 'next/navigation'; @@ -170,18 +171,28 @@ export function UnifiedInsightsDashboard() { > {/* Header with Back Button */} - - router.back()} sx={{ mr: 2 }}> - - - - - Insights & Predictions - - - AI-powered insights, patterns, and predictions for your child - + + + router.back()} sx={{ mr: 2 }}> + + + + + Insights & Predictions + + + AI-powered insights, patterns, and predictions for your child + + + {/* Error Alert */} diff --git a/maternal-web/components/layouts/TabBar/TabBar.tsx b/maternal-web/components/layouts/TabBar/TabBar.tsx index 4eeb015..715c5ab 100644 --- a/maternal-web/components/layouts/TabBar/TabBar.tsx +++ b/maternal-web/components/layouts/TabBar/TabBar.tsx @@ -22,7 +22,7 @@ export const TabBar = () => { { label: t('navigation.home'), icon: , value: '/' }, { label: t('navigation.track'), icon: , value: '/track' }, { label: '', icon: null, value: 'voice' }, // Placeholder for center button - { label: t('navigation.insights'), icon: , value: '/insights' }, + { label: t('navigation.insights'), icon: , value: pathname.startsWith('/analytics') ? pathname : '/insights' }, { label: t('navigation.aiChat'), icon: , value: '/ai-assistant' }, ]; diff --git a/maternal-web/components/ui/alert.tsx b/maternal-web/components/ui/alert.tsx new file mode 100644 index 0000000..5a7ba0f --- /dev/null +++ b/maternal-web/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } \ No newline at end of file diff --git a/maternal-web/components/ui/badge.tsx b/maternal-web/components/ui/badge.tsx new file mode 100644 index 0000000..12daad7 --- /dev/null +++ b/maternal-web/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } \ No newline at end of file diff --git a/maternal-web/components/ui/button.tsx b/maternal-web/components/ui/button.tsx new file mode 100644 index 0000000..c9c71de --- /dev/null +++ b/maternal-web/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } \ No newline at end of file diff --git a/maternal-web/components/ui/card.tsx b/maternal-web/components/ui/card.tsx new file mode 100644 index 0000000..938aa22 --- /dev/null +++ b/maternal-web/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } \ No newline at end of file diff --git a/maternal-web/components/ui/progress.tsx b/maternal-web/components/ui/progress.tsx new file mode 100644 index 0000000..339efec --- /dev/null +++ b/maternal-web/components/ui/progress.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)) +Progress.displayName = ProgressPrimitive.Root.displayName + +export { Progress } \ No newline at end of file diff --git a/maternal-web/components/ui/tabs.tsx b/maternal-web/components/ui/tabs.tsx new file mode 100644 index 0000000..8873b85 --- /dev/null +++ b/maternal-web/components/ui/tabs.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } \ No newline at end of file diff --git a/maternal-web/lib/api/analytics.ts b/maternal-web/lib/api/analytics.ts index 6a151ac..9142c13 100644 --- a/maternal-web/lib/api/analytics.ts +++ b/maternal-web/lib/api/analytics.ts @@ -112,6 +112,177 @@ export interface MonthlyReport { trends: string[]; } +// Advanced Analytics Interfaces +export interface CircadianRhythm { + sleepPhaseShift: number; + consistency: number; + optimalBedtime: string; + optimalWakeTime: string; + chronotype: 'early_bird' | 'night_owl' | 'typical'; + melatoninOnset: string; + recommendedSchedule: { + wakeTime: string; + morningNap?: { start: string; duration: number }; + afternoonNap?: { start: string; duration: number }; + bedtime: string; + totalSleepTarget: number; + }; +} + +export interface AnomalyDetection { + anomalies: Array<{ + activityId: string; + type: string; + timestamp: Date; + severity: 'low' | 'medium' | 'high'; + description: string; + deviation: number; + }>; + alerts: Array<{ + type: string; + severity: 'info' | 'warning' | 'critical'; + message: string; + recommendations: string[]; + }>; + confidenceScore: number; +} + +export interface PatternCluster { + clusterId: string; + label: string; + activities: any[]; + centroid: { + averageTime: string; + averageDuration: number; + characteristics: Record; + }; + confidence: number; +} + +export interface CorrelationAnalysis { + feedingSleepCorrelation: number; + activityDiaperCorrelation: number; + sleepMoodCorrelation?: number; + insights: string[]; +} + +export interface TrendAnalysis { + shortTermTrend: { + direction: 'improving' | 'stable' | 'declining'; + slope: number; + confidence: number; + r2Score: number; + changePercent: number; + }; + mediumTermTrend: { + direction: 'improving' | 'stable' | 'declining'; + slope: number; + confidence: number; + r2Score: number; + changePercent: number; + }; + longTermTrend: { + direction: 'improving' | 'stable' | 'declining'; + slope: number; + confidence: number; + r2Score: number; + changePercent: number; + }; + seasonalPatterns?: Array<{ + type: 'weekly' | 'monthly'; + pattern: string; + strength: number; + }>; + prediction: { + next7Days: Array<{ + date: Date; + predictedValue: number; + confidenceInterval: { lower: number; upper: number }; + }>; + confidence: number; + factors: string[]; + }; +} + +export interface GrowthPercentile { + weight?: { + value: number; + percentile: number; + zScore: number; + interpretation: string; + }; + height?: { + value: number; + percentile: number; + zScore: number; + interpretation: string; + }; + headCircumference?: { + value: number; + percentile: number; + zScore: number; + interpretation: string; + }; + bmi?: { + value: number; + percentile: number; + zScore: number; + interpretation: string; + }; +} + +export interface GrowthAnalysis { + currentPercentiles: GrowthPercentile; + growthVelocity: { + weightVelocity?: { + value: number; + unit: string; + percentile: number; + }; + heightVelocity?: { + value: number; + unit: string; + percentile: number; + }; + interpretation: string; + concerns: string[]; + }; + growthCurve: { + measurements: Array<{ + date: Date; + weight?: number; + height?: number; + headCircumference?: number; + }>; + percentileCurves: { + weight: Record; + height: Record; + headCircumference: Record; + }; + }; + projections: { + threeMonths: GrowthPercentile; + sixMonths: GrowthPercentile; + confidence: number; + }; + recommendations: string[]; + alerts: Array<{ + type: string; + severity: 'low' | 'medium' | 'high'; + message: string; + action: string; + }>; +} + +export interface AdvancedAnalyticsDashboard { + circadianRhythm: CircadianRhythm; + anomalies: AnomalyDetection; + correlations: CorrelationAnalysis; + growthAnalysis: GrowthAnalysis; + trends: Record; + clusters: Record; +} + export const analyticsApi = { // Get pattern insights getInsights: async (childId: string, days: number = 7): Promise => { @@ -221,6 +392,139 @@ export const analyticsApi = { }); return response.data.data; }, + + // Advanced Analytics Methods + + // Get circadian rhythm analysis + getCircadianRhythm: async (childId: string, days: number = 14): Promise => { + const response = await apiClient.get(`/api/v1/analytics/advanced/circadian/${childId}`, { + params: { days }, + }); + return response.data.data; + }, + + // Get anomaly detection + getAnomalies: async (childId: string, days: number = 30): Promise => { + const response = await apiClient.get(`/api/v1/analytics/advanced/anomalies/${childId}`, { + params: { days }, + }); + const data = response.data.data; + + // Convert timestamps + return { + ...data, + anomalies: data.anomalies.map((a: any) => ({ + ...a, + timestamp: new Date(a.timestamp), + })), + }; + }, + + // Get activity clusters + getClusters: async ( + childId: string, + activityType: string, + days: number = 30, + ): Promise => { + const response = await apiClient.get(`/api/v1/analytics/advanced/clusters/${childId}`, { + params: { activityType, days }, + }); + return response.data.data; + }, + + // Get correlation analysis + getCorrelations: async (childId: string, days: number = 14): Promise => { + const response = await apiClient.get(`/api/v1/analytics/advanced/correlations/${childId}`, { + params: { days }, + }); + return response.data.data; + }, + + // Get trend analysis + getTrends: async (childId: string, activityType: string): Promise => { + const response = await apiClient.get(`/api/v1/analytics/advanced/trends/${childId}`, { + params: { activityType }, + }); + const data = response.data.data; + + // Convert dates in predictions + return { + ...data, + prediction: { + ...data.prediction, + next7Days: data.prediction.next7Days.map((p: any) => ({ + ...p, + date: new Date(p.date), + })), + }, + }; + }, + + // Get growth percentiles + calculateGrowthPercentiles: async ( + childId: string, + measurement: { + date: Date; + weight?: number; + height?: number; + headCircumference?: number; + }, + ): Promise => { + const response = await apiClient.post( + `/api/v1/analytics/growth/percentiles/${childId}`, + { + ...measurement, + date: measurement.date.toISOString(), + }, + ); + return response.data.data; + }, + + // Get growth analysis + getGrowthAnalysis: async (childId: string): Promise => { + const response = await apiClient.get(`/api/v1/analytics/growth/analysis/${childId}`); + const data = response.data.data; + + // Convert dates in measurements + return { + ...data, + growthCurve: { + ...data.growthCurve, + measurements: data.growthCurve.measurements.map((m: any) => ({ + ...m, + date: new Date(m.date), + })), + }, + }; + }, + + // Get comprehensive analytics dashboard + getAdvancedDashboard: async (childId: string): Promise => { + const response = await apiClient.get(`/api/v1/analytics/advanced/dashboard/${childId}`); + const data = response.data.data; + + // Process all date conversions + return { + ...data, + anomalies: { + ...data.anomalies, + anomalies: data.anomalies.anomalies.map((a: any) => ({ + ...a, + timestamp: new Date(a.timestamp), + })), + }, + growthAnalysis: { + ...data.growthAnalysis, + growthCurve: { + ...data.growthAnalysis.growthCurve, + measurements: data.growthAnalysis.growthCurve.measurements.map((m: any) => ({ + ...m, + date: new Date(m.date), + })), + }, + }, + }; + }, }; export enum ComparisonMetric { diff --git a/maternal-web/lib/utils.ts b/maternal-web/lib/utils.ts new file mode 100644 index 0000000..1a860ee --- /dev/null +++ b/maternal-web/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} \ No newline at end of file diff --git a/maternal-web/package-lock.json b/maternal-web/package-lock.json index 469656e..4059c2e 100644 --- a/maternal-web/package-lock.json +++ b/maternal-web/package-lock.json @@ -15,10 +15,17 @@ "@mui/icons-material": "^7.3.3", "@mui/material": "^7.3.3", "@mui/material-nextjs": "^7.3.3", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tabs": "^1.1.13", "@reduxjs/toolkit": "^2.9.0", "@simplewebauthn/browser": "^13.2.0", "@tanstack/react-query": "^5.90.2", "axios": "^1.12.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "focus-trap-react": "^11.0.4", @@ -26,6 +33,7 @@ "graphql": "^16.11.0", "i18next": "^25.5.3", "i18next-browser-languagedetector": "^8.2.0", + "lucide-react": "^0.544.0", "next": "^15.5.4", "next-pwa": "^5.6.0", "react": "^19.2.0", @@ -39,6 +47,7 @@ "redux-persist": "^6.0.0", "remark-gfm": "^4.0.1", "socket.io-client": "^4.8.1", + "tailwind-merge": "^3.3.1", "web-vitals": "^5.1.0", "workbox-webpack-plugin": "^7.3.0", "workbox-window": "^7.3.0", @@ -2346,6 +2355,44 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", @@ -3910,6 +3957,675 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@reduxjs/toolkit": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz", @@ -5630,6 +6346,18 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -6459,6 +7187,18 @@ "dev": true, "license": "MIT" }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/clean-webpack-plugin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", @@ -8865,6 +9605,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", @@ -11643,6 +12392,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", + "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -14239,6 +14997,75 @@ } } }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -15740,6 +16567,16 @@ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", "license": "MIT" }, + "node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", diff --git a/maternal-web/package.json b/maternal-web/package.json index 5a25b9e..c8741f3 100644 --- a/maternal-web/package.json +++ b/maternal-web/package.json @@ -22,10 +22,17 @@ "@mui/icons-material": "^7.3.3", "@mui/material": "^7.3.3", "@mui/material-nextjs": "^7.3.3", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tabs": "^1.1.13", "@reduxjs/toolkit": "^2.9.0", "@simplewebauthn/browser": "^13.2.0", "@tanstack/react-query": "^5.90.2", "axios": "^1.12.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "focus-trap-react": "^11.0.4", @@ -33,6 +40,7 @@ "graphql": "^16.11.0", "i18next": "^25.5.3", "i18next-browser-languagedetector": "^8.2.0", + "lucide-react": "^0.544.0", "next": "^15.5.4", "next-pwa": "^5.6.0", "react": "^19.2.0", @@ -46,6 +54,7 @@ "redux-persist": "^6.0.0", "remark-gfm": "^4.0.1", "socket.io-client": "^4.8.1", + "tailwind-merge": "^3.3.1", "web-vitals": "^5.1.0", "workbox-webpack-plugin": "^7.3.0", "workbox-window": "^7.3.0", diff --git a/maternal-web/public/sw.js b/maternal-web/public/sw.js index ee01af9..83a66b3 100644 --- a/maternal-web/public/sw.js +++ b/maternal-web/public/sw.js @@ -1 +1 @@ -if(!self.define){let e,a={};const s=(s,c)=>(s=new URL(s+".js",c).href,a[s]||new Promise(a=>{if("document"in self){const e=document.createElement("script");e.src=s,e.onload=a,document.head.appendChild(e)}else e=s,importScripts(s),a()}).then(()=>{let e=a[s];if(!e)throw new Error(`Module ${s} didn’t register its module`);return e}));self.define=(c,i)=>{const n=e||("document"in self?document.currentScript.src:"")||location.href;if(a[n])return;let t={};const r=e=>s(e,n),f={module:{uri:n},exports:t,require:r};a[n]=Promise.all(c.map(e=>f[e]||r(e))).then(e=>(i(...e),t))}}define(["./workbox-4d767a27"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"2b3cb35ea252002ccf2dc5f5b26bc330"},{url:"/_next/static/O-Rs9FsvedxX8COyH-JBk/_buildManifest.js",revision:"003849461a7dd4bad470c5d1d5e5254c"},{url:"/_next/static/O-Rs9FsvedxX8COyH-JBk/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/1091.c762d795c6885f94.js",revision:"c762d795c6885f94"},{url:"/_next/static/chunks/1197.b7511947a737e909.js",revision:"b7511947a737e909"},{url:"/_next/static/chunks/1255-b2f7fd83e387a9e1.js",revision:"b2f7fd83e387a9e1"},{url:"/_next/static/chunks/1280-296e0a2b6e9dd9b1.js",revision:"296e0a2b6e9dd9b1"},{url:"/_next/static/chunks/1514-a6ed8a01b9885870.js",revision:"a6ed8a01b9885870"},{url:"/_next/static/chunks/1543-530e0f57f7af68aa.js",revision:"530e0f57f7af68aa"},{url:"/_next/static/chunks/1569-55e6fb5a18f58b21.js",revision:"55e6fb5a18f58b21"},{url:"/_next/static/chunks/1863-7231108310f72246.js",revision:"7231108310f72246"},{url:"/_next/static/chunks/189-acf917e753c20aa8.js",revision:"acf917e753c20aa8"},{url:"/_next/static/chunks/2262-26293d6453fcc927.js",revision:"26293d6453fcc927"},{url:"/_next/static/chunks/2506-b6cf4a8108dc7c86.js",revision:"b6cf4a8108dc7c86"},{url:"/_next/static/chunks/2619-04bc32f026a0d946.js",revision:"04bc32f026a0d946"},{url:"/_next/static/chunks/3039-0e9bf08230c8ee7b.js",revision:"0e9bf08230c8ee7b"},{url:"/_next/static/chunks/3127-49a95e7cb556ace3.js",revision:"49a95e7cb556ace3"},{url:"/_next/static/chunks/3183-823e6c5b74284106.js",revision:"823e6c5b74284106"},{url:"/_next/static/chunks/340-ce44e06c2ff738ed.js",revision:"ce44e06c2ff738ed"},{url:"/_next/static/chunks/3664-56dedfcaec4aaceb.js",revision:"56dedfcaec4aaceb"},{url:"/_next/static/chunks/3762-c2c13ecf11b3eabb.js",revision:"c2c13ecf11b3eabb"},{url:"/_next/static/chunks/4241-f94f398f62924b88.js",revision:"f94f398f62924b88"},{url:"/_next/static/chunks/4337-7b7eba57ddf5f8f1.js",revision:"7b7eba57ddf5f8f1"},{url:"/_next/static/chunks/4bd1b696-100b9d70ed4e49c1.js",revision:"100b9d70ed4e49c1"},{url:"/_next/static/chunks/5125-c990fc036d2a6ce4.js",revision:"c990fc036d2a6ce4"},{url:"/_next/static/chunks/525-9faf3d762ffa7508.js",revision:"9faf3d762ffa7508"},{url:"/_next/static/chunks/5380-9004e1ac3565daca.js",revision:"9004e1ac3565daca"},{url:"/_next/static/chunks/5385-7ecda8e4ba984edc.js",revision:"7ecda8e4ba984edc"},{url:"/_next/static/chunks/5482-7535aa0aab02d518.js",revision:"7535aa0aab02d518"},{url:"/_next/static/chunks/5761-e5b2862a3b7d7efa.js",revision:"e5b2862a3b7d7efa"},{url:"/_next/static/chunks/6088-c165c565edce02be.js",revision:"c165c565edce02be"},{url:"/_next/static/chunks/670-a4ca0f366ee779f5.js",revision:"a4ca0f366ee779f5"},{url:"/_next/static/chunks/6733-52673e6aade9c963.js",revision:"52673e6aade9c963"},{url:"/_next/static/chunks/6873-ff265086321345c8.js",revision:"ff265086321345c8"},{url:"/_next/static/chunks/6886-40f1779ffff00d58.js",revision:"40f1779ffff00d58"},{url:"/_next/static/chunks/710-7e96cbf5d461482a.js",revision:"7e96cbf5d461482a"},{url:"/_next/static/chunks/7359-1abfb9f346309354.js",revision:"1abfb9f346309354"},{url:"/_next/static/chunks/7741-0af8b5a61d8e63d3.js",revision:"0af8b5a61d8e63d3"},{url:"/_next/static/chunks/7855-72c79224370eff7b.js",revision:"72c79224370eff7b"},{url:"/_next/static/chunks/787-032067ae978e62a8.js",revision:"032067ae978e62a8"},{url:"/_next/static/chunks/8221-d51102291d5ddaf9.js",revision:"d51102291d5ddaf9"},{url:"/_next/static/chunks/8241-eaf1b9c6054e9ad8.js",revision:"eaf1b9c6054e9ad8"},{url:"/_next/static/chunks/8412-8ce7440f3599e2d9.js",revision:"8ce7440f3599e2d9"},{url:"/_next/static/chunks/8466-ffa71cea7998f777.js",revision:"ffa71cea7998f777"},{url:"/_next/static/chunks/8544.74f59dd908783038.js",revision:"74f59dd908783038"},{url:"/_next/static/chunks/8746-92ff3ad56eb06d6e.js",revision:"92ff3ad56eb06d6e"},{url:"/_next/static/chunks/8863-7b43165e8b5cae38.js",revision:"7b43165e8b5cae38"},{url:"/_next/static/chunks/9205-f540995b767df00b.js",revision:"f540995b767df00b"},{url:"/_next/static/chunks/9333-b4ce9ef429de942e.js",revision:"b4ce9ef429de942e"},{url:"/_next/static/chunks/9392-2887c5e5703ed90a.js",revision:"2887c5e5703ed90a"},{url:"/_next/static/chunks/9397-40b8ac68e22a4d87.js",revision:"40b8ac68e22a4d87"},{url:"/_next/static/chunks/9515-53e74005e71810bd.js",revision:"53e74005e71810bd"},{url:"/_next/static/chunks/9517-17518b5fffe76114.js",revision:"17518b5fffe76114"},{url:"/_next/static/chunks/9738-c70b13d86cc3ea77.js",revision:"c70b13d86cc3ea77"},{url:"/_next/static/chunks/9958.962493a298c38a17.js",revision:"962493a298c38a17"},{url:"/_next/static/chunks/app/(auth)/forgot-password/page-294fb4d9f0014755.js",revision:"294fb4d9f0014755"},{url:"/_next/static/chunks/app/(auth)/login/page-d8f5656a139ee55d.js",revision:"d8f5656a139ee55d"},{url:"/_next/static/chunks/app/(auth)/onboarding/page-ea1c1fa93f2bff9a.js",revision:"ea1c1fa93f2bff9a"},{url:"/_next/static/chunks/app/(auth)/register/page-4b000eea571750ea.js",revision:"4b000eea571750ea"},{url:"/_next/static/chunks/app/(auth)/reset-password/page-836751db40b617b5.js",revision:"836751db40b617b5"},{url:"/_next/static/chunks/app/_not-found/page-95f11f5fe94340f1.js",revision:"95f11f5fe94340f1"},{url:"/_next/static/chunks/app/ai-assistant/page-e34646713b4eca94.js",revision:"e34646713b4eca94"},{url:"/_next/static/chunks/app/analytics/page-7a6b08f18da0121a.js",revision:"7a6b08f18da0121a"},{url:"/_next/static/chunks/app/api/ai/chat/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/auth/login/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/auth/password-reset/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/auth/register/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/health/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/tracking/feeding/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/voice/transcribe/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/children/page-b6fb68596ba09b7a.js",revision:"b6fb68596ba09b7a"},{url:"/_next/static/chunks/app/family/page-3bf65f3ee77dd58f.js",revision:"3bf65f3ee77dd58f"},{url:"/_next/static/chunks/app/history/page-db18505f04d26741.js",revision:"db18505f04d26741"},{url:"/_next/static/chunks/app/insights/page-605c43713d69ed1e.js",revision:"605c43713d69ed1e"},{url:"/_next/static/chunks/app/layout-e9ae6b6360689f26.js",revision:"e9ae6b6360689f26"},{url:"/_next/static/chunks/app/legal/cookies/page-c39a3fa6e27a8806.js",revision:"c39a3fa6e27a8806"},{url:"/_next/static/chunks/app/legal/eula/page-8015f749ab4dd660.js",revision:"8015f749ab4dd660"},{url:"/_next/static/chunks/app/legal/page-3de074f0b9741bc6.js",revision:"3de074f0b9741bc6"},{url:"/_next/static/chunks/app/legal/privacy/page-3cb58024b6fd8e21.js",revision:"3cb58024b6fd8e21"},{url:"/_next/static/chunks/app/legal/terms/page-b5a1c96cae251767.js",revision:"b5a1c96cae251767"},{url:"/_next/static/chunks/app/logout/page-359b0e371fd55c32.js",revision:"359b0e371fd55c32"},{url:"/_next/static/chunks/app/offline/page-28c005360c2b2736.js",revision:"28c005360c2b2736"},{url:"/_next/static/chunks/app/page-6db54b61da7c6316.js",revision:"6db54b61da7c6316"},{url:"/_next/static/chunks/app/settings/page-3f8776db67351a35.js",revision:"3f8776db67351a35"},{url:"/_next/static/chunks/app/track/activity/page-2307107edcbf16b6.js",revision:"2307107edcbf16b6"},{url:"/_next/static/chunks/app/track/diaper/page-a39423216f7058a8.js",revision:"a39423216f7058a8"},{url:"/_next/static/chunks/app/track/feeding/page-45257beac2eb2bc3.js",revision:"45257beac2eb2bc3"},{url:"/_next/static/chunks/app/track/growth/page-207ac7ed192d7b4a.js",revision:"207ac7ed192d7b4a"},{url:"/_next/static/chunks/app/track/medicine/page-9e2aaabd12c4ea8f.js",revision:"9e2aaabd12c4ea8f"},{url:"/_next/static/chunks/app/track/page-dd5ade1eb19ad389.js",revision:"dd5ade1eb19ad389"},{url:"/_next/static/chunks/app/track/sleep/page-bc6d6b16af0f9b5e.js",revision:"bc6d6b16af0f9b5e"},{url:"/_next/static/chunks/framework-bd61ec64032c2de7.js",revision:"bd61ec64032c2de7"},{url:"/_next/static/chunks/main-520e5ec2d671abe7.js",revision:"520e5ec2d671abe7"},{url:"/_next/static/chunks/main-app-02fc3649960ba6c7.js",revision:"02fc3649960ba6c7"},{url:"/_next/static/chunks/pages/_app-4b3fb5e477a0267f.js",revision:"4b3fb5e477a0267f"},{url:"/_next/static/chunks/pages/_error-c970d8b55ace1b48.js",revision:"c970d8b55ace1b48"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-cd699ef02cde1c76.js",revision:"cd699ef02cde1c76"},{url:"/_next/static/css/2eb0f1dfbb62d2c0.css",revision:"2eb0f1dfbb62d2c0"},{url:"/_next/static/media/19cfc7226ec3afaa-s.woff2",revision:"9dda5cfc9a46f256d0e131bb535e46f8"},{url:"/_next/static/media/21350d82a1f187e9-s.woff2",revision:"4e2553027f1d60eff32898367dd4d541"},{url:"/_next/static/media/8e9860b6e62d6359-s.woff2",revision:"01ba6c2a184b8cba08b0d57167664d75"},{url:"/_next/static/media/ba9851c3c22cd980-s.woff2",revision:"9e494903d6b0ffec1a1e14d34427d44d"},{url:"/_next/static/media/c5fe6dc8356a8c31-s.woff2",revision:"027a89e9ab733a145db70f09b8a18b42"},{url:"/_next/static/media/df0a9ae256c0569c-s.woff2",revision:"d54db44de5ccb18886ece2fda72bdfe0"},{url:"/_next/static/media/e4af272ccee01ff0-s.p.woff2",revision:"65850a373e258f1c897a2b3d75eb74de"},{url:"/apple-touch-icon.png",revision:"fa2d4d791b90148a18d49bc3bfd7a43a"},{url:"/check-updates.js",revision:"bc016a0ceb6c72a5fe9ba02ad05d78be"},{url:"/favicon-16x16.png",revision:"db2da3355c89a6149f6d9ee35ebe6bf3"},{url:"/favicon-32x32.png",revision:"0fd88d56aa584bd0546d05ffc63ef777"},{url:"/icon-192x192.png",revision:"b8ef7f117472c4399cceffea644eb8bd"},{url:"/icons/icon-128x128.png",revision:"96cff3b189d9c1daa1edf470290a90cd"},{url:"/icons/icon-144x144.png",revision:"b627c346c431d7e306005aec5f51baff"},{url:"/icons/icon-152x152.png",revision:"012071830c13d310e51f833baed531af"},{url:"/icons/icon-192x192.png",revision:"dfb20132ddb628237eccd4b0e2ee4aaa"},{url:"/icons/icon-384x384.png",revision:"d032b25376232878a2a29b5688992a8d"},{url:"/icons/icon-512x512.png",revision:"ffda0043571d60956f4e321cba706670"},{url:"/icons/icon-72x72.png",revision:"cc89e74126e7e1109f0186774b3c0d77"},{url:"/icons/icon-96x96.png",revision:"32813cdad5b636fc09eec01c7d705936"},{url:"/manifest.json",revision:"5cbf1ecd33b05c4772688ce7d00c2c23"},{url:"/next.svg",revision:"8e061864f388b47f33a1c3780831193e"},{url:"/vercel.svg",revision:"61c6b19abff40ea7acd577be818f3976"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:a,event:s,state:c})=>a&&"opaqueredirect"===a.type?new Response(a.body,{status:200,statusText:"OK",headers:a.headers}):a}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/api\/.*$/i,new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/.*/i,new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET")}); +if(!self.define){let e,a={};const s=(s,c)=>(s=new URL(s+".js",c).href,a[s]||new Promise(a=>{if("document"in self){const e=document.createElement("script");e.src=s,e.onload=a,document.head.appendChild(e)}else e=s,importScripts(s),a()}).then(()=>{let e=a[s];if(!e)throw new Error(`Module ${s} didn’t register its module`);return e}));self.define=(c,i)=>{const n=e||("document"in self?document.currentScript.src:"")||location.href;if(a[n])return;let t={};const r=e=>s(e,n),f={module:{uri:n},exports:t,require:r};a[n]=Promise.all(c.map(e=>f[e]||r(e))).then(e=>(i(...e),t))}}define(["./workbox-4d767a27"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"69166eaf5d8d1ec7f1e507aee6a6178e"},{url:"/_next/static/YsFOZO14Phq9lS-rztod_/_buildManifest.js",revision:"673df67655213af81147283455f8956d"},{url:"/_next/static/YsFOZO14Phq9lS-rztod_/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/1037-e1aaa2cc4d6d34f0.js",revision:"e1aaa2cc4d6d34f0"},{url:"/_next/static/chunks/1091.c762d795c6885f94.js",revision:"c762d795c6885f94"},{url:"/_next/static/chunks/1255-b2f7fd83e387a9e1.js",revision:"b2f7fd83e387a9e1"},{url:"/_next/static/chunks/1280-296e0a2b6e9dd9b1.js",revision:"296e0a2b6e9dd9b1"},{url:"/_next/static/chunks/1514-a6ed8a01b9885870.js",revision:"a6ed8a01b9885870"},{url:"/_next/static/chunks/1543-530e0f57f7af68aa.js",revision:"530e0f57f7af68aa"},{url:"/_next/static/chunks/1843-b2c03c59f335abf4.js",revision:"b2c03c59f335abf4"},{url:"/_next/static/chunks/189-453061dd646fdba4.js",revision:"453061dd646fdba4"},{url:"/_next/static/chunks/1930-cd8328eb1cfa4178.js",revision:"cd8328eb1cfa4178"},{url:"/_next/static/chunks/2262-26293d6453fcc927.js",revision:"26293d6453fcc927"},{url:"/_next/static/chunks/2506-35fdf1de6e0fd0ec.js",revision:"35fdf1de6e0fd0ec"},{url:"/_next/static/chunks/2619-04bc32f026a0d946.js",revision:"04bc32f026a0d946"},{url:"/_next/static/chunks/3127-49a95e7cb556ace3.js",revision:"49a95e7cb556ace3"},{url:"/_next/static/chunks/3452-86647d15ff7842a5.js",revision:"86647d15ff7842a5"},{url:"/_next/static/chunks/3460-a2b6a712ec21acfb.js",revision:"a2b6a712ec21acfb"},{url:"/_next/static/chunks/3664-56dedfcaec4aaceb.js",revision:"56dedfcaec4aaceb"},{url:"/_next/static/chunks/3762-6764a708ac67fa18.js",revision:"6764a708ac67fa18"},{url:"/_next/static/chunks/4337-7b7eba57ddf5f8f1.js",revision:"7b7eba57ddf5f8f1"},{url:"/_next/static/chunks/4450.a965376e27addfd5.js",revision:"a965376e27addfd5"},{url:"/_next/static/chunks/4bd1b696-100b9d70ed4e49c1.js",revision:"100b9d70ed4e49c1"},{url:"/_next/static/chunks/5124-1abf907e61fbf455.js",revision:"1abf907e61fbf455"},{url:"/_next/static/chunks/5125-c990fc036d2a6ce4.js",revision:"c990fc036d2a6ce4"},{url:"/_next/static/chunks/5385-7ecda8e4ba984edc.js",revision:"7ecda8e4ba984edc"},{url:"/_next/static/chunks/5482-7535aa0aab02d518.js",revision:"7535aa0aab02d518"},{url:"/_next/static/chunks/6088-c165c565edce02be.js",revision:"c165c565edce02be"},{url:"/_next/static/chunks/6183-84d624cdd79749ef.js",revision:"84d624cdd79749ef"},{url:"/_next/static/chunks/658-1d9d4c0c8b5fb129.js",revision:"1d9d4c0c8b5fb129"},{url:"/_next/static/chunks/670-a4ca0f366ee779f5.js",revision:"a4ca0f366ee779f5"},{url:"/_next/static/chunks/6873-ff265086321345c8.js",revision:"ff265086321345c8"},{url:"/_next/static/chunks/6886-40f1779ffff00d58.js",revision:"40f1779ffff00d58"},{url:"/_next/static/chunks/7029-7ec3e4452c8304d7.js",revision:"7ec3e4452c8304d7"},{url:"/_next/static/chunks/710-7e96cbf5d461482a.js",revision:"7e96cbf5d461482a"},{url:"/_next/static/chunks/7166-45df3fb3c28138b5.js",revision:"45df3fb3c28138b5"},{url:"/_next/static/chunks/7225-8f913ab2896c1c1a.js",revision:"8f913ab2896c1c1a"},{url:"/_next/static/chunks/7359-1abfb9f346309354.js",revision:"1abfb9f346309354"},{url:"/_next/static/chunks/7741-0af8b5a61d8e63d3.js",revision:"0af8b5a61d8e63d3"},{url:"/_next/static/chunks/7855-72c79224370eff7b.js",revision:"72c79224370eff7b"},{url:"/_next/static/chunks/787-032067ae978e62a8.js",revision:"032067ae978e62a8"},{url:"/_next/static/chunks/7902-e1f71c3b4c62bff9.js",revision:"e1f71c3b4c62bff9"},{url:"/_next/static/chunks/8221-d51102291d5ddaf9.js",revision:"d51102291d5ddaf9"},{url:"/_next/static/chunks/8241-eaf1b9c6054e9ad8.js",revision:"eaf1b9c6054e9ad8"},{url:"/_next/static/chunks/8412-8ce7440f3599e2d9.js",revision:"8ce7440f3599e2d9"},{url:"/_next/static/chunks/8466-ffa71cea7998f777.js",revision:"ffa71cea7998f777"},{url:"/_next/static/chunks/8544.74f59dd908783038.js",revision:"74f59dd908783038"},{url:"/_next/static/chunks/8746-92ff3ad56eb06d6e.js",revision:"92ff3ad56eb06d6e"},{url:"/_next/static/chunks/9064-b6e187b9163c365e.js",revision:"b6e187b9163c365e"},{url:"/_next/static/chunks/9205-f540995b767df00b.js",revision:"f540995b767df00b"},{url:"/_next/static/chunks/9300-1cc361e1420fd381.js",revision:"1cc361e1420fd381"},{url:"/_next/static/chunks/9328-a7221e84fb4e038c.js",revision:"a7221e84fb4e038c"},{url:"/_next/static/chunks/9333-d0f251c321264800.js",revision:"d0f251c321264800"},{url:"/_next/static/chunks/9382-bf36ea7e8453c043.js",revision:"bf36ea7e8453c043"},{url:"/_next/static/chunks/9392-2887c5e5703ed90a.js",revision:"2887c5e5703ed90a"},{url:"/_next/static/chunks/9397-40b8ac68e22a4d87.js",revision:"40b8ac68e22a4d87"},{url:"/_next/static/chunks/9517-17518b5fffe76114.js",revision:"17518b5fffe76114"},{url:"/_next/static/chunks/9738-d4ae78df35beeba7.js",revision:"d4ae78df35beeba7"},{url:"/_next/static/chunks/9958.29bedcecbfff5702.js",revision:"29bedcecbfff5702"},{url:"/_next/static/chunks/app/(auth)/forgot-password/page-ca00943cf66c3e17.js",revision:"ca00943cf66c3e17"},{url:"/_next/static/chunks/app/(auth)/login/page-cc332e92a316f523.js",revision:"cc332e92a316f523"},{url:"/_next/static/chunks/app/(auth)/onboarding/page-762359ce9bda51b2.js",revision:"762359ce9bda51b2"},{url:"/_next/static/chunks/app/(auth)/register/page-e29ce0efae5e914c.js",revision:"e29ce0efae5e914c"},{url:"/_next/static/chunks/app/(auth)/reset-password/page-2eec6b4142e79702.js",revision:"2eec6b4142e79702"},{url:"/_next/static/chunks/app/_not-found/page-95f11f5fe94340f1.js",revision:"95f11f5fe94340f1"},{url:"/_next/static/chunks/app/ai-assistant/page-3edb2cda7412d8b4.js",revision:"3edb2cda7412d8b4"},{url:"/_next/static/chunks/app/analytics/advanced/page-f0a9b68c7fe29045.js",revision:"f0a9b68c7fe29045"},{url:"/_next/static/chunks/app/analytics/page-e8b19d9a0c19ea81.js",revision:"e8b19d9a0c19ea81"},{url:"/_next/static/chunks/app/api/ai/chat/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/auth/login/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/auth/password-reset/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/auth/register/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/health/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/tracking/feeding/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/api/voice/transcribe/route-a631d97a33877f8a.js",revision:"a631d97a33877f8a"},{url:"/_next/static/chunks/app/children/page-40f1bfa952ee593c.js",revision:"40f1bfa952ee593c"},{url:"/_next/static/chunks/app/family/page-a6c42634bbec4ac7.js",revision:"a6c42634bbec4ac7"},{url:"/_next/static/chunks/app/history/page-16263a21d7085bd2.js",revision:"16263a21d7085bd2"},{url:"/_next/static/chunks/app/insights/page-353479d33da19bb2.js",revision:"353479d33da19bb2"},{url:"/_next/static/chunks/app/layout-542bc44b26b9e572.js",revision:"542bc44b26b9e572"},{url:"/_next/static/chunks/app/legal/cookies/page-c39a3fa6e27a8806.js",revision:"c39a3fa6e27a8806"},{url:"/_next/static/chunks/app/legal/eula/page-8015f749ab4dd660.js",revision:"8015f749ab4dd660"},{url:"/_next/static/chunks/app/legal/page-3de074f0b9741bc6.js",revision:"3de074f0b9741bc6"},{url:"/_next/static/chunks/app/legal/privacy/page-3cb58024b6fd8e21.js",revision:"3cb58024b6fd8e21"},{url:"/_next/static/chunks/app/legal/terms/page-b5a1c96cae251767.js",revision:"b5a1c96cae251767"},{url:"/_next/static/chunks/app/logout/page-82ffa2a8f9b19c05.js",revision:"82ffa2a8f9b19c05"},{url:"/_next/static/chunks/app/offline/page-28c005360c2b2736.js",revision:"28c005360c2b2736"},{url:"/_next/static/chunks/app/page-a3c318199e34d81a.js",revision:"a3c318199e34d81a"},{url:"/_next/static/chunks/app/settings/page-defd290e5f83d748.js",revision:"defd290e5f83d748"},{url:"/_next/static/chunks/app/track/activity/page-5a7c9c4ba32c547e.js",revision:"5a7c9c4ba32c547e"},{url:"/_next/static/chunks/app/track/diaper/page-a46f1a6342e09976.js",revision:"a46f1a6342e09976"},{url:"/_next/static/chunks/app/track/feeding/page-50d04c323c3920a8.js",revision:"50d04c323c3920a8"},{url:"/_next/static/chunks/app/track/growth/page-a7aa8f6196c26c5f.js",revision:"a7aa8f6196c26c5f"},{url:"/_next/static/chunks/app/track/medicine/page-ba39d82f93b4a318.js",revision:"ba39d82f93b4a318"},{url:"/_next/static/chunks/app/track/page-dd5ade1eb19ad389.js",revision:"dd5ade1eb19ad389"},{url:"/_next/static/chunks/app/track/sleep/page-b586a2d14249bb9a.js",revision:"b586a2d14249bb9a"},{url:"/_next/static/chunks/framework-bd61ec64032c2de7.js",revision:"bd61ec64032c2de7"},{url:"/_next/static/chunks/main-520e5ec2d671abe7.js",revision:"520e5ec2d671abe7"},{url:"/_next/static/chunks/main-app-02fc3649960ba6c7.js",revision:"02fc3649960ba6c7"},{url:"/_next/static/chunks/pages/_app-4b3fb5e477a0267f.js",revision:"4b3fb5e477a0267f"},{url:"/_next/static/chunks/pages/_error-c970d8b55ace1b48.js",revision:"c970d8b55ace1b48"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-45f02cdad7c631c1.js",revision:"45f02cdad7c631c1"},{url:"/_next/static/css/76c3c1634e215e77.css",revision:"76c3c1634e215e77"},{url:"/_next/static/media/19cfc7226ec3afaa-s.woff2",revision:"9dda5cfc9a46f256d0e131bb535e46f8"},{url:"/_next/static/media/21350d82a1f187e9-s.woff2",revision:"4e2553027f1d60eff32898367dd4d541"},{url:"/_next/static/media/8e9860b6e62d6359-s.woff2",revision:"01ba6c2a184b8cba08b0d57167664d75"},{url:"/_next/static/media/ba9851c3c22cd980-s.woff2",revision:"9e494903d6b0ffec1a1e14d34427d44d"},{url:"/_next/static/media/c5fe6dc8356a8c31-s.woff2",revision:"027a89e9ab733a145db70f09b8a18b42"},{url:"/_next/static/media/df0a9ae256c0569c-s.woff2",revision:"d54db44de5ccb18886ece2fda72bdfe0"},{url:"/_next/static/media/e4af272ccee01ff0-s.p.woff2",revision:"65850a373e258f1c897a2b3d75eb74de"},{url:"/apple-touch-icon.png",revision:"fa2d4d791b90148a18d49bc3bfd7a43a"},{url:"/check-updates.js",revision:"bc016a0ceb6c72a5fe9ba02ad05d78be"},{url:"/favicon-16x16.png",revision:"db2da3355c89a6149f6d9ee35ebe6bf3"},{url:"/favicon-32x32.png",revision:"0fd88d56aa584bd0546d05ffc63ef777"},{url:"/icon-192x192.png",revision:"b8ef7f117472c4399cceffea644eb8bd"},{url:"/icons/icon-128x128.png",revision:"96cff3b189d9c1daa1edf470290a90cd"},{url:"/icons/icon-144x144.png",revision:"b627c346c431d7e306005aec5f51baff"},{url:"/icons/icon-152x152.png",revision:"012071830c13d310e51f833baed531af"},{url:"/icons/icon-192x192.png",revision:"dfb20132ddb628237eccd4b0e2ee4aaa"},{url:"/icons/icon-384x384.png",revision:"d032b25376232878a2a29b5688992a8d"},{url:"/icons/icon-512x512.png",revision:"ffda0043571d60956f4e321cba706670"},{url:"/icons/icon-72x72.png",revision:"cc89e74126e7e1109f0186774b3c0d77"},{url:"/icons/icon-96x96.png",revision:"32813cdad5b636fc09eec01c7d705936"},{url:"/manifest.json",revision:"5cbf1ecd33b05c4772688ce7d00c2c23"},{url:"/next.svg",revision:"8e061864f388b47f33a1c3780831193e"},{url:"/vercel.svg",revision:"61c6b19abff40ea7acd577be818f3976"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:a,event:s,state:c})=>a&&"opaqueredirect"===a.type?new Response(a.body,{status:200,statusText:"OK",headers:a.headers}):a}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/api\/.*$/i,new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/.*/i,new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET")});