diff --git a/src/baby_monitor/static/diapers.html b/src/baby_monitor/static/diapers.html index b02239c..7183d92 100644 --- a/src/baby_monitor/static/diapers.html +++ b/src/baby_monitor/static/diapers.html @@ -281,6 +281,9 @@ let currentDaysRange = parseInt(localStorage.getItem("lastDiaperTimeRange")) || 7; let selectedChildId = null; // null means show first/only child + let barChartInstance = null; // Track chart instance for proper cleanup + let poopPieChartInstance = null; + let peePieChartInstance = null; // Restore last selected child from localStorage const lastChildId = localStorage.getItem("lastDiaperChildFilter"); @@ -782,7 +785,12 @@ function renderBarChart(chartData) { const ctx = document.getElementById("diaperChart").getContext("2d"); - new Chart(ctx, { + // Destroy existing chart instance if it exists + if (barChartInstance) { + barChartInstance.destroy(); + } + + barChartInstance = new Chart(ctx, { type: "bar", data: { labels: chartData.labels, @@ -875,6 +883,11 @@ function renderPoopPieChart(chartData) { const ctx = document.getElementById("poopPieChart").getContext("2d"); + // Destroy existing chart instance if it exists + if (poopPieChartInstance) { + poopPieChartInstance.destroy(); + } + const colors = { Light: { bg: "rgba(139, 69, 19, 0.5)", @@ -901,7 +914,7 @@ (label) => colors[label]?.border || "rgba(200, 200, 200, 1)", ); - new Chart(ctx, { + poopPieChartInstance = new Chart(ctx, { type: "pie", data: { labels: chartData.labels, @@ -957,6 +970,11 @@ function renderPeePieChart(chartData) { const ctx = document.getElementById("peePieChart").getContext("2d"); + // Destroy existing chart instance if it exists + if (peePieChartInstance) { + peePieChartInstance.destroy(); + } + const colors = { Light: { bg: "rgba(255, 215, 0, 0.5)", @@ -983,7 +1001,7 @@ (label) => colors[label]?.border || "rgba(200, 200, 200, 1)", ); - new Chart(ctx, { + peePieChartInstance = new Chart(ctx, { type: "pie", data: { labels: chartData.labels, diff --git a/src/baby_monitor/static/feedings.html b/src/baby_monitor/static/feedings.html index 3336189..241b33a 100644 --- a/src/baby_monitor/static/feedings.html +++ b/src/baby_monitor/static/feedings.html @@ -319,6 +319,8 @@ parseInt(localStorage.getItem("lastFeedingTimeRange")) || 7; let selectedChildId = null; // null means show first/only child let barChartInstance = null; + let pieChartInstance = null; + let durationChartInstance = null; // Restore last selected child from localStorage const lastChildId = localStorage.getItem("lastFeedingChildFilter"); @@ -979,6 +981,11 @@ function renderPieChart(chartData) { const ctx = document.getElementById("pieChart").getContext("2d"); + // Destroy existing chart instance if it exists + if (pieChartInstance) { + pieChartInstance.destroy(); + } + // Color mapping for each type const colorMap = { "Left Breast": { @@ -1003,7 +1010,7 @@ (label) => colorMap[label]?.border || "rgba(200, 200, 200, 1)", ); - new Chart(ctx, { + pieChartInstance = new Chart(ctx, { type: "pie", data: { labels: chartData.labels, @@ -1059,7 +1066,12 @@ function renderDurationChart(chartData) { const ctx = document.getElementById("durationChart").getContext("2d"); - new Chart(ctx, { + // Destroy existing chart instance if it exists + if (durationChartInstance) { + durationChartInstance.destroy(); + } + + durationChartInstance = new Chart(ctx, { type: "bar", data: { labels: chartData.labels, diff --git a/src/baby_monitor/static/index.html b/src/baby_monitor/static/index.html index c382d67..153c7b2 100644 --- a/src/baby_monitor/static/index.html +++ b/src/baby_monitor/static/index.html @@ -167,6 +167,52 @@ margin-top: 0.5rem; opacity: 0.9; } + .recent-activity { + margin-top: 2rem; + padding: 1.5rem; + background: #f8f9fa; + border-radius: 8px; + } + .recent-activity h3 { + margin-top: 0; + margin-bottom: 1rem; + color: #333; + font-size: 1.1rem; + } + .activity-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + } + .activity-card { + background: white; + padding: 1rem; + border-radius: 6px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + } + .activity-card .icon { + font-size: 1.5rem; + margin-bottom: 0.5rem; + } + .activity-card .type { + font-weight: 600; + color: #333; + margin-bottom: 0.25rem; + } + .activity-card .time { + font-size: 0.85rem; + color: #666; + } + .activity-card .details { + font-size: 0.85rem; + color: #888; + margin-top: 0.5rem; + } + .activity-card.empty { + opacity: 0.5; + font-style: italic; + color: #999; + } @@ -321,6 +367,7 @@ let activeSleep = null; let lastSleep = null; let sleepUpdateTimerInterval = null; + let lastDiaperChange = null; async function loadFeedingStatus(children) { userChildren = children; @@ -376,6 +423,20 @@ lastSleep = sleeps[0]; } } + + // Fetch all diaper changes to get the last one + const diaperResponse = await fetch("/api/diaper-changes", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (diaperResponse.ok) { + const diapers = await diaperResponse.json(); + if (diapers.length > 0) { + lastDiaperChange = diapers[0]; + } + } } catch (error) { console.error("Error loading feeding status:", error); } @@ -478,9 +539,98 @@ ${feedingButtonHtml} ${sleepButtonHtml} + ${buildRecentActivitySection()} `; } + function buildRecentActivitySection() { + // Build diaper card + const diaperCard = lastDiaperChange + ? ` +
+
๐Ÿงท
+
Last Diaper
+
${formatTime(lastDiaperChange.change_time)}
+
+ ${lastDiaperChange.poop_amount ? `๐Ÿ’ฉ ${lastDiaperChange.poop_amount}` : ""} + ${lastDiaperChange.poop_amount && lastDiaperChange.pee_amount ? " โ€ข " : ""} + ${lastDiaperChange.pee_amount ? `๐Ÿ’ง ${lastDiaperChange.pee_amount}` : ""} +
+
+ ` + : ` +
+
๐Ÿงท
+
No diaper changes yet
+
+ `; + + // Build feeding card + const feedingCard = lastFeeding + ? ` +
+
๐Ÿผ
+
Last Feeding
+
${formatTime(lastFeeding.start_time)}
+
+ ${formatFeedingType(lastFeeding.feeding_type)} + ${lastFeeding.end_time ? ` โ€ข ${calculateDuration(lastFeeding.start_time, lastFeeding.end_time)}` : " (ongoing)"} +
+
+ ` + : ` +
+
๐Ÿผ
+
No feedings yet
+
+ `; + + // Build sleep card + const sleepCard = lastSleep + ? ` +
+
๐Ÿ˜ด
+
Last Sleep
+
${formatTime(lastSleep.start_time)}
+
+ ${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"} +
+
+ ` + : ` +
+
๐Ÿ˜ด
+
No sleep sessions yet
+
+ `; + + return ` +
+

๐Ÿ“Š Recent Activity

+
+ ${diaperCard} + ${feedingCard} + ${sleepCard} +
+
+ `; + } + + function calculateDuration(startTime, endTime) { + const start = new Date(startTime); + const end = new Date(endTime); + const diffMs = end - start; + const diffMins = Math.floor(diffMs / 60000); + + if (diffMins < 60) { + return `${diffMins} min`; + } + + const hours = Math.floor(diffMins / 60); + const mins = diffMins % 60; + return `${hours}h ${mins}m`; + } + function formatFeedingType(type) { const types = { left_breast: "Left Breast",