From a3cbe2259222dffa3ab0ee210bc6ceabb452a91e Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 4 Oct 2025 21:43:05 +0000 Subject: [PATCH] feat: Implement dynamic dashboard with tabs/cards for multi-child families - Create DynamicChildDashboard component with tab view (1-3 children) and card view (4+) - Integrate with Redux viewMode selector for automatic layout switching - Update GraphQL dashboard query to include displayColor, sortOrder, nickname - Replace static summary with dynamic multi-child dashboard - Add child selection handling with Redux state sync - Implement compact metrics display for card view - Build and test successfully --- maternal-web/app/page.tsx | 159 ++++----- .../dashboard/DynamicChildDashboard.tsx | 312 ++++++++++++++++++ maternal-web/graphql/queries/dashboard.ts | 6 + maternal-web/public/sw.js | 2 +- 4 files changed, 376 insertions(+), 103 deletions(-) create mode 100644 maternal-web/components/dashboard/DynamicChildDashboard.tsx diff --git a/maternal-web/app/page.tsx b/maternal-web/app/page.tsx index a899516..439346f 100644 --- a/maternal-web/app/page.tsx +++ b/maternal-web/app/page.tsx @@ -9,6 +9,7 @@ import { ErrorBoundary } from '@/components/common/ErrorBoundary'; import { DataErrorFallback } from '@/components/common/ErrorFallbacks'; import { NetworkStatusIndicator } from '@/components/common/NetworkStatusIndicator'; import { StatGridSkeleton } from '@/components/common/LoadingSkeletons'; +import DynamicChildDashboard from '@/components/dashboard/DynamicChildDashboard'; import { Restaurant, Hotel, @@ -28,16 +29,29 @@ import { useRealTimeActivities } from '@/hooks/useWebSocket'; import { useTranslation } from '@/hooks/useTranslation'; import { analyticsApi } from '@/lib/api/analytics'; import { useTheme } from '@mui/material/styles'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchChildren, selectSelectedChild } from '@/store/slices/childrenSlice'; +import { AppDispatch, RootState } from '@/store/store'; export default function HomePage() { const { t } = useTranslation('dashboard'); const theme = useTheme(); const { user, isLoading: authLoading } = useAuth(); const router = useRouter(); + const dispatch = useDispatch(); + const selectedChild = useSelector(selectSelectedChild); + const familyId = useSelector((state: RootState) => state.auth.user?.familyId); const [selectedChildId, setSelectedChildId] = useState(null); const [predictions, setPredictions] = useState(null); const [predictionsLoading, setPredictionsLoading] = useState(false); + // Fetch children on mount + useEffect(() => { + if (familyId && !authLoading) { + dispatch(fetchChildren(familyId)); + } + }, [familyId, authLoading, dispatch]); + // GraphQL query for dashboard data const { data, loading, error, refetch } = useQuery(GET_DASHBOARD, { variables: { childId: selectedChildId }, @@ -99,6 +113,13 @@ export default function HomePage() { } }, [data, selectedChildId]); + // Sync selectedChildId with Redux selectedChild + useEffect(() => { + if (selectedChild?.id && selectedChild.id !== selectedChildId) { + setSelectedChildId(selectedChild.id); + } + }, [selectedChild, selectedChildId]); + // Fetch predictions when selectedChildId changes useEffect(() => { const fetchPredictions = async () => { @@ -142,10 +163,36 @@ export default function HomePage() { // Extract data from GraphQL response const children = data?.dashboard?.children || []; - const selectedChild = data?.dashboard?.selectedChild; + const graphqlSelectedChild = data?.dashboard?.selectedChild; const dailySummary = data?.dashboard?.todaySummary; const isLoading = authLoading || loading; + // Build child metrics object for DynamicChildDashboard + const childMetrics = children.reduce((acc: any, child: any) => { + // For now, use the same summary for selected child, or zero for others + // TODO: Fetch per-child summaries from backend + if (child.id === selectedChildId && dailySummary) { + acc[child.id] = { + feedingCount: dailySummary.feedingCount || 0, + sleepDuration: dailySummary.totalSleepDuration || 0, + diaperCount: dailySummary.diaperCount || 0, + medicationCount: dailySummary.medicationCount || 0, + }; + } else { + acc[child.id] = { + feedingCount: 0, + sleepDuration: 0, + diaperCount: 0, + medicationCount: 0, + }; + } + return acc; + }, {}); + + const handleChildSelect = (childId: string) => { + setSelectedChildId(childId); + }; + return ( @@ -223,9 +270,9 @@ export default function HomePage() { ))} - {/* Today's Summary */} + {/* Today's Summary - Dynamic Multi-Child Dashboard */} - {selectedChild ? t('summary.titleWithChild', { childName: selectedChild.name }) : t('summary.title')} + {t('summary.title')} {isLoading ? ( - ) : ( + ) : children.length === 0 ? ( - {!dailySummary ? ( - {children.length === 0 - ? t('summary.noChild') - : t('summary.noActivities')} + {t('summary.noChild')} - ) : ( - - - - - - - - - - - - - - - - - - - )} + ) : ( + )} diff --git a/maternal-web/components/dashboard/DynamicChildDashboard.tsx b/maternal-web/components/dashboard/DynamicChildDashboard.tsx new file mode 100644 index 0000000..b591458 --- /dev/null +++ b/maternal-web/components/dashboard/DynamicChildDashboard.tsx @@ -0,0 +1,312 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Box, + Tabs, + Tab, + Card, + CardContent, + Avatar, + Typography, + Grid, + Chip, + Paper, +} from '@mui/material'; +import { useSelector, useDispatch } from 'react-redux'; +import { + childrenSelectors, + selectViewMode, + selectSelectedChild, + selectChild, + Child, +} from '@/store/slices/childrenSlice'; +import { RootState } from '@/store/store'; +import { + Restaurant, + Hotel, + BabyChangingStation, + MedicalServices, +} from '@mui/icons-material'; + +interface DashboardMetrics { + feedingCount: number; + sleepDuration: number; + diaperCount: number; + medicationCount: number; +} + +interface DynamicChildDashboardProps { + childMetrics: Record; + onChildSelect: (childId: string) => void; +} + +export default function DynamicChildDashboard({ + childMetrics, + onChildSelect, +}: DynamicChildDashboardProps) { + const dispatch = useDispatch(); + const children = useSelector((state: RootState) => childrenSelectors.selectAll(state)); + const viewMode = useSelector(selectViewMode); + const selectedChild = useSelector(selectSelectedChild); + const [selectedTab, setSelectedTab] = useState(''); + + // Initialize selected tab + useEffect(() => { + if (selectedChild?.id) { + setSelectedTab(selectedChild.id); + } else if (children.length > 0) { + const firstChildId = children[0].id; + setSelectedTab(firstChildId); + dispatch(selectChild(firstChildId)); + onChildSelect(firstChildId); + } + }, [selectedChild, children, dispatch, onChildSelect]); + + const handleTabChange = (_event: React.SyntheticEvent, newValue: string) => { + setSelectedTab(newValue); + dispatch(selectChild(newValue)); + onChildSelect(newValue); + }; + + const handleCardClick = (childId: string) => { + setSelectedTab(childId); + dispatch(selectChild(childId)); + onChildSelect(childId); + }; + + const formatSleepHours = (minutes: number) => { + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + if (hours > 0 && mins > 0) { + return `${hours}h ${mins}m`; + } else if (hours > 0) { + return `${hours}h`; + } else { + return `${mins}m`; + } + }; + + const renderMetrics = (child: Child, isCompact: boolean = false) => { + const metrics = childMetrics[child.id] || { + feedingCount: 0, + sleepDuration: 0, + diaperCount: 0, + medicationCount: 0, + }; + + if (isCompact) { + return ( + + } + label={`${metrics.feedingCount} feeds`} + size="small" + variant="outlined" + /> + } + label={formatSleepHours(metrics.sleepDuration)} + size="small" + variant="outlined" + /> + } + label={`${metrics.diaperCount} diapers`} + size="small" + variant="outlined" + /> + {metrics.medicationCount > 0 && ( + } + label={`${metrics.medicationCount} meds`} + size="small" + variant="outlined" + /> + )} + + ); + } + + return ( + + + + + + {metrics.feedingCount} + + + Feedings + + + + + + + + {formatSleepHours(metrics.sleepDuration)} + + + Sleep + + + + + + + + {metrics.diaperCount} + + + Diapers + + + + + + + + {metrics.medicationCount} + + + Medications + + + + + ); + }; + + // Tab view for 1-3 children + if (viewMode === 'tabs') { + return ( + + + {children.map((child) => ( + + + {child.name[0]} + + + + {child.name} + + {child.nickname && ( + + {child.nickname} + + )} + + + } + sx={{ + textTransform: 'none', + minHeight: 64, + }} + /> + ))} + + + {children.map((child) => ( + + ))} + + ); + } + + // Card view for 4+ children + return ( + + + Select a child to view details + + + {children.map((child) => ( + + handleCardClick(child.id)} + > + + + + {child.name[0]} + + + + {child.name} + + {child.nickname && ( + + {child.nickname} + + )} + + + {renderMetrics(child, true)} + + + + ))} + + + {/* Show detailed metrics for selected child */} + {selectedTab && ( + + + {children.find(c => c.id === selectedTab)?.name}'s Today + + {renderMetrics(children.find(c => c.id === selectedTab)!, false)} + + )} + + ); +} diff --git a/maternal-web/graphql/queries/dashboard.ts b/maternal-web/graphql/queries/dashboard.ts index ac61adc..47bdc39 100644 --- a/maternal-web/graphql/queries/dashboard.ts +++ b/maternal-web/graphql/queries/dashboard.ts @@ -9,6 +9,9 @@ export const GET_DASHBOARD = gql` birthDate gender photoUrl + displayColor + sortOrder + nickname } selectedChild { id @@ -16,6 +19,9 @@ export const GET_DASHBOARD = gql` birthDate gender photoUrl + displayColor + sortOrder + nickname } recentActivities { id diff --git a/maternal-web/public/sw.js b/maternal-web/public/sw.js index b5b7e24..9d01c7a 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 d=e=>s(e,n),r={module:{uri:n},exports:t,require:d};a[n]=Promise.all(c.map(e=>r[e]||d(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:"d7500f7fee253390637ac42de2fe4521"},{url:"/_next/static/a51JgIgMTai8D3he2Zczd/_buildManifest.js",revision:"003849461a7dd4bad470c5d1d5e5254c"},{url:"/_next/static/a51JgIgMTai8D3he2Zczd/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/1213-7820689c8a23df1d.js",revision:"7820689c8a23df1d"},{url:"/_next/static/chunks/1233-aa8672e107c5a9d6.js",revision:"aa8672e107c5a9d6"},{url:"/_next/static/chunks/1255-b2f7fd83e387a9e1.js",revision:"b2f7fd83e387a9e1"},{url:"/_next/static/chunks/1280-296e0a2b6e9dd9b1.js",revision:"296e0a2b6e9dd9b1"},{url:"/_next/static/chunks/1543-530e0f57f7af68aa.js",revision:"530e0f57f7af68aa"},{url:"/_next/static/chunks/1863-7231108310f72246.js",revision:"7231108310f72246"},{url:"/_next/static/chunks/2262-26293d6453fcc927.js",revision:"26293d6453fcc927"},{url:"/_next/static/chunks/2349-f488a1827d114358.js",revision:"f488a1827d114358"},{url:"/_next/static/chunks/2449-b8a41aa6a7d2d3a4.js",revision:"b8a41aa6a7d2d3a4"},{url:"/_next/static/chunks/2619-04bc32f026a0d946.js",revision:"04bc32f026a0d946"},{url:"/_next/static/chunks/3039-0e9bf08230c8ee7b.js",revision:"0e9bf08230c8ee7b"},{url:"/_next/static/chunks/3505-dc772ce29ac0b276.js",revision:"dc772ce29ac0b276"},{url:"/_next/static/chunks/3762-c2c13ecf11b3eabb.js",revision:"c2c13ecf11b3eabb"},{url:"/_next/static/chunks/3782-ec9e7e72c6eacec1.js",revision:"ec9e7e72c6eacec1"},{url:"/_next/static/chunks/3823-7d22f3a064856b06.js",revision:"7d22f3a064856b06"},{url:"/_next/static/chunks/4546-3be482382b443121.js",revision:"3be482382b443121"},{url:"/_next/static/chunks/4bd1b696-100b9d70ed4e49c1.js",revision:"100b9d70ed4e49c1"},{url:"/_next/static/chunks/5125-c990fc036d2a6ce4.js",revision:"c990fc036d2a6ce4"},{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/5491-75a34ac5f4b1bc71.js",revision:"75a34ac5f4b1bc71"},{url:"/_next/static/chunks/6088-c165c565edce02be.js",revision:"c165c565edce02be"},{url:"/_next/static/chunks/6107-8fb7b82c50ce5ddd.js",revision:"8fb7b82c50ce5ddd"},{url:"/_next/static/chunks/6191.e178f0fbe1b1be57.js",revision:"e178f0fbe1b1be57"},{url:"/_next/static/chunks/670-a4ca0f366ee779f5.js",revision:"a4ca0f366ee779f5"},{url:"/_next/static/chunks/6847-ce99bc721adda9c4.js",revision:"ce99bc721adda9c4"},{url:"/_next/static/chunks/6873-ff265086321345c8.js",revision:"ff265086321345c8"},{url:"/_next/static/chunks/6886-40f1779ffff00d58.js",revision:"40f1779ffff00d58"},{url:"/_next/static/chunks/6937.f8d44316fed7bc8e.js",revision:"f8d44316fed7bc8e"},{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/8126-48064e6c5d5794c7.js",revision:"48064e6c5d5794c7"},{url:"/_next/static/chunks/8221-d51102291d5ddaf9.js",revision:"d51102291d5ddaf9"},{url:"/_next/static/chunks/8241-eaf1b9c6054e9ad8.js",revision:"eaf1b9c6054e9ad8"},{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/8876-26dea77829b2c9a0.js",revision:"26dea77829b2c9a0"},{url:"/_next/static/chunks/9205-f540995b767df00b.js",revision:"f540995b767df00b"},{url:"/_next/static/chunks/9241-01664d98236f70ec.js",revision:"01664d98236f70ec"},{url:"/_next/static/chunks/9333-17f3dbe8f3dcc2d0.js",revision:"17f3dbe8f3dcc2d0"},{url:"/_next/static/chunks/9378-4fb7500ab3ba2b2b.js",revision:"4fb7500ab3ba2b2b"},{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/9522-fc1414d221355408.js",revision:"fc1414d221355408"},{url:"/_next/static/chunks/9738-c70b13d86cc3ea77.js",revision:"c70b13d86cc3ea77"},{url:"/_next/static/chunks/9958.57780b11643f5bd9.js",revision:"57780b11643f5bd9"},{url:"/_next/static/chunks/app/(auth)/forgot-password/page-fc9fe10cd0da6b8c.js",revision:"fc9fe10cd0da6b8c"},{url:"/_next/static/chunks/app/(auth)/login/page-d9a5d5755d9f403f.js",revision:"d9a5d5755d9f403f"},{url:"/_next/static/chunks/app/(auth)/onboarding/page-2f1efbb88e25690c.js",revision:"2f1efbb88e25690c"},{url:"/_next/static/chunks/app/(auth)/register/page-562dc7bf8851de3f.js",revision:"562dc7bf8851de3f"},{url:"/_next/static/chunks/app/(auth)/reset-password/page-877e00efc3824f99.js",revision:"877e00efc3824f99"},{url:"/_next/static/chunks/app/_not-found/page-95f11f5fe94340f1.js",revision:"95f11f5fe94340f1"},{url:"/_next/static/chunks/app/ai-assistant/page-48a8ac4a67cba5b6.js",revision:"48a8ac4a67cba5b6"},{url:"/_next/static/chunks/app/analytics/page-09432cf896153f96.js",revision:"09432cf896153f96"},{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-8a92f3d5521369bb.js",revision:"8a92f3d5521369bb"},{url:"/_next/static/chunks/app/family/page-3bf65f3ee77dd58f.js",revision:"3bf65f3ee77dd58f"},{url:"/_next/static/chunks/app/history/page-9f7b12a4bfa73be8.js",revision:"9f7b12a4bfa73be8"},{url:"/_next/static/chunks/app/insights/page-84991e82bea095c8.js",revision:"84991e82bea095c8"},{url:"/_next/static/chunks/app/layout-9521b2b894149e0d.js",revision:"9521b2b894149e0d"},{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-13b0bb5ffcd6597a.js",revision:"13b0bb5ffcd6597a"},{url:"/_next/static/chunks/app/settings/page-bacfdd22168d67f0.js",revision:"bacfdd22168d67f0"},{url:"/_next/static/chunks/app/track/activity/page-8d314ae80ae707da.js",revision:"8d314ae80ae707da"},{url:"/_next/static/chunks/app/track/diaper/page-87f3340684ba5beb.js",revision:"87f3340684ba5beb"},{url:"/_next/static/chunks/app/track/feeding/page-67ce4d6c1d0280f7.js",revision:"67ce4d6c1d0280f7"},{url:"/_next/static/chunks/app/track/growth/page-dd3130f009f9751a.js",revision:"dd3130f009f9751a"},{url:"/_next/static/chunks/app/track/medicine/page-9faf46b4c6ddcd92.js",revision:"9faf46b4c6ddcd92"},{url:"/_next/static/chunks/app/track/page-dd5ade1eb19ad389.js",revision:"dd5ade1eb19ad389"},{url:"/_next/static/chunks/app/track/sleep/page-2114dfbe2789d189.js",revision:"2114dfbe2789d189"},{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-b37dfa0737ad97e5.js",revision:"b37dfa0737ad97e5"},{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:"/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),d={module:{uri:n},exports:t,require:r};a[n]=Promise.all(c.map(e=>d[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:"f4a4328bc041aae7d3eefe0612aa55a0"},{url:"/_next/static/-g5XkaNa_mewGsLcat2PO/_buildManifest.js",revision:"003849461a7dd4bad470c5d1d5e5254c"},{url:"/_next/static/-g5XkaNa_mewGsLcat2PO/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/1233-aa8672e107c5a9d6.js",revision:"aa8672e107c5a9d6"},{url:"/_next/static/chunks/1255-b2f7fd83e387a9e1.js",revision:"b2f7fd83e387a9e1"},{url:"/_next/static/chunks/1280-296e0a2b6e9dd9b1.js",revision:"296e0a2b6e9dd9b1"},{url:"/_next/static/chunks/1543-530e0f57f7af68aa.js",revision:"530e0f57f7af68aa"},{url:"/_next/static/chunks/1863-7231108310f72246.js",revision:"7231108310f72246"},{url:"/_next/static/chunks/2262-26293d6453fcc927.js",revision:"26293d6453fcc927"},{url:"/_next/static/chunks/2449-b8a41aa6a7d2d3a4.js",revision:"b8a41aa6a7d2d3a4"},{url:"/_next/static/chunks/2619-04bc32f026a0d946.js",revision:"04bc32f026a0d946"},{url:"/_next/static/chunks/3039-0e9bf08230c8ee7b.js",revision:"0e9bf08230c8ee7b"},{url:"/_next/static/chunks/3190-1e2f8a49c25a4837.js",revision:"1e2f8a49c25a4837"},{url:"/_next/static/chunks/3762-c2c13ecf11b3eabb.js",revision:"c2c13ecf11b3eabb"},{url:"/_next/static/chunks/3823-7d22f3a064856b06.js",revision:"7d22f3a064856b06"},{url:"/_next/static/chunks/4384-fc5c0ec9da1b1013.js",revision:"fc5c0ec9da1b1013"},{url:"/_next/static/chunks/4546-3be482382b443121.js",revision:"3be482382b443121"},{url:"/_next/static/chunks/4bd1b696-100b9d70ed4e49c1.js",revision:"100b9d70ed4e49c1"},{url:"/_next/static/chunks/5125-c990fc036d2a6ce4.js",revision:"c990fc036d2a6ce4"},{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/6024-09f3a73b85a37fdd.js",revision:"09f3a73b85a37fdd"},{url:"/_next/static/chunks/6088-c165c565edce02be.js",revision:"c165c565edce02be"},{url:"/_next/static/chunks/6107-8fb7b82c50ce5ddd.js",revision:"8fb7b82c50ce5ddd"},{url:"/_next/static/chunks/6191.e178f0fbe1b1be57.js",revision:"e178f0fbe1b1be57"},{url:"/_next/static/chunks/6206-1b3859e1902bcca4.js",revision:"1b3859e1902bcca4"},{url:"/_next/static/chunks/670-a4ca0f366ee779f5.js",revision:"a4ca0f366ee779f5"},{url:"/_next/static/chunks/6847-ce99bc721adda9c4.js",revision:"ce99bc721adda9c4"},{url:"/_next/static/chunks/6873-ff265086321345c8.js",revision:"ff265086321345c8"},{url:"/_next/static/chunks/6886-40f1779ffff00d58.js",revision:"40f1779ffff00d58"},{url:"/_next/static/chunks/6937.f8d44316fed7bc8e.js",revision:"f8d44316fed7bc8e"},{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/8126-48064e6c5d5794c7.js",revision:"48064e6c5d5794c7"},{url:"/_next/static/chunks/8221-d51102291d5ddaf9.js",revision:"d51102291d5ddaf9"},{url:"/_next/static/chunks/8241-eaf1b9c6054e9ad8.js",revision:"eaf1b9c6054e9ad8"},{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/8876-26dea77829b2c9a0.js",revision:"26dea77829b2c9a0"},{url:"/_next/static/chunks/9001-fcb9cb356cb7c166.js",revision:"fcb9cb356cb7c166"},{url:"/_next/static/chunks/9205-f540995b767df00b.js",revision:"f540995b767df00b"},{url:"/_next/static/chunks/9241-c8dc90a3c6f79226.js",revision:"c8dc90a3c6f79226"},{url:"/_next/static/chunks/9333-46ca47c5bf6eb021.js",revision:"46ca47c5bf6eb021"},{url:"/_next/static/chunks/9378-4fb7500ab3ba2b2b.js",revision:"4fb7500ab3ba2b2b"},{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/9522-fc1414d221355408.js",revision:"fc1414d221355408"},{url:"/_next/static/chunks/9738-c70b13d86cc3ea77.js",revision:"c70b13d86cc3ea77"},{url:"/_next/static/chunks/9958.57780b11643f5bd9.js",revision:"57780b11643f5bd9"},{url:"/_next/static/chunks/app/(auth)/forgot-password/page-fc9fe10cd0da6b8c.js",revision:"fc9fe10cd0da6b8c"},{url:"/_next/static/chunks/app/(auth)/login/page-d9a5d5755d9f403f.js",revision:"d9a5d5755d9f403f"},{url:"/_next/static/chunks/app/(auth)/onboarding/page-63d65082b403775d.js",revision:"63d65082b403775d"},{url:"/_next/static/chunks/app/(auth)/register/page-562dc7bf8851de3f.js",revision:"562dc7bf8851de3f"},{url:"/_next/static/chunks/app/(auth)/reset-password/page-877e00efc3824f99.js",revision:"877e00efc3824f99"},{url:"/_next/static/chunks/app/_not-found/page-95f11f5fe94340f1.js",revision:"95f11f5fe94340f1"},{url:"/_next/static/chunks/app/ai-assistant/page-48a8ac4a67cba5b6.js",revision:"48a8ac4a67cba5b6"},{url:"/_next/static/chunks/app/analytics/page-e1d11bdbf4285918.js",revision:"e1d11bdbf4285918"},{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-80393e6323e81afa.js",revision:"80393e6323e81afa"},{url:"/_next/static/chunks/app/layout-bbe8b40879672627.js",revision:"bbe8b40879672627"},{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-71cd35b76e1948cc.js",revision:"71cd35b76e1948cc"},{url:"/_next/static/chunks/app/settings/page-f294b3798b0e4d2d.js",revision:"f294b3798b0e4d2d"},{url:"/_next/static/chunks/app/track/activity/page-8d314ae80ae707da.js",revision:"8d314ae80ae707da"},{url:"/_next/static/chunks/app/track/diaper/page-75d581a053150f41.js",revision:"75d581a053150f41"},{url:"/_next/static/chunks/app/track/feeding/page-67ce4d6c1d0280f7.js",revision:"67ce4d6c1d0280f7"},{url:"/_next/static/chunks/app/track/growth/page-dd3130f009f9751a.js",revision:"dd3130f009f9751a"},{url:"/_next/static/chunks/app/track/medicine/page-9faf46b4c6ddcd92.js",revision:"9faf46b4c6ddcd92"},{url:"/_next/static/chunks/app/track/page-dd5ade1eb19ad389.js",revision:"dd5ade1eb19ad389"},{url:"/_next/static/chunks/app/track/sleep/page-9b61896cfb01ea3e.js",revision:"9b61896cfb01ea3e"},{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-128733d3899f7e9f.js",revision:"128733d3899f7e9f"},{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:"/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")});