From 788be7cd3201d4143304a16e691dabb7cef4b5ab Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 2 Oct 2025 14:46:18 +0000 Subject: [PATCH] Fix daily summary to display real activity counts and add medicine tracker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Backend Changes - Update tracking.service.ts getDailySummary to calculate actual counts - Import ActivityType enum for proper type comparisons - Calculate feedingCount, sleepTotalMinutes, diaperCount, medicationCount - Sleep duration now correctly calculated from startedAt/endedAt timestamps ## Frontend API Changes - Add medicationCount to DailySummary interface - Extract endTime from metadata and send as endedAt to backend - Enables proper sleep duration tracking with start/end times ## Homepage Updates - Add Medicine and Activities quick action buttons - Update summary grid from 3 to 4 columns (responsive layout) - Add medication count display with MedicalServices icon - Improve grid responsiveness (xs=6, sm=3) - Replace Analytics button with Activities button ## New Activities Page - Create /activities page to show recent activity history - Display last 7 days of activities with color-coded icons - Show smart timestamps (Today/Yesterday/date format) - Activity-specific descriptions (feeding amount, sleep duration, etc.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/modules/tracking/tracking.service.ts | 54 +++-- maternal-web/app/activities/page.tsx | 227 ++++++++++++++++++ maternal-web/app/page.tsx | 25 +- maternal-web/lib/api/tracking.ts | 9 +- 4 files changed, 295 insertions(+), 20 deletions(-) create mode 100644 maternal-web/app/activities/page.tsx diff --git a/maternal-app/maternal-app-backend/src/modules/tracking/tracking.service.ts b/maternal-app/maternal-app-backend/src/modules/tracking/tracking.service.ts index f7eb8cf..4627c72 100644 --- a/maternal-app/maternal-app-backend/src/modules/tracking/tracking.service.ts +++ b/maternal-app/maternal-app-backend/src/modules/tracking/tracking.service.ts @@ -6,7 +6,7 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Between, MoreThanOrEqual, LessThanOrEqual } from 'typeorm'; -import { Activity } from '../../database/entities/activity.entity'; +import { Activity, ActivityType } from '../../database/entities/activity.entity'; import { Child } from '../../database/entities/child.entity'; import { FamilyMember } from '../../database/entities/family-member.entity'; import { CreateActivityDto } from './dto/create-activity.dto'; @@ -255,31 +255,57 @@ export class TrackingService { }, }); - // Group by type - const summary: Record = { - date, - childId, - activities: [], - byType: {}, - }; + // Calculate summary statistics + let feedingCount = 0; + let sleepTotalMinutes = 0; + let diaperCount = 0; + let medicationCount = 0; + + const activityList = []; + const byType: Record = {}; activities.forEach((activity) => { - summary.activities.push({ + const activityData = { id: activity.id, type: activity.type, startedAt: activity.startedAt, endedAt: activity.endedAt, notes: activity.notes, metadata: activity.metadata, - }); + }; - if (!summary.byType[activity.type]) { - summary.byType[activity.type] = []; + activityList.push(activityData); + + if (!byType[activity.type]) { + byType[activity.type] = []; } + byType[activity.type].push(activity); - summary.byType[activity.type].push(activity); + // Count by type + if (activity.type === ActivityType.FEEDING) { + feedingCount++; + } else if (activity.type === ActivityType.SLEEP) { + // Calculate sleep duration in minutes + if (activity.startedAt && activity.endedAt) { + const durationMs = new Date(activity.endedAt).getTime() - new Date(activity.startedAt).getTime(); + sleepTotalMinutes += Math.floor(durationMs / 60000); + } + } else if (activity.type === ActivityType.DIAPER) { + diaperCount++; + } else if (activity.type === ActivityType.MEDICATION) { + medicationCount++; + } }); - return summary; + return { + date, + childId, + feedingCount, + sleepTotalMinutes, + diaperCount, + medicationCount, + activities: activityList, + byType, + }; } } \ No newline at end of file diff --git a/maternal-web/app/activities/page.tsx b/maternal-web/app/activities/page.tsx new file mode 100644 index 0000000..46a5660 --- /dev/null +++ b/maternal-web/app/activities/page.tsx @@ -0,0 +1,227 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Box, + Typography, + Paper, + List, + ListItem, + ListItemIcon, + ListItemText, + Chip, + CircularProgress, +} from '@mui/material'; +import { + Restaurant, + Hotel, + BabyChangingStation, + MedicalServices, + EmojiEvents, + Note, +} from '@mui/icons-material'; +import { AppShell } from '@/components/layouts/AppShell/AppShell'; +import { ProtectedRoute } from '@/components/common/ProtectedRoute'; +import { useAuth } from '@/lib/auth/AuthContext'; +import { childrenApi, Child } from '@/lib/api/children'; +import { trackingApi, Activity } from '@/lib/api/tracking'; +import { format } from 'date-fns'; + +const activityIcons: Record = { + feeding: , + sleep: , + diaper: , + medication: , + milestone: , + note: , +}; + +const activityColors: Record = { + feeding: '#FFB6C1', + sleep: '#B6D7FF', + diaper: '#FFE4B5', + medication: '#FFB8B8', + milestone: '#FFD700', + note: '#E0E0E0', +}; + +export default function ActivitiesPage() { + const { user } = useAuth(); + const [children, setChildren] = useState([]); + const [selectedChild, setSelectedChild] = useState(null); + const [activities, setActivities] = useState([]); + const [loading, setLoading] = useState(true); + + const familyId = user?.families?.[0]?.familyId; + + useEffect(() => { + const loadData = async () => { + if (!familyId) { + setLoading(false); + return; + } + + try { + const childrenData = await childrenApi.getChildren(familyId); + setChildren(childrenData); + + if (childrenData.length > 0) { + const firstChild = childrenData[0]; + setSelectedChild(firstChild); + + // Load activities for the last 7 days + const endDate = format(new Date(), 'yyyy-MM-dd'); + const startDate = format( + new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), + 'yyyy-MM-dd' + ); + + const activitiesData = await trackingApi.getActivities( + firstChild.id, + undefined, + startDate, + endDate + ); + setActivities(activitiesData); + } + } catch (error) { + console.error('[ActivitiesPage] Failed to load data:', error); + } finally { + setLoading(false); + } + }; + + loadData(); + }, [familyId]); + + const formatActivityTime = (timestamp: string) => { + const date = new Date(timestamp); + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(yesterday.getDate() - 1); + + const isToday = date.toDateString() === today.toDateString(); + const isYesterday = date.toDateString() === yesterday.toDateString(); + + if (isToday) { + return `Today at ${format(date, 'h:mm a')}`; + } else if (isYesterday) { + return `Yesterday at ${format(date, 'h:mm a')}`; + } else { + return format(date, 'MMM d, h:mm a'); + } + }; + + const getActivityDescription = (activity: Activity) => { + switch (activity.type) { + case 'feeding': + return activity.data?.amount + ? `${activity.data.amount} ${activity.data.unit || 'oz'}` + : 'Feeding'; + case 'sleep': + if (activity.data?.endedAt) { + const duration = Math.floor( + (new Date(activity.data.endedAt).getTime() - + new Date(activity.timestamp).getTime()) / + 60000 + ); + const hours = Math.floor(duration / 60); + const mins = duration % 60; + return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`; + } + return 'Sleep'; + case 'diaper': + return activity.data?.type || 'Diaper change'; + case 'medication': + return activity.data?.name || 'Medication'; + default: + return activity.type; + } + }; + + return ( + + + + + Recent Activities + + + {loading ? ( + + + + ) : activities.length === 0 ? ( + + + No activities recorded yet + + + ) : ( + + + {activities.map((activity, index) => ( + + + + {activityIcons[activity.type] || } + + + + + {getActivityDescription(activity)} + + + + } + secondary={ + + + {formatActivityTime(activity.timestamp)} + + {activity.notes && ( + + {activity.notes} + + )} + + } + /> + + ))} + + + )} + + + + ); +} diff --git a/maternal-web/app/page.tsx b/maternal-web/app/page.tsx index 9d3cc8c..cc38151 100644 --- a/maternal-web/app/page.tsx +++ b/maternal-web/app/page.tsx @@ -16,6 +16,7 @@ import { Insights, SmartToy, Analytics, + MedicalServices, } from '@mui/icons-material'; import { motion } from 'framer-motion'; import { useAuth } from '@/lib/auth/AuthContext'; @@ -64,7 +65,9 @@ export default function HomePage() { // Load today's summary for first child const today = format(new Date(), 'yyyy-MM-dd'); + console.log('[HomePage] Fetching daily summary for child:', firstChild.id, 'date:', today); const summary = await trackingApi.getDailySummary(firstChild.id, today); + console.log('[HomePage] Daily summary response:', summary); setDailySummary(summary); } } catch (error) { @@ -81,8 +84,9 @@ export default function HomePage() { { icon: , label: 'Feeding', color: '#FFB6C1', path: '/track/feeding' }, { icon: , label: 'Sleep', color: '#B6D7FF', path: '/track/sleep' }, { icon: , label: 'Diaper', color: '#FFE4B5', path: '/track/diaper' }, + { icon: , label: 'Medicine', color: '#FFB8B8', path: '/track/medication' }, + { icon: , label: 'Activities', color: '#C5E1A5', path: '/activities' }, { icon: , label: 'AI Assistant', color: '#FFD3B6', path: '/ai-assistant' }, - { icon: , label: 'Analytics', color: '#D4B5FF', path: '/analytics' }, ]; const formatSleepHours = (minutes: number) => { @@ -122,7 +126,7 @@ export default function HomePage() { {quickActions.map((action, index) => ( - + ) : ( - + @@ -185,7 +189,7 @@ export default function HomePage() { - + @@ -198,7 +202,7 @@ export default function HomePage() { - + @@ -209,6 +213,17 @@ export default function HomePage() { + + + + + {dailySummary.medicationCount || 0} + + + Medications + + + )} diff --git a/maternal-web/lib/api/tracking.ts b/maternal-web/lib/api/tracking.ts index b599d93..ae27b7a 100644 --- a/maternal-web/lib/api/tracking.ts +++ b/maternal-web/lib/api/tracking.ts @@ -27,6 +27,7 @@ export interface DailySummary { feedingCount: number; sleepTotalMinutes: number; diaperCount: number; + medicationCount: number; activities: Activity[]; } @@ -68,12 +69,18 @@ export const trackingApi = { // Create a new activity createActivity: async (childId: string, data: CreateActivityData): Promise => { // Transform frontend data structure to backend DTO format - const payload = { + const payload: any = { type: data.type, startedAt: data.timestamp, // Backend expects startedAt, not timestamp metadata: data.data, // Backend expects metadata, not data notes: data.notes, }; + + // Extract endTime from metadata and set as endedAt (for sleep tracking) + if (data.data?.endTime) { + payload.endedAt = data.data.endTime; + } + const response = await apiClient.post(`/api/v1/activities?childId=${childId}`, payload); const activity = response.data.data.activity; // Transform backend response to frontend format