Files
baby-monitor/src/baby_monitor/static/diapers.html
T
Brian Bjarke Jensen e26c53456f
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
fixed chart destruction and recreation
2025-11-13 22:55:06 +01:00

1093 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Diapers - Baby Monitor</title>
<link rel="stylesheet" href="/menu.css" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
Cantarell, sans-serif;
background-color: #f0f0f0;
min-height: 100vh;
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 40px;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 14px;
}
.button {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition:
transform 0.2s,
box-shadow 0.2s;
text-decoration: none;
display: inline-block;
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(240, 147, 251, 0.4);
}
.button.secondary {
background: #6c757d;
margin-left: 10px;
}
.button.secondary:hover {
box-shadow: 0 4px 12px rgba(108, 117, 125, 0.4);
}
.hidden {
display: none;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #666;
}
.empty-state-icon {
font-size: 64px;
margin-bottom: 20px;
}
.empty-state h2 {
color: #333;
margin-bottom: 10px;
}
.empty-state p {
margin-bottom: 30px;
}
.chart-container {
margin-bottom: 40px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.chart-container h2 {
color: #333;
margin-bottom: 20px;
font-size: 20px;
}
.chart-wrapper {
position: relative;
height: 300px;
max-width: 100%;
margin: 0 auto;
}
.chart-wrapper canvas {
max-width: 100%;
max-height: 100%;
}
.charts-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 40px;
}
@media (max-width: 768px) {
.charts-row {
grid-template-columns: 1fr;
}
}
.controls {
margin-bottom: 30px;
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
}
.controls label {
font-weight: 600;
color: #333;
}
.controls select {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
background: white;
}
.controls select:focus {
outline: none;
border-color: #f093fb;
}
.diapers-list {
margin-top: 40px;
}
.diapers-list h2 {
color: #333;
margin-bottom: 20px;
font-size: 20px;
}
.diapers-scroll {
max-height: 500px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 8px;
background: #f8f9fa;
}
.diaper-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: white;
border-bottom: 1px solid #eee;
transition: background 0.2s;
}
.diaper-item:hover {
background: #f8f9fa;
}
.diaper-item:last-child {
border-bottom: none;
}
.diaper-info-left {
flex: 1;
}
.diaper-time {
font-weight: 600;
color: #333;
margin-bottom: 5px;
}
.diaper-details {
font-size: 14px;
color: #666;
}
.diaper-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
margin-right: 8px;
}
.diaper-badge.poop {
background: rgba(139, 69, 19, 0.1);
color: rgba(139, 69, 19, 1);
}
.diaper-badge.pee {
background: rgba(255, 215, 0, 0.2);
color: rgba(184, 134, 11, 1);
}
.edit-button {
background: #f093fb;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.edit-button:hover {
background: #d77ee6;
}
</style>
</head>
<body>
<div class="container">
<h1>🧷 Diaper Changes</h1>
<p class="subtitle">Overview of all diaper change sessions</p>
<div id="content" class="loading">
<p>Loading...</p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<script src="/menu.js"></script>
<script>
// Check if user is authenticated
const token = localStorage.getItem("access_token");
if (!token) {
window.location.href = "/login.html";
}
// Initialize burger menu
initBurgerMenu({ includeHome: true });
let allDiaperChanges = [];
let allChildren = [];
let currentDaysRange =
parseInt(localStorage.getItem("lastDiaperTimeRange")) || 7;
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
const lastChildId = localStorage.getItem("lastDiaperChildFilter");
if (lastChildId && lastChildId !== "all") {
selectedChildId = parseInt(lastChildId);
}
// Load diaper changes data
async function loadDiaperChanges() {
try {
const response = await fetch("/api/diaper-changes", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
allDiaperChanges = await response.json();
await loadChildren();
displayDiaperChanges(allDiaperChanges);
} else {
showError("Failed to load diaper changes");
}
} catch (error) {
console.error("Error:", error);
showError("Network error. Please try again.");
}
}
// Load children data
async function loadChildren() {
try {
const response = await fetch("/api/children", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
allChildren = await response.json();
// Auto-select first child if no selection exists
if (selectedChildId === null && allChildren.length > 0) {
selectedChildId = allChildren[0].id;
}
}
} catch (error) {
console.error("Error loading children:", error);
}
}
function onDaysRangeChange(event) {
currentDaysRange = parseInt(event.target.value);
localStorage.setItem("lastDiaperTimeRange", currentDaysRange);
displayDiaperChanges(allDiaperChanges);
}
function onChildChange(event) {
selectedChildId = parseInt(event.target.value);
localStorage.setItem("lastDiaperChildFilter", event.target.value);
displayDiaperChanges(allDiaperChanges);
}
function displayDiaperChanges(diaperChanges) {
const content = document.getElementById("content");
if (diaperChanges.length === 0) {
content.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">🧷</div>
<h2>No Diaper Changes Yet</h2>
<p>Start tracking diaper changes from the home page</p>
</div>
`;
} else {
// Prepare chart data
const barChartData = prepareBarChartData(diaperChanges);
const poopPieData = preparePoopPieChartData(diaperChanges);
const peePieData = preparePeePieChartData(diaperChanges);
// Build child dropdown options
const childOptions = allChildren
.map(
(child) =>
`<option value="${child.id}" ${selectedChildId === child.id ? "selected" : ""}>${child.name}</option>`,
)
.join("");
content.innerHTML = `
<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>
<select id="childFilter" onchange="onChildChange(event)">
${childOptions}
</select>
`
}
<label for="daysRange">Time Range:</label>
<select id="daysRange" onchange="onDaysRangeChange(event)">
<option value="1" ${currentDaysRange === 1 ? "selected" : ""}>Last 24 Hours</option>
<option value="3" ${currentDaysRange === 3 ? "selected" : ""}>Last 3 Days</option>
<option value="7" ${currentDaysRange === 7 ? "selected" : ""}>Last 7 Days</option>
<option value="14" ${currentDaysRange === 14 ? "selected" : ""}>Last 14 Days</option>
<option value="30" ${currentDaysRange === 30 ? "selected" : ""}>Last 30 Days</option>
<option value="90" ${currentDaysRange === 90 ? "selected" : ""}>Last 90 Days</option>
</select>
</div>
<div class="chart-container">
<h2>Diaper Changes (Last ${currentDaysRange} Day${currentDaysRange !== 1 ? "s" : ""})</h2>
<div class="chart-wrapper">
<canvas id="diaperChart"></canvas>
</div>
</div>
<div class="charts-row">
<div class="chart-container">
<h2>💩 Poop Distribution</h2>
<div class="chart-wrapper">
<canvas id="poopPieChart"></canvas>
</div>
</div>
<div class="chart-container">
<h2>💧 Pee Distribution</h2>
<div class="chart-wrapper">
<canvas id="peePieChart"></canvas>
</div>
</div>
</div>
`;
// Render charts
renderBarChart(barChartData);
renderPoopPieChart(poopPieData);
renderPeePieChart(peePieData);
// Render diaper change list
renderDiaperList(diaperChanges);
}
}
function renderDiaperList(diaperChanges) {
// Filter by current time range
const now = new Date();
const daysAgo = new Date(now);
daysAgo.setDate(daysAgo.getDate() - currentDaysRange);
daysAgo.setHours(0, 0, 0, 0);
let filteredChanges = diaperChanges.filter((dc) => {
const changeDate = new Date(dc.change_time);
return changeDate >= daysAgo;
});
// Filter by selected child if applicable
if (selectedChildId !== null) {
filteredChanges = filteredChanges.filter(
(dc) => dc.child_id === selectedChildId,
);
}
// Sort by most recent first
filteredChanges.sort((a, b) => {
return new Date(b.change_time) - new Date(a.change_time);
});
const listHtml = filteredChanges
.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
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-info-left">
<div class="diaper-time">${dateStr} at ${timeStr}${selectedChildId === null ? ` - ${childName}` : ""}</div>
<div class="diaper-details">
${badges.join("")}
</div>
</div>
<button class="edit-button" onclick="editDiaperChange(${dc.id})">Edit</button>
</div>
`;
})
.join("");
const listContainer = `
<div class="diapers-list">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">Diaper Change History</h2>
<button class="button" onclick="addManualDiaperChange()" style="padding: 8px 16px; font-size: 14px;"> Add Manual Entry</button>
</div>
<div class="diapers-scroll">
${listHtml || '<div class="diaper-item">No diaper changes in this time range</div>'}
</div>
</div>
`;
const content = document.getElementById("content");
content.insertAdjacentHTML("beforeend", listContainer);
}
function editDiaperChange(diaperChangeId) {
window.location.href = `/log-diaper.html?id=${diaperChangeId}`;
}
function addManualDiaperChange() {
window.location.href = `/log-diaper.html`;
}
function preparePoopPieChartData(diaperChanges) {
const now = new Date();
const daysAgo = new Date(now);
daysAgo.setDate(daysAgo.getDate() - currentDaysRange);
daysAgo.setHours(0, 0, 0, 0);
// Filter by time range
let recentChanges = diaperChanges.filter((dc) => {
const changeDate = new Date(dc.change_time);
return changeDate >= daysAgo;
});
// Filter by selected child if applicable
if (selectedChildId !== null) {
recentChanges = recentChanges.filter(
(dc) => dc.child_id === selectedChildId,
);
}
// Count poop amounts
const counts = {
light: 0,
medium: 0,
heavy: 0,
none: 0,
};
recentChanges.forEach((dc) => {
if (dc.poop_amount) {
counts[dc.poop_amount]++;
} else {
counts.none++;
}
});
// Only include categories with data
const labels = [];
const data = [];
const labelMap = {
light: "Light",
medium: "Medium",
heavy: "Heavy",
none: "No Poop",
};
Object.entries(counts).forEach(([type, count]) => {
if (count > 0) {
labels.push(labelMap[type]);
data.push(count);
}
});
return { labels, data };
}
function preparePeePieChartData(diaperChanges) {
const now = new Date();
const daysAgo = new Date(now);
daysAgo.setDate(daysAgo.getDate() - currentDaysRange);
daysAgo.setHours(0, 0, 0, 0);
// Filter by time range
let recentChanges = diaperChanges.filter((dc) => {
const changeDate = new Date(dc.change_time);
return changeDate >= daysAgo;
});
// Filter by selected child if applicable
if (selectedChildId !== null) {
recentChanges = recentChanges.filter(
(dc) => dc.child_id === selectedChildId,
);
}
// Count pee amounts
const counts = {
light: 0,
medium: 0,
heavy: 0,
none: 0,
};
recentChanges.forEach((dc) => {
if (dc.pee_amount) {
counts[dc.pee_amount]++;
} else {
counts.none++;
}
});
// Only include categories with data
const labels = [];
const data = [];
const labelMap = {
light: "Light",
medium: "Medium",
heavy: "Heavy",
none: "No Pee",
};
Object.entries(counts).forEach(([type, count]) => {
if (count > 0) {
labels.push(labelMap[type]);
data.push(count);
}
});
return { labels, data };
}
function prepareBarChartData(diaperChanges) {
const now = new Date();
const labels = [];
const poopData = [];
const peeData = [];
// Filter by selected child if applicable
let filteredChanges = diaperChanges;
if (selectedChildId !== null) {
filteredChanges = diaperChanges.filter(
(dc) => dc.child_id === selectedChildId,
);
}
// If 24 hours selected, show 3-hour aggregates
if (currentDaysRange === 1) {
// Create 8 three-hour buckets
for (let i = 7; i >= 0; i--) {
const periodStart = new Date(now);
periodStart.setHours(periodStart.getHours() - i * 3, 0, 0, 0);
const periodEnd = new Date(periodStart);
periodEnd.setHours(periodEnd.getHours() + 3);
const startLabel = periodStart.toLocaleTimeString("en-US", {
hour: "numeric",
hour12: true,
});
const endLabel = periodEnd.toLocaleTimeString("en-US", {
hour: "numeric",
hour12: true,
});
labels.push(`${startLabel}-${endLabel}`);
// Count poop and pee diapers for this 3-hour period
const periodChanges = filteredChanges.filter((dc) => {
const changeDate = new Date(dc.change_time);
return changeDate >= periodStart && changeDate < periodEnd;
});
const poopCount = periodChanges.filter(
(dc) => dc.poop_amount !== null,
).length;
const peeCount = periodChanges.filter(
(dc) => dc.pee_amount !== null,
).length;
poopData.push(poopCount);
peeData.push(peeCount);
}
} else if (currentDaysRange === 3) {
// Show 8-hour aggregates for 3 days (9 buckets total)
for (let i = 8; i >= 0; i--) {
const periodStart = new Date(now);
periodStart.setHours(periodStart.getHours() - i * 8, 0, 0, 0);
const periodEnd = new Date(periodStart);
periodEnd.setHours(periodEnd.getHours() + 8);
// Format label based on the 8-hour period
const dayStr = periodStart.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
});
const startHour = periodStart.getHours();
let periodLabel;
if (startHour >= 0 && startHour < 8) {
periodLabel = `${dayStr} Night`;
} else if (startHour >= 8 && startHour < 16) {
periodLabel = `${dayStr} Morning`;
} else {
periodLabel = `${dayStr} Evening`;
}
labels.push(periodLabel);
// Count poop and pee diapers for this 8-hour period
const periodChanges = filteredChanges.filter((dc) => {
const changeDate = new Date(dc.change_time);
return changeDate >= periodStart && changeDate < periodEnd;
});
const poopCount = periodChanges.filter(
(dc) => dc.poop_amount !== null,
).length;
const peeCount = periodChanges.filter(
(dc) => dc.pee_amount !== null,
).length;
poopData.push(poopCount);
peeData.push(peeCount);
}
} else {
// Create days based on currentDaysRange (including today)
for (let i = currentDaysRange - 1; i >= 0; i--) {
const date = new Date(now);
date.setDate(date.getDate() - i);
date.setHours(0, 0, 0, 0);
const dateStr = date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
});
labels.push(dateStr);
// Count poop and pee diapers for this day
const nextDay = new Date(date);
nextDay.setDate(nextDay.getDate() + 1);
const dayChanges = filteredChanges.filter((dc) => {
const changeDate = new Date(dc.change_time);
return changeDate >= date && changeDate < nextDay;
});
const poopCount = dayChanges.filter(
(dc) => dc.poop_amount !== null,
).length;
const peeCount = dayChanges.filter(
(dc) => dc.pee_amount !== null,
).length;
poopData.push(poopCount);
peeData.push(peeCount);
}
}
return { labels, poopData, peeData };
}
function renderBarChart(chartData) {
const ctx = document.getElementById("diaperChart").getContext("2d");
// Destroy existing chart instance if it exists
if (barChartInstance) {
barChartInstance.destroy();
}
barChartInstance = new Chart(ctx, {
type: "bar",
data: {
labels: chartData.labels,
datasets: [
{
label: "💩 Poop",
data: chartData.poopData,
backgroundColor: "rgba(139, 69, 19, 0.7)",
borderColor: "rgba(139, 69, 19, 1)",
borderWidth: 2,
borderRadius: 6,
},
{
label: "💧 Pee",
data: chartData.peeData,
backgroundColor: "rgba(255, 215, 0, 0.7)",
borderColor: "rgba(255, 215, 0, 1)",
borderWidth: 2,
borderRadius: 6,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: "top",
labels: {
padding: 15,
font: {
size: 13,
},
},
},
tooltip: {
backgroundColor: "rgba(0, 0, 0, 0.8)",
padding: 12,
titleFont: {
size: 14,
},
bodyFont: {
size: 13,
},
callbacks: {
label: function (context) {
const count = context.parsed.y;
return `${context.dataset.label}: ${count}`;
},
},
},
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: "Number of Changes",
font: {
size: 13,
weight: "600",
},
},
ticks: {
stepSize: 1,
font: {
size: 12,
},
},
grid: {
color: "rgba(0, 0, 0, 0.05)",
},
},
x: {
ticks: {
font: {
size: 12,
},
},
grid: {
display: false,
},
},
},
},
});
}
function renderPoopPieChart(chartData) {
const ctx = document.getElementById("poopPieChart").getContext("2d");
// Destroy existing chart instance if it exists
if (poopPieChartInstance) {
poopPieChartInstance.destroy();
}
const colors = {
Light: {
bg: "rgba(139, 69, 19, 0.5)",
border: "rgba(139, 69, 19, 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(
(label) => colors[label]?.bg || "rgba(200, 200, 200, 0.5)",
);
const borderColors = chartData.labels.map(
(label) => colors[label]?.border || "rgba(200, 200, 200, 1)",
);
poopPieChartInstance = new Chart(ctx, {
type: "pie",
data: {
labels: chartData.labels,
datasets: [
{
data: chartData.data,
backgroundColor: backgroundColors,
borderColor: borderColors,
borderWidth: 2,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom",
labels: {
padding: 20,
font: {
size: 13,
},
},
},
tooltip: {
backgroundColor: "rgba(0, 0, 0, 0.8)",
padding: 12,
titleFont: {
size: 14,
},
bodyFont: {
size: 13,
},
callbacks: {
label: function (context) {
const count = context.parsed;
const total = context.dataset.data.reduce(
(a, b) => a + b,
0,
);
const percentage =
total > 0 ? ((count / total) * 100).toFixed(1) : 0;
return `${context.label}: ${count} (${percentage}%)`;
},
},
},
},
},
});
}
function renderPeePieChart(chartData) {
const ctx = document.getElementById("peePieChart").getContext("2d");
// Destroy existing chart instance if it exists
if (peePieChartInstance) {
peePieChartInstance.destroy();
}
const colors = {
Light: {
bg: "rgba(255, 215, 0, 0.5)",
border: "rgba(255, 215, 0, 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(
(label) => colors[label]?.bg || "rgba(200, 200, 200, 0.5)",
);
const borderColors = chartData.labels.map(
(label) => colors[label]?.border || "rgba(200, 200, 200, 1)",
);
peePieChartInstance = new Chart(ctx, {
type: "pie",
data: {
labels: chartData.labels,
datasets: [
{
data: chartData.data,
backgroundColor: backgroundColors,
borderColor: borderColors,
borderWidth: 2,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom",
labels: {
padding: 20,
font: {
size: 13,
},
},
},
tooltip: {
backgroundColor: "rgba(0, 0, 0, 0.8)",
padding: 12,
titleFont: {
size: 14,
},
bodyFont: {
size: 13,
},
callbacks: {
label: function (context) {
const count = context.parsed;
const total = context.dataset.data.reduce(
(a, b) => a + b,
0,
);
const percentage =
total > 0 ? ((count / total) * 100).toFixed(1) : 0;
return `${context.label}: ${count} (${percentage}%)`;
},
},
},
},
},
});
}
function showError(message) {
const content = document.getElementById("content");
content.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">⚠️</div>
<h2>Error</h2>
<p>${message}</p>
</div>
`;
}
async function logout() {
const token = localStorage.getItem("access_token");
try {
await fetch("/api/logout", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
});
} catch (error) {
console.error("Logout error:", error);
} finally {
// Clear local storage and redirect
localStorage.removeItem("access_token");
localStorage.removeItem("username");
window.location.href = "/login.html";
}
}
// Load data on page load
loadDiaperChanges();
</script>
</body>
</html>