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
brian 320696c57c Merge pull request 'added recent activity cards to home page' (#37) from add-most-recent-entries-to-home-page into main
Python Code Quality / python-code-quality (push) Successful in 9s
Python Test / python-test (push) Successful in 18s
Build and Push Docker Image / build-and-push (push) Successful in 28s
Refresh Showcase Data / refresh-data (push) Successful in 17s
Reviewed-on: #37
2025-11-13 22:57:07 +01:00
Brian Bjarke Jensen e26c53456f fixed chart destruction and recreation
Build and Push Docker Image / build-and-push (pull_request) Successful in 58s
Python Code Quality / python-code-quality (pull_request) Successful in 11s
Python Test / python-test (pull_request) Successful in 18s
2025-11-13 22:55:06 +01:00
Brian Bjarke Jensen 1eeb8ad148 added recent activity cards to home page
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
2025-11-13 22:44:41 +01:00
13 changed files with 398 additions and 182 deletions
-6
View File
@@ -143,12 +143,6 @@ def serve_menu_js() -> FileResponse:
return FileResponse(static_path / "menu.js") return FileResponse(static_path / "menu.js")
@app.get("/auth-utils.js", include_in_schema=False)
def serve_auth_utils_js() -> FileResponse:
"""Serve the shared auth utilities JavaScript."""
return FileResponse(static_path / "auth-utils.js")
@app.get("/api/") @app.get("/api/")
def read_root(token: Annotated[str, Depends(verify_token)]) -> dict: def read_root(token: Annotated[str, Depends(verify_token)]) -> dict:
"""API root endpoint (requires authentication).""" """API root endpoint (requires authentication)."""
+4 -2
View File
@@ -211,10 +211,12 @@
</div> </div>
</div> </div>
<script src="/auth-utils.js"></script>
<script> <script>
// Check if user is authenticated // Check if user is authenticated
const token = getToken(); const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Get child ID from URL if editing // Get child ID from URL if editing
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
+5 -3
View File
@@ -424,15 +424,17 @@
</div> </div>
</div> </div>
<script src="/auth-utils.js"></script>
<script> <script>
// Check if user is authenticated and is admin // Check if user is authenticated and is admin
const token = getToken(); const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
let allUsers = []; let allUsers = [];
let userToDelete = null; let userToDelete = null;
// Load data // Verify user is admin and load data
fetch("/api/me", { fetch("/api/me", {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
-21
View File
@@ -1,21 +0,0 @@
/**
* Redirect to login page with return URL
* @param {string} returnUrl - The URL to return to after login (defaults to current page)
*/
function redirectToLogin(returnUrl) {
const url = returnUrl || window.location.pathname + window.location.search;
window.location.href = `/login.html?return=${encodeURIComponent(url)}`;
}
/**
* Check if user has valid token, redirect to login if not
* @returns {string|null} - Returns token if valid, redirects to login if not
*/
function getToken() {
const token = localStorage.getItem("access_token");
if (!token) {
redirectToLogin();
return null;
}
return token;
}
+25 -5
View File
@@ -266,10 +266,12 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="/menu.js"></script> <script src="/menu.js"></script>
<script src="/auth-utils.js"></script>
<script> <script>
// Check if user is authenticated // Check if user is authenticated
const token = getToken(); const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Initialize burger menu // Initialize burger menu
initBurgerMenu({ includeHome: true }); initBurgerMenu({ includeHome: true });
@@ -279,6 +281,9 @@
let currentDaysRange = let currentDaysRange =
parseInt(localStorage.getItem("lastDiaperTimeRange")) || 7; parseInt(localStorage.getItem("lastDiaperTimeRange")) || 7;
let selectedChildId = null; // null means show first/only child 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 // Restore last selected child from localStorage
const lastChildId = localStorage.getItem("lastDiaperChildFilter"); const lastChildId = localStorage.getItem("lastDiaperChildFilter");
@@ -780,7 +785,12 @@
function renderBarChart(chartData) { function renderBarChart(chartData) {
const ctx = document.getElementById("diaperChart").getContext("2d"); 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", type: "bar",
data: { data: {
labels: chartData.labels, labels: chartData.labels,
@@ -873,6 +883,11 @@
function renderPoopPieChart(chartData) { function renderPoopPieChart(chartData) {
const ctx = document.getElementById("poopPieChart").getContext("2d"); const ctx = document.getElementById("poopPieChart").getContext("2d");
// Destroy existing chart instance if it exists
if (poopPieChartInstance) {
poopPieChartInstance.destroy();
}
const colors = { const colors = {
Light: { Light: {
bg: "rgba(139, 69, 19, 0.5)", bg: "rgba(139, 69, 19, 0.5)",
@@ -899,7 +914,7 @@
(label) => colors[label]?.border || "rgba(200, 200, 200, 1)", (label) => colors[label]?.border || "rgba(200, 200, 200, 1)",
); );
new Chart(ctx, { poopPieChartInstance = new Chart(ctx, {
type: "pie", type: "pie",
data: { data: {
labels: chartData.labels, labels: chartData.labels,
@@ -955,6 +970,11 @@
function renderPeePieChart(chartData) { function renderPeePieChart(chartData) {
const ctx = document.getElementById("peePieChart").getContext("2d"); const ctx = document.getElementById("peePieChart").getContext("2d");
// Destroy existing chart instance if it exists
if (peePieChartInstance) {
peePieChartInstance.destroy();
}
const colors = { const colors = {
Light: { Light: {
bg: "rgba(255, 215, 0, 0.5)", bg: "rgba(255, 215, 0, 0.5)",
@@ -981,7 +1001,7 @@
(label) => colors[label]?.border || "rgba(200, 200, 200, 1)", (label) => colors[label]?.border || "rgba(200, 200, 200, 1)",
); );
new Chart(ctx, { peePieChartInstance = new Chart(ctx, {
type: "pie", type: "pie",
data: { data: {
labels: chartData.labels, labels: chartData.labels,
+18 -4
View File
@@ -303,10 +303,12 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="/menu.js"></script> <script src="/menu.js"></script>
<script src="/auth-utils.js"></script>
<script> <script>
// Check if user is authenticated // Check if user is authenticated
const token = getToken(); const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Initialize burger menu // Initialize burger menu
initBurgerMenu({ includeHome: true }); initBurgerMenu({ includeHome: true });
@@ -317,6 +319,8 @@
parseInt(localStorage.getItem("lastFeedingTimeRange")) || 7; parseInt(localStorage.getItem("lastFeedingTimeRange")) || 7;
let selectedChildId = null; // null means show first/only child let selectedChildId = null; // null means show first/only child
let barChartInstance = null; let barChartInstance = null;
let pieChartInstance = null;
let durationChartInstance = null;
// Restore last selected child from localStorage // Restore last selected child from localStorage
const lastChildId = localStorage.getItem("lastFeedingChildFilter"); const lastChildId = localStorage.getItem("lastFeedingChildFilter");
@@ -977,6 +981,11 @@
function renderPieChart(chartData) { function renderPieChart(chartData) {
const ctx = document.getElementById("pieChart").getContext("2d"); const ctx = document.getElementById("pieChart").getContext("2d");
// Destroy existing chart instance if it exists
if (pieChartInstance) {
pieChartInstance.destroy();
}
// Color mapping for each type // Color mapping for each type
const colorMap = { const colorMap = {
"Left Breast": { "Left Breast": {
@@ -1001,7 +1010,7 @@
(label) => colorMap[label]?.border || "rgba(200, 200, 200, 1)", (label) => colorMap[label]?.border || "rgba(200, 200, 200, 1)",
); );
new Chart(ctx, { pieChartInstance = new Chart(ctx, {
type: "pie", type: "pie",
data: { data: {
labels: chartData.labels, labels: chartData.labels,
@@ -1057,7 +1066,12 @@
function renderDurationChart(chartData) { function renderDurationChart(chartData) {
const ctx = document.getElementById("durationChart").getContext("2d"); 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", type: "bar",
data: { data: {
labels: chartData.labels, labels: chartData.labels,
+235 -46
View File
@@ -167,6 +167,52 @@
margin-top: 0.5rem; margin-top: 0.5rem;
opacity: 0.9; 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: flex;
flex-direction: column;
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;
}
</style> </style>
</head> </head>
<body> <body>
@@ -236,21 +282,32 @@
</div> </div>
<script src="/menu.js"></script> <script src="/menu.js"></script>
<script src="/auth-utils.js"></script>
<script> <script>
const token = getToken(); const token = localStorage.getItem("access_token");
const username = localStorage.getItem("username"); const username = localStorage.getItem("username");
// Check if user is logged in
if (!token) {
window.location.href = "/login.html";
} else {
// Fetch current user info including admin status // Fetch current user info including admin status
fetch("/api/me", { fetch("/api/me", {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
}) })
.then((response) => response.json()) .then((response) => {
if (response.ok) {
return response.json();
} else {
// Token invalid, redirect to login
localStorage.removeItem("access_token");
localStorage.removeItem("username");
window.location.href = "/login.html";
throw new Error("Authentication failed");
}
})
.then((user) => { .then((user) => {
if (!user) return; // Early exit if redirected
// Initialize burger menu // Initialize burger menu
initBurgerMenu({ initBurgerMenu({
includeHome: true, includeHome: true,
@@ -277,6 +334,7 @@
.catch((error) => { .catch((error) => {
console.error("Error:", error); console.error("Error:", error);
}); });
}
function showNoChildrenMessage() { function showNoChildrenMessage() {
const content = document.getElementById("content"); const content = document.getElementById("content");
@@ -309,6 +367,7 @@
let activeSleep = null; let activeSleep = null;
let lastSleep = null; let lastSleep = null;
let sleepUpdateTimerInterval = null; let sleepUpdateTimerInterval = null;
let lastDiaperChange = null;
async function loadFeedingStatus(children) { async function loadFeedingStatus(children) {
userChildren = children; userChildren = children;
@@ -325,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}`,
}, },
@@ -335,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,
};
} }
} }
@@ -364,6 +483,20 @@
lastSleep = sleeps[0]; 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) { } catch (error) {
console.error("Error loading feeding status:", error); console.error("Error loading feeding status:", error);
} }
@@ -437,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>
`; `;
@@ -466,9 +599,100 @@
</button> </button>
${feedingButtonHtml} ${feedingButtonHtml}
${sleepButtonHtml} ${sleepButtonHtml}
${buildRecentActivitySection()}
`; `;
} }
function buildRecentActivitySection() {
// Build diaper card
const diaperCard = lastDiaperChange
? `
<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>
<div class="details">
${lastDiaperChange.poop_amount ? `💩 ${lastDiaperChange.poop_amount}` : ""}
${lastDiaperChange.poop_amount && lastDiaperChange.pee_amount ? " • " : ""}
${lastDiaperChange.pee_amount ? `💧 ${lastDiaperChange.pee_amount}` : ""}
</div>
</a>
`
: `
<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>
</a>
`;
// Build feeding card
const feedingCard = lastFeeding
? `
<a href="/feedings.html" class="activity-card" style="text-decoration:none; color:inherit;">
<div class="icon">🍼</div>
<div class="type">Last Feeding Session</div>
<div class="time">${formatTime(lastFeeding.start_time)}</div>
<div class="details">
${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>
`
: `
<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>
</a>
`;
// Build sleep card
const sleepCard = lastSleep
? `
<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">
Duration: ${lastSleep.end_time ? calculateDuration(lastSleep.start_time, lastSleep.end_time) : "ongoing"}
</div>
</a>
`
: `
<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>
</a>
`;
return `
<div class="recent-activity">
<h3>📊 Recent Activity</h3>
<div class="activity-grid">
${diaperCard}
${feedingCard}
${sleepCard}
</div>
</div>
`;
}
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) { function formatFeedingType(type) {
const types = { const types = {
left_breast: "Left Breast", left_breast: "Left Breast",
@@ -640,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() {
+27 -2
View File
@@ -242,10 +242,12 @@
</form> </form>
</div> </div>
<script src="/auth-utils.js"></script>
<script> <script>
// Check if user is authenticated // Check if user is authenticated
const token = getToken(); const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Determine return URL based on referrer // Determine return URL based on referrer
function getReturnUrl() { function getReturnUrl() {
@@ -334,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");
+20 -2
View File
@@ -199,10 +199,12 @@
</form> </form>
</div> </div>
<script src="/auth-utils.js"></script>
<script> <script>
// Check if user is authenticated // Check if user is authenticated
const token = getToken(); const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Determine return URL based on referrer // Determine return URL based on referrer
function getReturnUrl() { function getReturnUrl() {
@@ -301,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");
+4 -2
View File
@@ -189,10 +189,12 @@
</form> </form>
</div> </div>
<script src="/auth-utils.js"></script>
<script> <script>
// Check if user is authenticated // Check if user is authenticated
const token = getToken(); const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Determine return URL based on referrer // Determine return URL based on referrer
function getReturnUrl() { function getReturnUrl() {
+2 -38
View File
@@ -82,36 +82,10 @@
</div> </div>
<script> <script>
// Check if redirected due to expired session
const params = new URLSearchParams(window.location.search);
const returnUrl = params.get("return");
const form = document.getElementById("loginForm"); const form = document.getElementById("loginForm");
const messageDiv = document.getElementById("message"); const messageDiv = document.getElementById("message");
const loginButton = document.getElementById("loginButton"); const loginButton = document.getElementById("loginButton");
// Show session expired message if redirected from another page
if (returnUrl) {
showMessage("Your session has expired. Please log in again.", "error");
}
// Clear user-specific cache data on login
function clearUserCache() {
const keysToRemove = [
'lastDiaperChildFilter',
'lastFeedingChildFilter',
'lastSleepChildFilter',
'lastDiaperTimeRange',
'lastFeedingTimeRange'
];
keysToRemove.forEach(key => {
localStorage.removeItem(key);
});
console.log('Cleared user cache on login');
}
form.addEventListener("submit", async (e) => { form.addEventListener("submit", async (e) => {
e.preventDefault(); e.preventDefault();
@@ -135,23 +109,13 @@
if (response.ok) { if (response.ok) {
showMessage(data.message, "success"); showMessage(data.message, "success");
// Store token in localStorage // Store token in localStorage
localStorage.setItem("access_token", data.access_token); localStorage.setItem("access_token", data.access_token);
localStorage.setItem("username", data.username); localStorage.setItem("username", data.username);
// Clear stale user preferences from previous sessions // Redirect based on is_admin from login response
clearUserCache();
// Check for return URL parameter
const params = new URLSearchParams(window.location.search);
const returnUrl = params.get("return");
// Redirect based on return URL or is_admin from login response
setTimeout(() => { setTimeout(() => {
if (returnUrl) { if (data.is_admin === true) {
window.location.href = returnUrl;
} else if (data.is_admin === true) {
window.location.href = "/admin.html"; window.location.href = "/admin.html";
} else { } else {
window.location.href = "/"; window.location.href = "/";
+6 -6
View File
@@ -469,9 +469,11 @@
</div> </div>
<script src="/menu.js"></script> <script src="/menu.js"></script>
<script src="/auth-utils.js"></script>
<script> <script>
const token = getToken(); const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Initialize burger menu // Initialize burger menu
initBurgerMenu({ includeHome: true }); initBurgerMenu({ includeHome: true });
@@ -493,7 +495,7 @@
currentUser = await userResponse.json(); currentUser = await userResponse.json();
document.getElementById("username").value = currentUser.username; document.getElementById("username").value = currentUser.username;
} else { } else {
redirectToLogin(); window.location.href = "/login.html";
return; return;
} }
@@ -850,10 +852,8 @@
} }
}); });
// Load data on page load (only if authenticated) // Load data on page load
if (token) {
loadData(); loadData();
}
</script> </script>
</body> </body>
</html> </html>
+9 -2
View File
@@ -313,9 +313,13 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="/menu.js"></script> <script src="/menu.js"></script>
<script src="/auth-utils.js"></script>
<script> <script>
const token = getToken(); const token = localStorage.getItem("access_token");
// Check if user is logged in
if (!token) {
window.location.href = "/login.html";
}
// Initialize burger menu // Initialize burger menu
initBurgerMenu({ includeHome: true }); initBurgerMenu({ includeHome: true });
@@ -1100,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>
`; `;
} }