formatting fixes
Build and Push Docker Image / build-and-push (pull_request) Successful in 58s
Python Code Quality / python-code-quality (pull_request) Successful in 10s
Python Test / python-test (pull_request) Successful in 18s

This commit is contained in:
Brian Bjarke Jensen
2025-11-10 21:19:13 +01:00
parent cd3db4fb86
commit 6c3d301027
6 changed files with 268 additions and 169 deletions
+2 -6
View File
@@ -35,9 +35,7 @@ def create_sleep(
raise HTTPException(status_code=404, detail="Child not found") raise HTTPException(status_code=404, detail="Child not found")
if child["user_id"] != user_id: if child["user_id"] != user_id:
raise HTTPException( raise HTTPException(status_code=403, detail="Access denied to this child")
status_code=403, detail="Access denied to this child"
)
sleep = sleep_repo.create( sleep = sleep_repo.create(
child_id=request.child_id, child_id=request.child_id,
@@ -125,9 +123,7 @@ def update_sleep(
) )
if not updated: if not updated:
raise HTTPException( raise HTTPException(status_code=500, detail="Failed to update sleep log")
status_code=500, detail="Failed to update sleep log"
)
return SleepResponse(**updated) return SleepResponse(**updated)
+134 -74
View File
@@ -433,7 +433,8 @@
} }
function onChildChange(event) { function onChildChange(event) {
selectedChildId = event.target.value === "all" ? null : parseInt(event.target.value); selectedChildId =
event.target.value === "all" ? null : parseInt(event.target.value);
displayDiaperChanges(allDiaperChanges); displayDiaperChanges(allDiaperChanges);
} }
@@ -455,31 +456,34 @@
const peePieData = preparePeePieChartData(diaperChanges); const peePieData = preparePeePieChartData(diaperChanges);
// Build child dropdown options // Build child dropdown options
const childOptions = allChildren.map(child => const childOptions = allChildren
`<option value="${child.id}" ${selectedChildId === child.id ? 'selected' : ''}>${child.name}</option>` .map(
).join(''); (child) =>
`<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`,
)
.join("");
content.innerHTML = ` content.innerHTML = `
<div class="controls"> <div class="controls">
<label for="childFilter">Child:</label> <label for="childFilter">Child:</label>
<select id="childFilter" onchange="onChildChange(event)"> <select id="childFilter" onchange="onChildChange(event)">
<option value="all" ${selectedChildId === null ? 'selected' : ''}>All Children</option> <option value="all" ${selectedChildId === null ? "selected" : ""}>All Children</option>
${childOptions} ${childOptions}
</select> </select>
<label for="daysRange">Time Range:</label> <label for="daysRange">Time Range:</label>
<select id="daysRange" onchange="onDaysRangeChange(event)"> <select id="daysRange" onchange="onDaysRangeChange(event)">
<option value="1" ${currentDaysRange === 1 ? 'selected' : ''}>Last 24 Hours</option> <option value="1" ${currentDaysRange === 1 ? "selected" : ""}>Last 24 Hours</option>
<option value="3" ${currentDaysRange === 3 ? 'selected' : ''}>Last 3 Days</option> <option value="3" ${currentDaysRange === 3 ? "selected" : ""}>Last 3 Days</option>
<option value="7" ${currentDaysRange === 7 ? 'selected' : ''}>Last 7 Days</option> <option value="7" ${currentDaysRange === 7 ? "selected" : ""}>Last 7 Days</option>
<option value="14" ${currentDaysRange === 14 ? 'selected' : ''}>Last 14 Days</option> <option value="14" ${currentDaysRange === 14 ? "selected" : ""}>Last 14 Days</option>
<option value="30" ${currentDaysRange === 30 ? 'selected' : ''}>Last 30 Days</option> <option value="30" ${currentDaysRange === 30 ? "selected" : ""}>Last 30 Days</option>
<option value="90" ${currentDaysRange === 90 ? 'selected' : ''}>Last 90 Days</option> <option value="90" ${currentDaysRange === 90 ? "selected" : ""}>Last 90 Days</option>
</select> </select>
</div> </div>
<div class="chart-container"> <div class="chart-container">
<h2>Diaper Changes Per Day (Last ${currentDaysRange} Day${currentDaysRange !== 1 ? 's' : ''})</h2> <h2>Diaper Changes Per Day (Last ${currentDaysRange} Day${currentDaysRange !== 1 ? "s" : ""})</h2>
<div class="chart-wrapper"> <div class="chart-wrapper">
<canvas id="diaperChart"></canvas> <canvas id="diaperChart"></canvas>
</div> </div>
@@ -506,7 +510,7 @@
renderBarChart(barChartData); renderBarChart(barChartData);
renderPoopPieChart(poopPieData); renderPoopPieChart(poopPieData);
renderPeePieChart(peePieData); renderPeePieChart(peePieData);
// Render diaper change list // Render diaper change list
renderDiaperList(diaperChanges); renderDiaperList(diaperChanges);
} }
@@ -526,7 +530,9 @@
// Filter by selected child if applicable // Filter by selected child if applicable
if (selectedChildId !== null) { if (selectedChildId !== null) {
filteredChanges = filteredChanges.filter((dc) => dc.child_id === selectedChildId); filteredChanges = filteredChanges.filter(
(dc) => dc.child_id === selectedChildId,
);
} }
// Sort by most recent first // Sort by most recent first
@@ -534,50 +540,62 @@
return new Date(b.change_time) - new Date(a.change_time); return new Date(b.change_time) - new Date(a.change_time);
}); });
const listHtml = filteredChanges.map((dc) => { const listHtml = filteredChanges
const changeTime = new Date(dc.change_time); .map((dc) => {
const changeTime = new Date(dc.change_time);
const dateStr = changeTime.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
const timeStr = changeTime.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
});
// Build badges for poop and pee const dateStr = changeTime.toLocaleDateString("en-US", {
let badges = []; month: "short",
if (dc.poop_amount || dc.poop_color) { day: "numeric",
const poopInfo = [dc.poop_amount, dc.poop_color].filter(Boolean).join(", "); year: "numeric",
badges.push(`<span class="diaper-badge poop">💩 ${poopInfo}</span>`); });
}
if (dc.pee_amount || dc.pee_color) {
const peeInfo = [dc.pee_amount, dc.pee_color].filter(Boolean).join(", ");
badges.push(`<span class="diaper-badge pee">💧 ${peeInfo}</span>`);
}
if (badges.length === 0) {
badges.push('<span class="diaper-badge" style="background: #f0f0f0; color: #666;">Clean diaper</span>');
}
// Get child name const timeStr = changeTime.toLocaleTimeString("en-US", {
const child = allChildren.find(c => c.id === dc.child_id); hour: "numeric",
const childName = child ? child.name : "Unknown"; minute: "2-digit",
});
return ` // Build badges for poop and pee
let badges = [];
if (dc.poop_amount || dc.poop_color) {
const poopInfo = [dc.poop_amount, dc.poop_color]
.filter(Boolean)
.join(", ");
badges.push(
`<span class="diaper-badge poop">💩 ${poopInfo}</span>`,
);
}
if (dc.pee_amount || dc.pee_color) {
const peeInfo = [dc.pee_amount, dc.pee_color]
.filter(Boolean)
.join(", ");
badges.push(
`<span class="diaper-badge pee">💧 ${peeInfo}</span>`,
);
}
if (badges.length === 0) {
badges.push(
'<span class="diaper-badge" style="background: #f0f0f0; color: #666;">Clean diaper</span>',
);
}
// Get child name
const child = allChildren.find((c) => c.id === dc.child_id);
const childName = child ? child.name : "Unknown";
return `
<div class="diaper-item"> <div class="diaper-item">
<div class="diaper-info-left"> <div class="diaper-info-left">
<div class="diaper-time">${dateStr} at ${timeStr}${selectedChildId === null ? ` - ${childName}` : ''}</div> <div class="diaper-time">${dateStr} at ${timeStr}${selectedChildId === null ? ` - ${childName}` : ""}</div>
<div class="diaper-details"> <div class="diaper-details">
${badges.join('')} ${badges.join("")}
</div> </div>
</div> </div>
<button class="edit-button" onclick="editDiaperChange(${dc.id})">Edit</button> <button class="edit-button" onclick="editDiaperChange(${dc.id})">Edit</button>
</div> </div>
`; `;
}).join(""); })
.join("");
const listContainer = ` const listContainer = `
<div class="diapers-list"> <div class="diapers-list">
@@ -592,7 +610,7 @@
`; `;
const content = document.getElementById("content"); const content = document.getElementById("content");
content.insertAdjacentHTML('beforeend', listContainer); content.insertAdjacentHTML("beforeend", listContainer);
} }
function editDiaperChange(diaperChangeId) { function editDiaperChange(diaperChangeId) {
@@ -617,7 +635,9 @@
// Filter by selected child if applicable // Filter by selected child if applicable
if (selectedChildId !== null) { if (selectedChildId !== null) {
recentChanges = recentChanges.filter((dc) => dc.child_id === selectedChildId); recentChanges = recentChanges.filter(
(dc) => dc.child_id === selectedChildId,
);
} }
// Count poop amounts // Count poop amounts
@@ -670,7 +690,9 @@
// Filter by selected child if applicable // Filter by selected child if applicable
if (selectedChildId !== null) { if (selectedChildId !== null) {
recentChanges = recentChanges.filter((dc) => dc.child_id === selectedChildId); recentChanges = recentChanges.filter(
(dc) => dc.child_id === selectedChildId,
);
} }
// Count pee amounts // Count pee amounts
@@ -718,7 +740,9 @@
// Filter by selected child if applicable // Filter by selected child if applicable
let filteredChanges = diaperChanges; let filteredChanges = diaperChanges;
if (selectedChildId !== null) { if (selectedChildId !== null) {
filteredChanges = diaperChanges.filter((dc) => dc.child_id === selectedChildId); filteredChanges = diaperChanges.filter(
(dc) => dc.child_id === selectedChildId,
);
} }
// Create days based on currentDaysRange (including today) // Create days based on currentDaysRange (including today)
@@ -742,8 +766,12 @@
return changeDate >= date && changeDate < nextDay; return changeDate >= date && changeDate < nextDay;
}); });
const poopCount = dayChanges.filter(dc => dc.poop_amount !== null).length; const poopCount = dayChanges.filter(
const peeCount = dayChanges.filter(dc => dc.pee_amount !== null).length; (dc) => dc.poop_amount !== null,
).length;
const peeCount = dayChanges.filter(
(dc) => dc.pee_amount !== null,
).length;
poopData.push(poopCount); poopData.push(poopCount);
peeData.push(peeCount); peeData.push(peeCount);
@@ -754,7 +782,7 @@
function renderBarChart(chartData) { function renderBarChart(chartData) {
const ctx = document.getElementById("diaperChart").getContext("2d"); const ctx = document.getElementById("diaperChart").getContext("2d");
new Chart(ctx, { new Chart(ctx, {
type: "bar", type: "bar",
data: { data: {
@@ -847,21 +875,33 @@
function renderPoopPieChart(chartData) { function renderPoopPieChart(chartData) {
const ctx = document.getElementById("poopPieChart").getContext("2d"); const ctx = document.getElementById("poopPieChart").getContext("2d");
const colors = { const colors = {
"Light": { bg: "rgba(139, 69, 19, 0.5)", border: "rgba(139, 69, 19, 1)" }, Light: {
"Medium": { bg: "rgba(139, 69, 19, 0.7)", border: "rgba(139, 69, 19, 1)" }, bg: "rgba(139, 69, 19, 0.5)",
"Heavy": { bg: "rgba(139, 69, 19, 0.9)", border: "rgba(139, 69, 19, 1)" }, border: "rgba(139, 69, 19, 1)",
"No Poop": { bg: "rgba(200, 200, 200, 0.5)", border: "rgba(200, 200, 200, 1)" }, },
Medium: {
bg: "rgba(139, 69, 19, 0.7)",
border: "rgba(139, 69, 19, 1)",
},
Heavy: {
bg: "rgba(139, 69, 19, 0.9)",
border: "rgba(139, 69, 19, 1)",
},
"No Poop": {
bg: "rgba(200, 200, 200, 0.5)",
border: "rgba(200, 200, 200, 1)",
},
}; };
const backgroundColors = chartData.labels.map( const backgroundColors = chartData.labels.map(
(label) => colors[label]?.bg || "rgba(200, 200, 200, 0.5)" (label) => colors[label]?.bg || "rgba(200, 200, 200, 0.5)",
); );
const borderColors = chartData.labels.map( const borderColors = chartData.labels.map(
(label) => colors[label]?.border || "rgba(200, 200, 200, 1)" (label) => colors[label]?.border || "rgba(200, 200, 200, 1)",
); );
new Chart(ctx, { new Chart(ctx, {
type: "pie", type: "pie",
data: { data: {
@@ -900,8 +940,12 @@
callbacks: { callbacks: {
label: function (context) { label: function (context) {
const count = context.parsed; const count = context.parsed;
const total = context.dataset.data.reduce((a, b) => a + b, 0); const total = context.dataset.data.reduce(
const percentage = total > 0 ? ((count / total) * 100).toFixed(1) : 0; (a, b) => a + b,
0,
);
const percentage =
total > 0 ? ((count / total) * 100).toFixed(1) : 0;
return `${context.label}: ${count} (${percentage}%)`; return `${context.label}: ${count} (${percentage}%)`;
}, },
}, },
@@ -913,21 +957,33 @@
function renderPeePieChart(chartData) { function renderPeePieChart(chartData) {
const ctx = document.getElementById("peePieChart").getContext("2d"); const ctx = document.getElementById("peePieChart").getContext("2d");
const colors = { const colors = {
"Light": { bg: "rgba(255, 215, 0, 0.5)", border: "rgba(255, 215, 0, 1)" }, Light: {
"Medium": { bg: "rgba(255, 215, 0, 0.7)", border: "rgba(255, 215, 0, 1)" }, bg: "rgba(255, 215, 0, 0.5)",
"Heavy": { bg: "rgba(255, 215, 0, 0.9)", border: "rgba(255, 215, 0, 1)" }, border: "rgba(255, 215, 0, 1)",
"No Pee": { bg: "rgba(200, 200, 200, 0.5)", border: "rgba(200, 200, 200, 1)" }, },
Medium: {
bg: "rgba(255, 215, 0, 0.7)",
border: "rgba(255, 215, 0, 1)",
},
Heavy: {
bg: "rgba(255, 215, 0, 0.9)",
border: "rgba(255, 215, 0, 1)",
},
"No Pee": {
bg: "rgba(200, 200, 200, 0.5)",
border: "rgba(200, 200, 200, 1)",
},
}; };
const backgroundColors = chartData.labels.map( const backgroundColors = chartData.labels.map(
(label) => colors[label]?.bg || "rgba(200, 200, 200, 0.5)" (label) => colors[label]?.bg || "rgba(200, 200, 200, 0.5)",
); );
const borderColors = chartData.labels.map( const borderColors = chartData.labels.map(
(label) => colors[label]?.border || "rgba(200, 200, 200, 1)" (label) => colors[label]?.border || "rgba(200, 200, 200, 1)",
); );
new Chart(ctx, { new Chart(ctx, {
type: "pie", type: "pie",
data: { data: {
@@ -966,8 +1022,12 @@
callbacks: { callbacks: {
label: function (context) { label: function (context) {
const count = context.parsed; const count = context.parsed;
const total = context.dataset.data.reduce((a, b) => a + b, 0); const total = context.dataset.data.reduce(
const percentage = total > 0 ? ((count / total) * 100).toFixed(1) : 0; (a, b) => a + b,
0,
);
const percentage =
total > 0 ? ((count / total) * 100).toFixed(1) : 0;
return `${context.label}: ${count} (${percentage}%)`; return `${context.label}: ${count} (${percentage}%)`;
}, },
}, },
+76 -56
View File
@@ -470,7 +470,8 @@
} }
function onChildChange(event) { function onChildChange(event) {
selectedChildId = event.target.value === "all" ? null : parseInt(event.target.value); selectedChildId =
event.target.value === "all" ? null : parseInt(event.target.value);
displayFeedings(allFeedings); displayFeedings(allFeedings);
} }
@@ -492,31 +493,34 @@
const durationChartData = prepareDurationChartData(feedings); const durationChartData = prepareDurationChartData(feedings);
// Build child dropdown options // Build child dropdown options
const childOptions = allChildren.map(child => const childOptions = allChildren
`<option value="${child.id}" ${selectedChildId === child.id ? 'selected' : ''}>${child.name}</option>` .map(
).join(''); (child) =>
`<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`,
)
.join("");
content.innerHTML = ` content.innerHTML = `
<div class="controls"> <div class="controls">
<label for="childFilter">Child:</label> <label for="childFilter">Child:</label>
<select id="childFilter" onchange="onChildChange(event)"> <select id="childFilter" onchange="onChildChange(event)">
<option value="all" ${selectedChildId === null ? 'selected' : ''}>All Children</option> <option value="all" ${selectedChildId === null ? "selected" : ""}>All Children</option>
${childOptions} ${childOptions}
</select> </select>
<label for="daysRange">Time Range:</label> <label for="daysRange">Time Range:</label>
<select id="daysRange" onchange="onDaysRangeChange(event)"> <select id="daysRange" onchange="onDaysRangeChange(event)">
<option value="1" ${currentDaysRange === 1 ? 'selected' : ''}>Last 24 Hours</option> <option value="1" ${currentDaysRange === 1 ? "selected" : ""}>Last 24 Hours</option>
<option value="3" ${currentDaysRange === 3 ? 'selected' : ''}>Last 3 Days</option> <option value="3" ${currentDaysRange === 3 ? "selected" : ""}>Last 3 Days</option>
<option value="7" ${currentDaysRange === 7 ? 'selected' : ''}>Last 7 Days</option> <option value="7" ${currentDaysRange === 7 ? "selected" : ""}>Last 7 Days</option>
<option value="14" ${currentDaysRange === 14 ? 'selected' : ''}>Last 14 Days</option> <option value="14" ${currentDaysRange === 14 ? "selected" : ""}>Last 14 Days</option>
<option value="30" ${currentDaysRange === 30 ? 'selected' : ''}>Last 30 Days</option> <option value="30" ${currentDaysRange === 30 ? "selected" : ""}>Last 30 Days</option>
<option value="90" ${currentDaysRange === 90 ? 'selected' : ''}>Last 90 Days</option> <option value="90" ${currentDaysRange === 90 ? "selected" : ""}>Last 90 Days</option>
</select> </select>
</div> </div>
<div class="chart-container"> <div class="chart-container">
<h2>Feedings Per Day (Last ${currentDaysRange} Day${currentDaysRange !== 1 ? 's' : ''})</h2> <h2>Feedings Per Day (Last ${currentDaysRange} Day${currentDaysRange !== 1 ? "s" : ""})</h2>
<div class="chart-wrapper"> <div class="chart-wrapper">
<canvas id="feedingsChart"></canvas> <canvas id="feedingsChart"></canvas>
</div> </div>
@@ -543,7 +547,7 @@
renderBarChart(barChartData); renderBarChart(barChartData);
renderPieChart(pieChartData); renderPieChart(pieChartData);
renderDurationChart(durationChartData); renderDurationChart(durationChartData);
// Render feeding list // Render feeding list
renderFeedingList(feedings); renderFeedingList(feedings);
} }
@@ -563,7 +567,9 @@
// Filter by selected child if applicable // Filter by selected child if applicable
if (selectedChildId !== null) { if (selectedChildId !== null) {
filteredFeedings = filteredFeedings.filter((f) => f.child_id === selectedChildId); filteredFeedings = filteredFeedings.filter(
(f) => f.child_id === selectedChildId,
);
} }
// Sort by most recent first // Sort by most recent first
@@ -571,45 +577,49 @@
return new Date(b.start_time) - new Date(a.start_time); return new Date(b.start_time) - new Date(a.start_time);
}); });
const listHtml = filteredFeedings.map((feeding) => { const listHtml = filteredFeedings
const startTime = new Date(feeding.start_time); .map((feeding) => {
const endTime = feeding.end_time ? new Date(feeding.end_time) : null; const startTime = new Date(feeding.start_time);
const endTime = feeding.end_time
const dateStr = startTime.toLocaleDateString("en-US", { ? new Date(feeding.end_time)
month: "short", : null;
day: "numeric",
year: "numeric",
});
const timeStr = startTime.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
});
const duration = endTime const dateStr = startTime.toLocaleDateString("en-US", {
? Math.round((endTime - startTime) / 60000) month: "short",
: null; day: "numeric",
year: "numeric",
});
const typeClass = feeding.feeding_type.replace("_", "-"); const timeStr = startTime.toLocaleTimeString("en-US", {
const typeLabel = formatFeedingType(feeding.feeding_type); hour: "numeric",
minute: "2-digit",
});
// Get child name const duration = endTime
const child = allChildren.find(c => c.id === feeding.child_id); ? Math.round((endTime - startTime) / 60000)
const childName = child ? child.name : "Unknown"; : null;
return ` const typeClass = feeding.feeding_type.replace("_", "-");
const typeLabel = formatFeedingType(feeding.feeding_type);
// Get child name
const child = allChildren.find((c) => c.id === feeding.child_id);
const childName = child ? child.name : "Unknown";
return `
<div class="feeding-item"> <div class="feeding-item">
<div class="feeding-info-left"> <div class="feeding-info-left">
<div class="feeding-time">${dateStr} at ${timeStr}${selectedChildId === null ? ` - ${childName}` : ''}</div> <div class="feeding-time">${dateStr} at ${timeStr}${selectedChildId === null ? ` - ${childName}` : ""}</div>
<div class="feeding-details"> <div class="feeding-details">
<span class="feeding-type-badge ${typeClass}">${typeLabel}</span> <span class="feeding-type-badge ${typeClass}">${typeLabel}</span>
${duration !== null ? `${duration} minutes` : 'Ongoing'} ${duration !== null ? `${duration} minutes` : "Ongoing"}
</div> </div>
</div> </div>
<button class="edit-button" onclick="editFeeding(${feeding.id})">Edit</button> <button class="edit-button" onclick="editFeeding(${feeding.id})">Edit</button>
</div> </div>
`; `;
}).join(""); })
.join("");
const listContainer = ` const listContainer = `
<div class="feedings-list"> <div class="feedings-list">
@@ -624,7 +634,7 @@
`; `;
const content = document.getElementById("content"); const content = document.getElementById("content");
content.insertAdjacentHTML('beforeend', listContainer); content.insertAdjacentHTML("beforeend", listContainer);
} }
function formatFeedingType(type) { function formatFeedingType(type) {
@@ -660,7 +670,9 @@
// Filter by selected child if applicable // Filter by selected child if applicable
if (selectedChildId !== null) { if (selectedChildId !== null) {
recentFeedings = recentFeedings.filter((f) => f.child_id === selectedChildId); recentFeedings = recentFeedings.filter(
(f) => f.child_id === selectedChildId,
);
} }
const typeCounts = { const typeCounts = {
@@ -708,7 +720,9 @@
// Filter by selected child if applicable // Filter by selected child if applicable
if (selectedChildId !== null) { if (selectedChildId !== null) {
completedFeedings = completedFeedings.filter((f) => f.child_id === selectedChildId); completedFeedings = completedFeedings.filter(
(f) => f.child_id === selectedChildId,
);
} }
// Calculate durations in minutes // Calculate durations in minutes
@@ -721,14 +735,14 @@
// Find max duration to determine how many bins we need // Find max duration to determine how many bins we need
const maxDuration = Math.max(...durations, 0); const maxDuration = Math.max(...durations, 0);
const maxBin = Math.ceil(maxDuration / 5) * 5; // Round up to nearest 5 const maxBin = Math.ceil(maxDuration / 5) * 5; // Round up to nearest 5
// Create 5-minute duration buckets dynamically // Create 5-minute duration buckets dynamically
const buckets = {}; const buckets = {};
for (let i = 0; i < maxBin; i += 5) { for (let i = 0; i < maxBin; i += 5) {
const label = `${i}-${i + 5}`; const label = `${i}-${i + 5}`;
buckets[label] = 0; buckets[label] = 0;
} }
// Add a final bucket for anything over the max // Add a final bucket for anything over the max
if (maxBin > 0) { if (maxBin > 0) {
buckets[`${maxBin}+`] = 0; buckets[`${maxBin}+`] = 0;
@@ -738,7 +752,7 @@
durations.forEach((duration) => { durations.forEach((duration) => {
const binIndex = Math.floor(duration / 5) * 5; const binIndex = Math.floor(duration / 5) * 5;
const label = `${binIndex}-${binIndex + 5}`; const label = `${binIndex}-${binIndex + 5}`;
if (buckets[label] !== undefined) { if (buckets[label] !== undefined) {
buckets[label]++; buckets[label]++;
} else { } else {
@@ -760,7 +774,9 @@
// Filter by selected child if applicable // Filter by selected child if applicable
let filteredFeedings = feedings; let filteredFeedings = feedings;
if (selectedChildId !== null) { if (selectedChildId !== null) {
filteredFeedings = feedings.filter((f) => f.child_id === selectedChildId); filteredFeedings = feedings.filter(
(f) => f.child_id === selectedChildId,
);
} }
// Create days based on currentDaysRange (including today) // Create days based on currentDaysRange (including today)
@@ -792,7 +808,7 @@
function renderBarChart(chartData) { function renderBarChart(chartData) {
const ctx = document.getElementById("feedingsChart").getContext("2d"); const ctx = document.getElementById("feedingsChart").getContext("2d");
new Chart(ctx, { new Chart(ctx, {
type: "bar", type: "bar",
data: { data: {
@@ -870,7 +886,7 @@
function renderPieChart(chartData) { function renderPieChart(chartData) {
const ctx = document.getElementById("pieChart").getContext("2d"); const ctx = document.getElementById("pieChart").getContext("2d");
// Color mapping for each type // Color mapping for each type
const colorMap = { const colorMap = {
"Left Breast": { "Left Breast": {
@@ -881,7 +897,7 @@
background: "rgba(118, 75, 162, 0.8)", background: "rgba(118, 75, 162, 0.8)",
border: "rgba(118, 75, 162, 1)", border: "rgba(118, 75, 162, 1)",
}, },
"Bottle": { Bottle: {
background: "rgba(255, 159, 64, 0.8)", background: "rgba(255, 159, 64, 0.8)",
border: "rgba(255, 159, 64, 1)", border: "rgba(255, 159, 64, 1)",
}, },
@@ -889,12 +905,12 @@
// Generate colors based on labels present // Generate colors based on labels present
const backgroundColors = chartData.labels.map( const backgroundColors = chartData.labels.map(
(label) => colorMap[label]?.background || "rgba(200, 200, 200, 0.8)" (label) => colorMap[label]?.background || "rgba(200, 200, 200, 0.8)",
); );
const borderColors = chartData.labels.map( const borderColors = chartData.labels.map(
(label) => colorMap[label]?.border || "rgba(200, 200, 200, 1)" (label) => colorMap[label]?.border || "rgba(200, 200, 200, 1)",
); );
new Chart(ctx, { new Chart(ctx, {
type: "pie", type: "pie",
data: { data: {
@@ -933,8 +949,12 @@
callbacks: { callbacks: {
label: function (context) { label: function (context) {
const count = context.parsed; const count = context.parsed;
const total = context.dataset.data.reduce((a, b) => a + b, 0); const total = context.dataset.data.reduce(
const percentage = total > 0 ? ((count / total) * 100).toFixed(1) : 0; (a, b) => a + b,
0,
);
const percentage =
total > 0 ? ((count / total) * 100).toFixed(1) : 0;
return `${context.label}: ${count} (${percentage}%)`; return `${context.label}: ${count} (${percentage}%)`;
}, },
}, },
@@ -946,7 +966,7 @@
function renderDurationChart(chartData) { function renderDurationChart(chartData) {
const ctx = document.getElementById("durationChart").getContext("2d"); const ctx = document.getElementById("durationChart").getContext("2d");
new Chart(ctx, { new Chart(ctx, {
type: "bar", type: "bar",
data: { data: {
+29 -16
View File
@@ -232,7 +232,11 @@
<button type="submit" class="button" id="submitBtn"> <button type="submit" class="button" id="submitBtn">
Log Diaper Change Log Diaper Change
</button> </button>
<button type="button" class="button secondary" onclick="window.location.href='/'"> <button
type="button"
class="button secondary"
onclick="window.location.href='/'"
>
Cancel Cancel
</button> </button>
</form> </form>
@@ -252,8 +256,10 @@
// Update UI for edit mode // Update UI for edit mode
if (isEditMode) { if (isEditMode) {
document.getElementById("pageTitle").textContent = "🧷 Edit Diaper Change"; document.getElementById("pageTitle").textContent =
document.getElementById("submitBtn").textContent = "Update Diaper Change"; "🧷 Edit Diaper Change";
document.getElementById("submitBtn").textContent =
"Update Diaper Change";
} }
let children = []; let children = [];
@@ -291,7 +297,7 @@
// Load last used child from localStorage // Load last used child from localStorage
const lastDiaperChange = JSON.parse( const lastDiaperChange = JSON.parse(
localStorage.getItem("lastDiaperChange") || "{}" localStorage.getItem("lastDiaperChange") || "{}",
); );
if (lastDiaperChange.child_id) { if (lastDiaperChange.child_id) {
@@ -311,22 +317,26 @@
// Load diaper change data for editing // Load diaper change data for editing
async function loadDiaperChangeData() { async function loadDiaperChangeData() {
try { try {
const response = await fetch(`/api/diaper-changes/${diaperChangeId}`, { const response = await fetch(
headers: { `/api/diaper-changes/${diaperChangeId}`,
Authorization: `Bearer ${token}`, {
headers: {
Authorization: `Bearer ${token}`,
},
}, },
}); );
if (response.ok) { if (response.ok) {
const diaperChange = await response.json(); const diaperChange = await response.json();
// Populate form fields // Populate form fields
document.getElementById("childSelect").value = diaperChange.child_id; document.getElementById("childSelect").value =
diaperChange.child_id;
// Convert UTC time to local datetime-local format // Convert UTC time to local datetime-local format
const changeTime = new Date(diaperChange.change_time); const changeTime = new Date(diaperChange.change_time);
changeTime.setMinutes( changeTime.setMinutes(
changeTime.getMinutes() - changeTime.getTimezoneOffset() changeTime.getMinutes() - changeTime.getTimezoneOffset(),
); );
document.getElementById("changeTime").value = changeTime document.getElementById("changeTime").value = changeTime
.toISOString() .toISOString()
@@ -355,11 +365,14 @@
.addEventListener("submit", async (e) => { .addEventListener("submit", async (e) => {
e.preventDefault(); e.preventDefault();
const childId = parseInt(document.getElementById("childSelect").value); const childId = parseInt(
document.getElementById("childSelect").value,
);
const changeTime = new Date( const changeTime = new Date(
document.getElementById("changeTime").value document.getElementById("changeTime").value,
).toISOString(); ).toISOString();
const poopAmount = document.getElementById("poopAmount").value || null; const poopAmount =
document.getElementById("poopAmount").value || null;
const poopColor = document.getElementById("poopColor").value || null; const poopColor = document.getElementById("poopColor").value || null;
const peeAmount = document.getElementById("peeAmount").value || null; const peeAmount = document.getElementById("peeAmount").value || null;
const peeColor = document.getElementById("peeColor").value || null; const peeColor = document.getElementById("peeColor").value || null;
@@ -407,7 +420,7 @@
"lastDiaperChange", "lastDiaperChange",
JSON.stringify({ JSON.stringify({
child_id: childId, child_id: childId,
}) }),
); );
} }
@@ -415,7 +428,7 @@
isEditMode isEditMode
? "Diaper change updated successfully!" ? "Diaper change updated successfully!"
: "Diaper change logged successfully!", : "Diaper change logged successfully!",
"success" "success",
); );
setTimeout(() => { setTimeout(() => {
window.location.href = "/"; window.location.href = "/";
@@ -424,7 +437,7 @@
const errorData = await response.json(); const errorData = await response.json();
showMessage( showMessage(
errorData.detail || "Failed to log diaper change", errorData.detail || "Failed to log diaper change",
"error" "error",
); );
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.textContent = isEditMode submitBtn.textContent = isEditMode
+24 -14
View File
@@ -231,7 +231,8 @@
// Update title and button text if editing // Update title and button text if editing
if (isEditMode) { if (isEditMode) {
document.querySelector("h1").textContent = "🍼 Edit Feeding"; document.querySelector("h1").textContent = "🍼 Edit Feeding";
document.querySelector(".subtitle").textContent = "Update feeding session details"; document.querySelector(".subtitle").textContent =
"Update feeding session details";
submitBtn.textContent = "Update Feeding"; submitBtn.textContent = "Update Feeding";
} }
@@ -292,25 +293,29 @@
if (response.ok) { if (response.ok) {
const feeding = await response.json(); const feeding = await response.json();
// Populate form with existing data // Populate form with existing data
document.getElementById("childId").value = feeding.child_id; document.getElementById("childId").value = feeding.child_id;
// Format datetime for datetime-local input // Format datetime for datetime-local input
const startTime = new Date(feeding.start_time); const startTime = new Date(feeding.start_time);
const startLocalDateTime = new Date( const startLocalDateTime = new Date(
startTime.getTime() - startTime.getTimezoneOffset() * 60000 startTime.getTime() - startTime.getTimezoneOffset() * 60000,
).toISOString().slice(0, 16); )
.toISOString()
.slice(0, 16);
document.getElementById("startTime").value = startLocalDateTime; document.getElementById("startTime").value = startLocalDateTime;
if (feeding.end_time) { if (feeding.end_time) {
const endTime = new Date(feeding.end_time); const endTime = new Date(feeding.end_time);
const endLocalDateTime = new Date( const endLocalDateTime = new Date(
endTime.getTime() - endTime.getTimezoneOffset() * 60000 endTime.getTime() - endTime.getTimezoneOffset() * 60000,
).toISOString().slice(0, 16); )
.toISOString()
.slice(0, 16);
document.getElementById("endTime").value = endLocalDateTime; document.getElementById("endTime").value = endLocalDateTime;
} }
document.getElementById("feedingType").value = feeding.feeding_type; document.getElementById("feedingType").value = feeding.feeding_type;
} else { } else {
showMessage("Failed to load feeding data", "error"); showMessage("Failed to load feeding data", "error");
@@ -336,9 +341,11 @@
messageDiv.classList.remove("show"); messageDiv.classList.remove("show");
try { try {
const url = isEditMode ? `/api/feedings/${feedingId}` : "/api/feedings"; const url = isEditMode
? `/api/feedings/${feedingId}`
: "/api/feedings";
const method = isEditMode ? "PUT" : "POST"; const method = isEditMode ? "PUT" : "POST";
const response = await fetch(url, { const response = await fetch(url, {
method: method, method: method,
headers: { headers: {
@@ -357,8 +364,10 @@
if (response.ok) { if (response.ok) {
showMessage( showMessage(
isEditMode ? "Feeding updated successfully!" : "Feeding logged successfully!", isEditMode
"success" ? "Feeding updated successfully!"
: "Feeding logged successfully!",
"success",
); );
// Redirect to feedings page after 1 second // Redirect to feedings page after 1 second
setTimeout(() => { setTimeout(() => {
@@ -366,7 +375,8 @@
}, 1000); }, 1000);
} else { } else {
showMessage( showMessage(
data.detail || `Failed to ${isEditMode ? 'update' : 'log'} feeding. Please try again.`, data.detail ||
`Failed to ${isEditMode ? "update" : "log"} feeding. Please try again.`,
"error", "error",
); );
} }
+3 -3
View File
@@ -528,8 +528,7 @@
const sleepDate = new Date(sleep.start_time); const sleepDate = new Date(sleep.start_time);
const matchesTimeRange = sleepDate >= cutoffDate; const matchesTimeRange = sleepDate >= cutoffDate;
const matchesChild = const matchesChild =
!selectedChildId || !selectedChildId || sleep.child_id === parseInt(selectedChildId);
sleep.child_id === parseInt(selectedChildId);
return matchesTimeRange && matchesChild; return matchesTimeRange && matchesChild;
}); });
@@ -576,7 +575,8 @@
// Prepare data for charts // Prepare data for charts
const barChartData = prepareBarChartData(filteredSleeps, timeRange); const barChartData = prepareBarChartData(filteredSleeps, timeRange);
const durationDistData = prepareDurationDistributionData(completedSleeps); const durationDistData =
prepareDurationDistributionData(completedSleeps);
let html = ` let html = `
<div class="stats-grid"> <div class="stats-grid">