Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c007aa6f3 | ||
|
|
63fc4b618c | ||
|
|
93eb1dc458 | ||
|
|
5626408e2d | ||
|
|
fd1472c5bd | ||
|
|
5b17508ade | ||
|
|
95c2bf6744 | ||
|
|
2c4336a5e4 | ||
|
|
32946e1165 | ||
|
|
fdcfb3c8a5 | ||
|
|
1bac464268 |
@@ -506,8 +506,8 @@
|
|||||||
ID: ${user.id} • Created: ${new Date(user.created_at).toLocaleDateString()}
|
ID: ${user.id} • Created: ${new Date(user.created_at).toLocaleDateString()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="delete-user-btn"
|
class="delete-user-btn"
|
||||||
onclick="showDeleteModal(${user.id}, '${user.username}', ${user.is_admin})"
|
onclick="showDeleteModal(${user.id}, '${user.username}', ${user.is_admin})"
|
||||||
${user.is_admin ? 'disabled title="Cannot delete admin users"' : ""}
|
${user.is_admin ? 'disabled title="Cannot delete admin users"' : ""}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -180,8 +180,8 @@
|
|||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
.activity-grid {
|
.activity-grid {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
.activity-card {
|
.activity-card {
|
||||||
@@ -384,8 +384,8 @@
|
|||||||
activeFeeding = await activeResponse.json();
|
activeFeeding = await activeResponse.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all feedings to get the last one
|
// Fetch the 10 most recent feedings to calculate the most recent session
|
||||||
const feedingsResponse = await fetch("/api/feedings", {
|
const feedingsResponse = await fetch("/api/feedings?limit=10", {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
@@ -394,8 +394,68 @@
|
|||||||
if (feedingsResponse.ok) {
|
if (feedingsResponse.ok) {
|
||||||
const feedings = await feedingsResponse.json();
|
const feedings = await feedingsResponse.json();
|
||||||
if (feedings.length > 0) {
|
if (feedings.length > 0) {
|
||||||
// Feedings are ordered by start_time desc, so first is most recent
|
// Calculate the most recent feeding session
|
||||||
lastFeeding = feedings[0];
|
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>
|
||||||
`
|
`
|
||||||
: `
|
: `
|
||||||
<button class="feeding-button" onclick="showStartFeedingModal()">
|
<button class="feeding-button" onclick="window.location.href='/log-feeding.html'">
|
||||||
🍼 Started Feeding
|
🍼 Started Feeding
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
@@ -547,7 +607,7 @@
|
|||||||
// Build diaper card
|
// Build diaper card
|
||||||
const diaperCard = lastDiaperChange
|
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="icon">🧷</div>
|
||||||
<div class="type">Last Diaper</div>
|
<div class="type">Last Diaper</div>
|
||||||
<div class="time">${formatTime(lastDiaperChange.change_time)}</div>
|
<div class="time">${formatTime(lastDiaperChange.change_time)}</div>
|
||||||
@@ -556,52 +616,54 @@
|
|||||||
${lastDiaperChange.poop_amount && lastDiaperChange.pee_amount ? " • " : ""}
|
${lastDiaperChange.poop_amount && lastDiaperChange.pee_amount ? " • " : ""}
|
||||||
${lastDiaperChange.pee_amount ? `💧 ${lastDiaperChange.pee_amount}` : ""}
|
${lastDiaperChange.pee_amount ? `💧 ${lastDiaperChange.pee_amount}` : ""}
|
||||||
</div>
|
</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="icon">🧷</div>
|
||||||
<div class="type">No diaper changes yet</div>
|
<div class="type">No diaper changes yet</div>
|
||||||
</div>
|
</a>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Build feeding card
|
// Build feeding card
|
||||||
const feedingCard = lastFeeding
|
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="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="time">${formatTime(lastFeeding.start_time)}</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
${formatFeedingType(lastFeeding.feeding_type)}
|
${lastFeeding.feeding_types_string ? `${lastFeeding.feeding_types_string}` : ""}
|
||||||
${lastFeeding.end_time ? ` • ${calculateDuration(lastFeeding.start_time, lastFeeding.end_time)}` : " (ongoing)"}
|
${lastFeeding.feeding_types_string && lastFeeding.accumulated_minutes ? " • " : ""}
|
||||||
|
${lastFeeding.accumulated_minutes} minutes total
|
||||||
|
${lastFeeding.end_time ? "" : " (ongoing)"}
|
||||||
</div>
|
</div>
|
||||||
</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="icon">🍼</div>
|
||||||
<div class="type">No feedings yet</div>
|
<div class="type">No feedings yet</div>
|
||||||
</div>
|
</a>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Build sleep card
|
// Build sleep card
|
||||||
const sleepCard = lastSleep
|
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="icon">😴</div>
|
||||||
<div class="type">Last Sleep</div>
|
<div class="type">Last Sleep</div>
|
||||||
<div class="time">${formatTime(lastSleep.start_time)}</div>
|
<div class="time">${formatTime(lastSleep.start_time)}</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"}
|
Duration: ${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"}
|
||||||
</div>
|
</div>
|
||||||
</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="icon">😴</div>
|
||||||
<div class="type">No sleep sessions yet</div>
|
<div class="type">No sleep sessions yet</div>
|
||||||
</div>
|
</a>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@@ -802,42 +864,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showStartSleepModal() {
|
function showStartSleepModal() {
|
||||||
const modal = document.getElementById("startSleepModal");
|
window.location.href = "/log-sleep.html";
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeStartSleepModal() {
|
function closeStartSleepModal() {
|
||||||
|
|||||||
@@ -336,6 +336,29 @@
|
|||||||
lastDiaperChange.child_id;
|
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 {
|
} else {
|
||||||
showMessage("Failed to load children", "error");
|
showMessage("Failed to load children", "error");
|
||||||
|
|||||||
@@ -303,6 +303,22 @@
|
|||||||
// If editing, load the feeding data
|
// If editing, load the feeding data
|
||||||
if (isEditMode) {
|
if (isEditMode) {
|
||||||
loadFeedingData();
|
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 {
|
} else {
|
||||||
showMessage("Failed to load children", "error");
|
showMessage("Failed to load children", "error");
|
||||||
|
|||||||
@@ -390,7 +390,7 @@
|
|||||||
readonly
|
readonly
|
||||||
style="background-color: #f5f5f5; cursor: default; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"
|
style="background-color: #f5f5f5; cursor: default; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label for="timeRangeFilter">Time Range:</label>
|
<label for="timeRangeFilter">Time Range:</label>
|
||||||
<select id="timeRangeFilter">
|
<select id="timeRangeFilter">
|
||||||
<option value="1">Last 24 hours</option>
|
<option value="1">Last 24 hours</option>
|
||||||
@@ -1104,6 +1104,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="sleep-duration">${durationText}</div>
|
<div class="sleep-duration">${durationText}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sleep-actions">
|
||||||
|
<button class="edit-button" onclick="window.location.href='/log-sleep.html?id=${sleep.id}'">Edit</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user