11 Commits
Author SHA1 Message Date
brian 8c007aa6f3 Merge pull request 'show-most-recent-feeding-session' (#39) from show-most-recent-feeding-session into main
Python Code Quality / python-code-quality (push) Successful in 10s
Python Test / python-test (push) Successful in 19s
Build and Push Docker Image / build-and-push (push) Successful in 26s
Refresh Showcase Data / refresh-data (push) Failing after 2m10s
Reviewed-on: #39
2025-12-03 21:41:12 +01:00
Brian Bjarke Jensen 63fc4b618c CQ fixes
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m0s
Python Code Quality / python-code-quality (pull_request) Successful in 10s
Python Test / python-test (pull_request) Successful in 19s
2025-12-03 21:29:36 +01:00
Brian Bjarke Jensen 93eb1dc458 added feeding session calculation and displayed values 2025-12-03 21:29:04 +01:00
Brian Bjarke Jensen 5626408e2d vertically stacked recent activity boxes 2025-12-03 20:18:06 +01:00
brian fd1472c5bd Merge pull request 'add-sleep-entry-page' (#38) from add-sleep-entry-page into main
Build and Push Docker Image / build-and-push (push) Successful in 27s
Python Code Quality / python-code-quality (push) Successful in 11s
Python Test / python-test (push) Successful in 18s
Reviewed-on: #38
2025-12-03 19:57:12 +01:00
Brian Bjarke Jensen 5b17508ade CQ fixes
Build and Push Docker Image / build-and-push (pull_request) Successful in 1m11s
Python Code Quality / python-code-quality (pull_request) Successful in 11s
Python Test / python-test (pull_request) Successful in 19s
2025-12-03 19:49:48 +01:00
Brian Bjarke Jensen 95c2bf6744 added peopulation of field values based on most recent entry 2025-12-03 19:45:03 +01:00
Brian Bjarke Jensen 2c4336a5e4 added edit button to entries 2025-12-03 19:40:07 +01:00
Brian Bjarke Jensen 32946e1165 added redirect to overview page when clicking most recent entry box 2025-12-03 19:39:48 +01:00
Brian Bjarke Jensen fdcfb3c8a5 feeding type dropdown now defaults to last entry feeding type 2025-12-03 19:23:26 +01:00
Brian Bjarke Jensen 1bac464268 streamlined logging workflow 2025-12-03 19:20:56 +01:00
5 changed files with 131 additions and 62 deletions
+86 -59
View File
@@ -180,8 +180,8 @@
font-size: 1.1rem;
}
.activity-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
display: flex;
flex-direction: column;
gap: 1rem;
}
.activity-card {
@@ -384,8 +384,8 @@
activeFeeding = await activeResponse.json();
}
// Fetch all feedings to get the last one
const feedingsResponse = await fetch("/api/feedings", {
// Fetch the 10 most recent feedings to calculate the most recent session
const feedingsResponse = await fetch("/api/feedings?limit=10", {
headers: {
Authorization: `Bearer ${token}`,
},
@@ -394,8 +394,68 @@
if (feedingsResponse.ok) {
const feedings = await feedingsResponse.json();
if (feedings.length > 0) {
// Feedings are ordered by start_time desc, so first is most recent
lastFeeding = feedings[0];
// Calculate the most recent feeding session
let sessionStart = feedings[0].start_time;
let sessionEnd = feedings[0].end_time || feedings[0].start_time;
let sessionChildId = feedings[0].child_id;
let sessionEntries = [feedings[0]];
for (let i = 1; i < feedings.length; i++) {
const entry = feedings[i];
// Only group entries for the same child (ignore feeding type)
if (entry.child_id !== sessionChildId) {
break;
}
// If the gap between this entry's end_time and current sessionStart is more than 30 min, stop
const prevEnd = entry.end_time || entry.start_time;
const sessionStartDate = new Date(sessionStart);
const prevEndDate = new Date(prevEnd);
const diffMins = Math.abs(
(sessionStartDate - prevEndDate) / 60000,
);
if (diffMins > 30) {
break;
}
// Extend session to include this entry
sessionStart = entry.start_time;
sessionEntries.push(entry);
}
// Calculate accumulated feeding time for all events in the session
let accumulatedMinutes = 0;
sessionEntries.forEach((entry) => {
if (entry.end_time && entry.start_time) {
const start = new Date(entry.start_time);
const end = new Date(entry.end_time);
const diffMs = end - start;
if (diffMs > 0) {
accumulatedMinutes += Math.floor(diffMs / 60000);
}
}
});
// Build a string showing all feeding types used during the session
const feedingTypesSet = new Set();
sessionEntries.forEach((entry) => {
if (entry.feeding_type) {
feedingTypesSet.add(entry.feeding_type);
}
});
const feedingTypesString = Array.from(feedingTypesSet)
.map(formatFeedingType)
.join(", ");
// Compose a session object
lastFeeding = {
child_id: sessionChildId,
feeding_type: feedings[0].feeding_type,
start_time: sessionStart,
end_time: sessionEnd,
entries: sessionEntries,
child_name: feedings[0].child_name,
accumulated_minutes: accumulatedMinutes,
feeding_types_string: feedingTypesString,
};
}
}
@@ -510,7 +570,7 @@
</button>
`
: `
<button class="feeding-button" onclick="showStartFeedingModal()">
<button class="feeding-button" onclick="window.location.href='/log-feeding.html'">
🍼 Started Feeding
</button>
`;
@@ -547,7 +607,7 @@
// Build diaper card
const diaperCard = lastDiaperChange
? `
<div class="activity-card">
<a href="/diapers.html" class="activity-card" style="text-decoration:none; color:inherit;">
<div class="icon">🧷</div>
<div class="type">Last Diaper</div>
<div class="time">${formatTime(lastDiaperChange.change_time)}</div>
@@ -556,52 +616,54 @@
${lastDiaperChange.poop_amount && lastDiaperChange.pee_amount ? " • " : ""}
${lastDiaperChange.pee_amount ? `💧 ${lastDiaperChange.pee_amount}` : ""}
</div>
</div>
</a>
`
: `
<div class="activity-card empty">
<a href="/diapers.html" class="activity-card empty" style="text-decoration:none; color:inherit;">
<div class="icon">🧷</div>
<div class="type">No diaper changes yet</div>
</div>
</a>
`;
// Build feeding card
const feedingCard = lastFeeding
? `
<div class="activity-card">
<a href="/feedings.html" class="activity-card" style="text-decoration:none; color:inherit;">
<div class="icon">🍼</div>
<div class="type">Last Feeding</div>
<div class="type">Last Feeding Session</div>
<div class="time">${formatTime(lastFeeding.start_time)}</div>
<div class="details">
${formatFeedingType(lastFeeding.feeding_type)}
${lastFeeding.end_time ? `${calculateDuration(lastFeeding.start_time, lastFeeding.end_time)}` : " (ongoing)"}
</div>
${lastFeeding.feeding_types_string ? `${lastFeeding.feeding_types_string}` : ""}
${lastFeeding.feeding_types_string && lastFeeding.accumulated_minutes ? " • " : ""}
${lastFeeding.accumulated_minutes} minutes total
${lastFeeding.end_time ? "" : " (ongoing)"}
</div>
</a>
`
: `
<div class="activity-card empty">
<a href="/feedings.html" class="activity-card empty" style="text-decoration:none; color:inherit;">
<div class="icon">🍼</div>
<div class="type">No feedings yet</div>
</div>
</a>
`;
// Build sleep card
const sleepCard = lastSleep
? `
<div class="activity-card">
<a href="/sleep.html" class="activity-card" style="text-decoration:none; color:inherit;">
<div class="icon">😴</div>
<div class="type">Last Sleep</div>
<div class="time">${formatTime(lastSleep.start_time)}</div>
<div class="details">
${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"}
</div>
Duration: ${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"}
</div>
</a>
`
: `
<div class="activity-card empty">
<a href="/sleep.html" class="activity-card empty" style="text-decoration:none; color:inherit;">
<div class="icon">😴</div>
<div class="type">No sleep sessions yet</div>
</div>
</a>
`;
return `
@@ -802,42 +864,7 @@
}
function showStartSleepModal() {
const modal = document.getElementById("startSleepModal");
const childSelect = document.getElementById("modalSleepChildId");
// Clear and populate child select or show single child
if (userChildren.length === 1) {
// Single child: replace dropdown with text display
const child = userChildren[0];
const formGroup = childSelect.parentElement;
formGroup.innerHTML = `
<label for="modalSleepChildName">Child</label>
<input
type="text"
id="modalSleepChildName"
value="${child.name}"
readonly
style="background-color: #f5f5f5; cursor: default;"
/>
<input type="hidden" id="modalSleepChildId" value="${child.id}" />
`;
} else {
// Multiple children: show dropdown
childSelect.innerHTML = '<option value="">Select a child</option>';
userChildren.forEach((child) => {
const option = document.createElement("option");
option.value = child.id;
option.textContent = child.name;
childSelect.appendChild(option);
});
// Set default based on last sleep
if (lastSleep) {
childSelect.value = lastSleep.child_id;
}
}
modal.classList.add("show");
window.location.href = "/log-sleep.html";
}
function closeStartSleepModal() {
+23
View File
@@ -336,6 +336,29 @@
lastDiaperChange.child_id;
}
}
// Prepopulate poop and pee fields from most recent entry
try {
const response = await fetch("/api/diaper-changes", {
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
const changes = await response.json();
if (changes.length > 0) {
const last = changes[0];
document.getElementById("poopAmount").value =
last.poop_amount || "";
document.getElementById("poopColor").value =
last.poop_color || "";
document.getElementById("peeAmount").value =
last.pee_amount || "";
document.getElementById("peeColor").value =
last.pee_color || "";
}
}
} catch (error) {
// Ignore error, just use default
}
}
} else {
showMessage("Failed to load children", "error");
+16
View File
@@ -303,6 +303,22 @@
// If editing, load the feeding data
if (isEditMode) {
loadFeedingData();
} else {
// Set default feeding type to most recent log entry
try {
const response = await fetch("/api/feedings", {
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
const feedings = await response.json();
if (feedings.length > 0) {
document.getElementById("feedingType").value =
feedings[0].feeding_type;
}
}
} catch (error) {
// Ignore error, just use default
}
}
} else {
showMessage("Failed to load children", "error");
+3
View File
@@ -1104,6 +1104,9 @@
</div>
<div class="sleep-duration">${durationText}</div>
</div>
<div class="sleep-actions">
<button class="edit-button" onclick="window.location.href='/log-sleep.html?id=${sleep.id}'">Edit</button>
</div>
</div>
`;
}