added feeding data objects and workflow
This commit is contained in:
@@ -117,6 +117,92 @@
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
.feeding-button {
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
.feeding-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
||||
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
|
||||
}
|
||||
.feeding-button.active {
|
||||
background: linear-gradient(135deg, #f44336 0%, #e91e63 100%);
|
||||
}
|
||||
.feeding-button.active:hover {
|
||||
background: linear-gradient(135deg, #e91e63 0%, #f44336 100%);
|
||||
}
|
||||
.feeding-info {
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.5rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1001;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.modal.show {
|
||||
display: flex;
|
||||
}
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.modal h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.modal-buttons button {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
.cancel-button {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
.cancel-button:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -139,6 +225,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Start Feeding Modal -->
|
||||
<div class="modal" id="startFeedingModal">
|
||||
<div class="modal-content">
|
||||
<h2>🍼 Start Feeding</h2>
|
||||
<form id="startFeedingForm">
|
||||
<div class="form-group">
|
||||
<label for="modalChildId">Child</label>
|
||||
<select id="modalChildId" required>
|
||||
<option value="">Select a child</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="modalFeedingType">Feeding Type</label>
|
||||
<select id="modalFeedingType" required>
|
||||
<option value="">Select type</option>
|
||||
<option value="left_breast">Left Breast</option>
|
||||
<option value="right_breast">Right Breast</option>
|
||||
<option value="bottle">Bottle</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="cancel-button"
|
||||
onclick="closeStartFeedingModal()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit">Start</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const token = localStorage.getItem("access_token");
|
||||
const username = localStorage.getItem("username");
|
||||
@@ -206,14 +326,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Show authenticated content
|
||||
document.getElementById("content").innerHTML = `
|
||||
<h1>Welcome to Baby Monitor!</h1>
|
||||
<div class="user-info">
|
||||
<p><strong>Logged in as:</strong> ${username}</p>
|
||||
</div>
|
||||
<p>Your session is active.</p>
|
||||
`;
|
||||
// Load feeding status and show content
|
||||
loadFeedingStatus(children);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -221,6 +335,249 @@
|
||||
});
|
||||
}
|
||||
|
||||
let activeFeeding = null;
|
||||
let userChildren = [];
|
||||
let lastFeeding = null;
|
||||
let updateTimerInterval = null;
|
||||
|
||||
async function loadFeedingStatus(children) {
|
||||
userChildren = children;
|
||||
|
||||
try {
|
||||
// Fetch active feeding
|
||||
const activeResponse = await fetch("/api/feedings/active", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (activeResponse.ok) {
|
||||
activeFeeding = await activeResponse.json();
|
||||
}
|
||||
|
||||
// Fetch all feedings to get the last one
|
||||
const feedingsResponse = await fetch("/api/feedings", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading feeding status:", error);
|
||||
}
|
||||
|
||||
renderContent();
|
||||
startUpdateTimer();
|
||||
}
|
||||
|
||||
function startUpdateTimer() {
|
||||
// Clear any existing timer
|
||||
if (updateTimerInterval) {
|
||||
clearInterval(updateTimerInterval);
|
||||
}
|
||||
|
||||
// Update every 30 seconds if there's an active feeding
|
||||
if (activeFeeding) {
|
||||
updateTimerInterval = setInterval(() => {
|
||||
if (activeFeeding) {
|
||||
updateFeedingTimeDisplay();
|
||||
} else {
|
||||
clearInterval(updateTimerInterval);
|
||||
updateTimerInterval = null;
|
||||
}
|
||||
}, 30000); // Update every 30 seconds
|
||||
}
|
||||
}
|
||||
|
||||
function updateFeedingTimeDisplay() {
|
||||
const timeElement = document.getElementById("feedingTimeInfo");
|
||||
if (timeElement && activeFeeding) {
|
||||
timeElement.textContent = formatTime(activeFeeding.start_time);
|
||||
}
|
||||
}
|
||||
|
||||
function renderContent() {
|
||||
const feedingButtonHtml = activeFeeding
|
||||
? `
|
||||
<button class="feeding-button active" onclick="stopFeeding()">
|
||||
🛑 Stop Feeding
|
||||
<div class="feeding-info">
|
||||
${activeFeeding.child_name || "Child"} - ${formatFeedingType(activeFeeding.feeding_type)}<br>
|
||||
Started <span id="feedingTimeInfo">${formatTime(activeFeeding.start_time)}</span>
|
||||
</div>
|
||||
</button>
|
||||
`
|
||||
: `
|
||||
<button class="feeding-button" onclick="showStartFeedingModal()">
|
||||
▶️ Start Feeding
|
||||
</button>
|
||||
`;
|
||||
|
||||
document.getElementById("content").innerHTML = `
|
||||
<h1>Welcome to Baby Monitor!</h1>
|
||||
<div class="user-info">
|
||||
<p><strong>Logged in as:</strong> ${username}</p>
|
||||
</div>
|
||||
${feedingButtonHtml}
|
||||
<p>Your session is active.</p>
|
||||
`;
|
||||
}
|
||||
|
||||
function formatFeedingType(type) {
|
||||
const types = {
|
||||
left_breast: "Left Breast",
|
||||
right_breast: "Right Breast",
|
||||
bottle: "Bottle",
|
||||
};
|
||||
return types[type] || type;
|
||||
}
|
||||
|
||||
function formatTime(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
|
||||
if (diffMins < 1) return "just now";
|
||||
if (diffMins < 60) return `${diffMins} min ago`;
|
||||
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
if (diffHours < 24) return `${diffHours}h ${diffMins % 60}m ago`;
|
||||
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
function showStartFeedingModal() {
|
||||
const modal = document.getElementById("startFeedingModal");
|
||||
const childSelect = document.getElementById("modalChildId");
|
||||
const typeSelect = document.getElementById("modalFeedingType");
|
||||
|
||||
// Clear and populate child select
|
||||
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 defaults based on last feeding
|
||||
if (lastFeeding) {
|
||||
childSelect.value = lastFeeding.child_id;
|
||||
typeSelect.value = lastFeeding.feeding_type;
|
||||
}
|
||||
|
||||
modal.classList.add("show");
|
||||
}
|
||||
|
||||
function closeStartFeedingModal() {
|
||||
const modal = document.getElementById("startFeedingModal");
|
||||
modal.classList.remove("show");
|
||||
document.getElementById("startFeedingForm").reset();
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("startFeedingForm")
|
||||
.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const childId = parseInt(
|
||||
document.getElementById("modalChildId").value,
|
||||
);
|
||||
const feedingType = document.getElementById("modalFeedingType").value;
|
||||
|
||||
try {
|
||||
const now = new Date();
|
||||
const localDateTime = new Date(
|
||||
now.getTime() - now.getTimezoneOffset() * 60000,
|
||||
)
|
||||
.toISOString()
|
||||
.slice(0, 19);
|
||||
|
||||
const response = await fetch("/api/feedings", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
child_id: childId,
|
||||
start_time: localDateTime,
|
||||
end_time: null,
|
||||
feeding_type: feedingType,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const feeding = await response.json();
|
||||
activeFeeding = feeding;
|
||||
lastFeeding = feeding; // Update last feeding
|
||||
|
||||
// Add child name to active feeding
|
||||
const child = userChildren.find((c) => c.id === childId);
|
||||
if (child) {
|
||||
activeFeeding.child_name = child.name;
|
||||
}
|
||||
|
||||
closeStartFeedingModal();
|
||||
renderContent();
|
||||
startUpdateTimer(); // Start the timer for live updates
|
||||
} else {
|
||||
const data = await response.json();
|
||||
alert(data.detail || "Failed to start feeding");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error starting feeding:", error);
|
||||
alert("Network error. Please try again.");
|
||||
}
|
||||
});
|
||||
|
||||
async function stopFeeding() {
|
||||
if (!activeFeeding) return;
|
||||
|
||||
try {
|
||||
const now = new Date();
|
||||
const localDateTime = new Date(
|
||||
now.getTime() - now.getTimezoneOffset() * 60000,
|
||||
)
|
||||
.toISOString()
|
||||
.slice(0, 19);
|
||||
|
||||
const response = await fetch(`/api/feedings/${activeFeeding.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
end_time: localDateTime,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
activeFeeding = null;
|
||||
if (updateTimerInterval) {
|
||||
clearInterval(updateTimerInterval);
|
||||
updateTimerInterval = null;
|
||||
}
|
||||
renderContent();
|
||||
} else {
|
||||
const data = await response.json();
|
||||
alert(data.detail || "Failed to stop feeding");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error stopping feeding:", error);
|
||||
alert("Network error. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
const token = localStorage.getItem("access_token");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user