Merge pull request 'Improved single child UX' (#29) from improve-single-child-UX into main
Build and Push Docker Image / build-and-push (push) Successful in 25s
Python Code Quality / python-code-quality (push) Successful in 9s
Python Test / python-test (push) Successful in 18s

Reviewed-on: #29
This commit was merged in pull request #29.
This commit is contained in:
2025-11-12 16:42:30 +01:00
7 changed files with 294 additions and 95 deletions
+18 -4
View File
@@ -280,7 +280,7 @@
let allChildren = []; let allChildren = [];
let currentDaysRange = let currentDaysRange =
parseInt(localStorage.getItem("lastDiaperTimeRange")) || 7; parseInt(localStorage.getItem("lastDiaperTimeRange")) || 7;
let selectedChildId = null; // null means "All Children" let selectedChildId = null; // null means show first/only child
// Restore last selected child from localStorage // Restore last selected child from localStorage
const lastChildId = localStorage.getItem("lastDiaperChildFilter"); const lastChildId = localStorage.getItem("lastDiaperChildFilter");
@@ -321,6 +321,11 @@
if (response.ok) { if (response.ok) {
allChildren = await response.json(); allChildren = await response.json();
// Auto-select first child if no selection exists
if (selectedChildId === null && allChildren.length > 0) {
selectedChildId = allChildren[0].id;
}
} }
} catch (error) { } catch (error) {
console.error("Error loading children:", error); console.error("Error loading children:", error);
@@ -334,8 +339,7 @@
} }
function onChildChange(event) { function onChildChange(event) {
selectedChildId = selectedChildId = parseInt(event.target.value);
event.target.value === "all" ? null : parseInt(event.target.value);
localStorage.setItem("lastDiaperChildFilter", event.target.value); localStorage.setItem("lastDiaperChildFilter", event.target.value);
displayDiaperChanges(allDiaperChanges); displayDiaperChanges(allDiaperChanges);
} }
@@ -367,11 +371,21 @@
content.innerHTML = ` content.innerHTML = `
<div class="controls"> <div class="controls">
${allChildren.length === 1 ? `
<label for="childDisplay">Child:</label>
<input
type="text"
id="childDisplay"
value="${allChildren[0].name}"
readonly
style="background-color: #f5f5f5; cursor: default; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"
/>
` : `
<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>
${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)">
+18 -4
View File
@@ -317,7 +317,7 @@
let allChildren = []; let allChildren = [];
let currentDaysRange = let currentDaysRange =
parseInt(localStorage.getItem("lastFeedingTimeRange")) || 7; parseInt(localStorage.getItem("lastFeedingTimeRange")) || 7;
let selectedChildId = null; // null means "All Children" let selectedChildId = null; // null means show first/only child
let barChartInstance = null; let barChartInstance = null;
// Restore last selected child from localStorage // Restore last selected child from localStorage
@@ -359,6 +359,11 @@
if (response.ok) { if (response.ok) {
allChildren = await response.json(); allChildren = await response.json();
// Auto-select first child if no selection exists
if (selectedChildId === null && allChildren.length > 0) {
selectedChildId = allChildren[0].id;
}
} }
} catch (error) { } catch (error) {
console.error("Error loading children:", error); console.error("Error loading children:", error);
@@ -372,8 +377,7 @@
} }
function onChildChange(event) { function onChildChange(event) {
selectedChildId = selectedChildId = parseInt(event.target.value);
event.target.value === "all" ? null : parseInt(event.target.value);
localStorage.setItem("lastFeedingChildFilter", event.target.value); localStorage.setItem("lastFeedingChildFilter", event.target.value);
displayFeedings(allFeedings); displayFeedings(allFeedings);
} }
@@ -405,11 +409,21 @@
content.innerHTML = ` content.innerHTML = `
<div class="controls"> <div class="controls">
${allChildren.length === 1 ? `
<label for="childDisplay">Child:</label>
<input
type="text"
id="childDisplay"
value="${allChildren[0].name}"
readonly
style="background-color: #f5f5f5; cursor: default; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"
/>
` : `
<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>
${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)">
+42 -2
View File
@@ -510,7 +510,24 @@
const childSelect = document.getElementById("modalChildId"); const childSelect = document.getElementById("modalChildId");
const typeSelect = document.getElementById("modalFeedingType"); const typeSelect = document.getElementById("modalFeedingType");
// Clear and populate child select // 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="modalChildName">Child</label>
<input
type="text"
id="modalChildName"
value="${child.name}"
readonly
style="background-color: #f5f5f5; cursor: default;"
/>
<input type="hidden" id="modalChildId" value="${child.id}" />
`;
} else {
// Multiple children: show dropdown
childSelect.innerHTML = '<option value="">Select a child</option>'; childSelect.innerHTML = '<option value="">Select a child</option>';
userChildren.forEach((child) => { userChildren.forEach((child) => {
const option = document.createElement("option"); const option = document.createElement("option");
@@ -522,6 +539,11 @@
// Set defaults based on last feeding // Set defaults based on last feeding
if (lastFeeding) { if (lastFeeding) {
childSelect.value = lastFeeding.child_id; childSelect.value = lastFeeding.child_id;
}
}
// Set default feeding type based on last feeding
if (lastFeeding) {
typeSelect.value = lastFeeding.feeding_type; typeSelect.value = lastFeeding.feeding_type;
} }
@@ -633,7 +655,24 @@
const modal = document.getElementById("startSleepModal"); const modal = document.getElementById("startSleepModal");
const childSelect = document.getElementById("modalSleepChildId"); const childSelect = document.getElementById("modalSleepChildId");
// Clear and populate child select // 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>'; childSelect.innerHTML = '<option value="">Select a child</option>';
userChildren.forEach((child) => { userChildren.forEach((child) => {
const option = document.createElement("option"); const option = document.createElement("option");
@@ -646,6 +685,7 @@
if (lastSleep) { if (lastSleep) {
childSelect.value = lastSleep.child_id; childSelect.value = lastSleep.child_id;
} }
}
modal.classList.add("show"); modal.classList.add("show");
} }
+35 -3
View File
@@ -235,7 +235,7 @@
<button <button
type="button" type="button"
class="button secondary" class="button secondary"
onclick="window.location.href='/diapers.html'" onclick="window.location.href=getReturnUrl()"
> >
Cancel Cancel
</button> </button>
@@ -249,6 +249,16 @@
window.location.href = "/login.html"; window.location.href = "/login.html";
} }
// Determine return URL based on referrer
function getReturnUrl() {
const referrer = document.referrer;
if (referrer.includes('/diapers.html')) {
return '/diapers.html';
}
// Default to home page
return '/';
}
// Check if we're in edit mode // Check if we're in edit mode
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const diaperChangeId = urlParams.get("id"); const diaperChangeId = urlParams.get("id");
@@ -277,12 +287,32 @@
children = await response.json(); children = await response.json();
const select = document.getElementById("childSelect"); const select = document.getElementById("childSelect");
// Populate select dropdown or show single child
if (children.length === 1) {
// Single child: replace dropdown with text display
const child = children[0];
const formGroup = select.parentElement;
formGroup.innerHTML = `
<label for="childName">Child *</label>
<input
type="text"
id="childName"
value="${child.name}"
readonly
style="background-color: #f5f5f5; cursor: default;"
/>
<input type="hidden" id="childSelect" name="childSelect" value="${child.id}" />
`;
} else {
// Multiple children: show dropdown (clear the placeholder first)
select.innerHTML = '';
children.forEach((child) => { children.forEach((child) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = child.id; option.value = child.id;
option.textContent = child.name; option.textContent = child.name;
select.appendChild(option); select.appendChild(option);
}); });
}
// If editing, load the diaper change data // If editing, load the diaper change data
if (isEditMode) { if (isEditMode) {
@@ -295,7 +325,8 @@
.toISOString() .toISOString()
.slice(0, 16); .slice(0, 16);
// Load last used child from localStorage // Load last used child from localStorage (only for multiple children)
if (children.length > 1) {
const lastDiaperChange = JSON.parse( const lastDiaperChange = JSON.parse(
localStorage.getItem("lastDiaperChange") || "{}", localStorage.getItem("lastDiaperChange") || "{}",
); );
@@ -305,6 +336,7 @@
lastDiaperChange.child_id; lastDiaperChange.child_id;
} }
} }
}
} else { } else {
showMessage("Failed to load children", "error"); showMessage("Failed to load children", "error");
} }
@@ -431,7 +463,7 @@
"success", "success",
); );
setTimeout(() => { setTimeout(() => {
window.location.href = "/diapers.html"; window.location.href = getReturnUrl();
}, 1000); }, 1000);
} else { } else {
const errorData = await response.json(); const errorData = await response.json();
+33 -4
View File
@@ -206,6 +206,16 @@
window.location.href = "/login.html"; window.location.href = "/login.html";
} }
// Determine return URL based on referrer
function getReturnUrl() {
const referrer = document.referrer;
if (referrer.includes('/feedings.html')) {
return '/feedings.html';
}
// Default to home page
return '/';
}
const form = document.getElementById("logFeedingForm"); const form = document.getElementById("logFeedingForm");
const messageDiv = document.getElementById("message"); const messageDiv = document.getElementById("message");
const submitBtn = document.getElementById("submitBtn"); const submitBtn = document.getElementById("submitBtn");
@@ -259,13 +269,32 @@
return; return;
} }
// Populate select dropdown // Populate select dropdown or show single child
if (children.length === 1) {
// Single child: replace dropdown with text display
const child = children[0];
const formGroup = childSelect.parentElement;
formGroup.innerHTML = `
<label for="childName">Child</label>
<input
type="text"
id="childName"
value="${child.name}"
readonly
style="background-color: #f5f5f5; cursor: default;"
/>
<input type="hidden" id="childId" name="childId" value="${child.id}" />
`;
} else {
// Multiple children: show dropdown (clear placeholder first)
childSelect.innerHTML = '';
children.forEach((child) => { children.forEach((child) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = child.id; option.value = child.id;
option.textContent = child.name; option.textContent = child.name;
childSelect.appendChild(option); childSelect.appendChild(option);
}); });
}
// Show form, hide loading // Show form, hide loading
loadingDiv.style.display = "none"; loadingDiv.style.display = "none";
@@ -369,9 +398,9 @@
: "Feeding logged successfully!", : "Feeding logged successfully!",
"success", "success",
); );
// Redirect to feedings page after 1 second // Redirect to appropriate page after 1 second
setTimeout(() => { setTimeout(() => {
window.location.href = "/feedings.html"; window.location.href = getReturnUrl();
}, 1000); }, 1000);
} else { } else {
showMessage( showMessage(
@@ -394,7 +423,7 @@
} }
function goBack() { function goBack() {
window.location.href = "/feedings.html"; window.location.href = getReturnUrl();
} }
</script> </script>
</body> </body>
+33 -4
View File
@@ -196,6 +196,16 @@
window.location.href = "/login.html"; window.location.href = "/login.html";
} }
// Determine return URL based on referrer
function getReturnUrl() {
const referrer = document.referrer;
if (referrer.includes('/sleep.html')) {
return '/sleep.html';
}
// Default to home page
return '/';
}
const form = document.getElementById("logSleepForm"); const form = document.getElementById("logSleepForm");
const messageDiv = document.getElementById("message"); const messageDiv = document.getElementById("message");
const submitBtn = document.getElementById("submitBtn"); const submitBtn = document.getElementById("submitBtn");
@@ -249,13 +259,32 @@
return; return;
} }
// Populate select dropdown // Populate select dropdown or show single child
if (children.length === 1) {
// Single child: replace dropdown with text display
const child = children[0];
const formGroup = childSelect.parentElement;
formGroup.innerHTML = `
<label for="childName">Child</label>
<input
type="text"
id="childName"
value="${child.name}"
readonly
style="background-color: #f5f5f5; cursor: default;"
/>
<input type="hidden" id="childId" name="childId" value="${child.id}" />
`;
} else {
// Multiple children: show dropdown (clear placeholder first)
childSelect.innerHTML = '';
children.forEach((child) => { children.forEach((child) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = child.id; option.value = child.id;
option.textContent = child.name; option.textContent = child.name;
childSelect.appendChild(option); childSelect.appendChild(option);
}); });
}
// Show form, hide loading // Show form, hide loading
loadingDiv.style.display = "none"; loadingDiv.style.display = "none";
@@ -353,9 +382,9 @@
: "Sleep logged successfully!", : "Sleep logged successfully!",
"success", "success",
); );
// Redirect to sleep page after 1 second // Redirect to appropriate page after 1 second
setTimeout(() => { setTimeout(() => {
window.location.href = "/sleep.html"; window.location.href = getReturnUrl();
}, 1000); }, 1000);
} else { } else {
showMessage( showMessage(
@@ -378,7 +407,7 @@
} }
function goBack() { function goBack() {
window.location.href = "/sleep.html"; window.location.href = getReturnUrl();
} }
</script> </script>
</body> </body>
+48 -7
View File
@@ -328,10 +328,11 @@
let allChildren = []; let allChildren = [];
let selectedChildId = ""; let selectedChildId = "";
// Filter change handlers // Filter change handlers (will be attached after children are loaded)
document function attachChildFilterListener() {
.getElementById("childFilter") const childFilter = document.getElementById("childFilter");
.addEventListener("change", function () { if (childFilter) {
childFilter.addEventListener("change", function () {
selectedChildId = this.value; selectedChildId = this.value;
// Save selection to localStorage // Save selection to localStorage
if (selectedChildId) { if (selectedChildId) {
@@ -341,6 +342,8 @@
} }
renderCharts(); renderCharts();
}); });
}
}
document document
.getElementById("timeRangeFilter") .getElementById("timeRangeFilter")
@@ -365,19 +368,52 @@
allChildren = await childrenResponse.json(); allChildren = await childrenResponse.json();
// Populate child filter // Auto-select first child if available
if (allChildren.length > 0 && !selectedChildId) {
selectedChildId = allChildren[0].id;
}
// Populate child filter or show single child
const childFilterContainer = document.querySelector('.controls');
if (allChildren.length === 1) {
// Single child: show as read-only text
const child = allChildren[0];
childFilterContainer.innerHTML = `
<label for="childDisplay">Child:</label>
<input
type="text"
id="childDisplay"
value="${child.name}"
readonly
style="background-color: #f5f5f5; cursor: default; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"
/>
<label for="timeRangeFilter">Time Range:</label>
<select id="timeRangeFilter">
<option value="1">Last 24 hours</option>
<option value="3">Last 3 days</option>
<option value="7" selected>Last 7 days</option>
<option value="14">Last 14 days</option>
<option value="30">Last 30 days</option>
<option value="90">Last 90 days</option>
</select>
`;
} else {
// Multiple children: show dropdown
const childFilter = document.getElementById("childFilter"); const childFilter = document.getElementById("childFilter");
childFilter.innerHTML = '<option value="">All Children</option>'; childFilter.innerHTML = '';
allChildren.forEach((child) => { allChildren.forEach((child) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = child.id; option.value = child.id;
option.textContent = child.name; option.textContent = child.name;
childFilter.appendChild(option); childFilter.appendChild(option);
}); });
}
// Restore last selected child from localStorage // Restore last selected child from localStorage
const lastChildId = localStorage.getItem("lastSleepChildFilter"); const lastChildId = localStorage.getItem("lastSleepChildFilter");
if (lastChildId) { if (lastChildId && allChildren.length > 1) {
const childFilter = document.getElementById("childFilter");
childFilter.value = lastChildId; childFilter.value = lastChildId;
selectedChildId = lastChildId; selectedChildId = lastChildId;
} }
@@ -402,6 +438,11 @@
allSleeps = await sleepResponse.json(); allSleeps = await sleepResponse.json();
// Attach event listener for child filter if multiple children
if (allChildren.length > 1) {
attachChildFilterListener();
}
renderCharts(); renderCharts();
} catch (error) { } catch (error) {
console.error("Error loading data:", error); console.error("Error loading data:", error);