665 lines
28 KiB
JavaScript
665 lines
28 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
// --- Global Variables & State ---
|
|
let jwtToken = localStorage.getItem('jwtToken'); // Load token from storage
|
|
let currentUserId = null; // Will be set after login or token validation
|
|
let milkDataCache = []; // Cache loaded milk data
|
|
|
|
// --- DOM Elements ---
|
|
const addMilkBtn = document.getElementById('add-milk-btn');
|
|
const recordConsumptionBtn = document.getElementById('record-consumption-btn');
|
|
const settingsBtn = document.getElementById('settings-btn');
|
|
const actionsContainer = document.querySelector('.actions');
|
|
const backToDashboardBtn = document.getElementById('back-to-dashboard-btn');
|
|
const logoutBtn = document.getElementById('logout-btn');
|
|
|
|
const addMilkModal = document.getElementById('add-milk-modal');
|
|
const recordConsumptionModal = document.getElementById('record-consumption-modal');
|
|
const authModal = document.getElementById('auth-modal'); // Auth modal
|
|
const closeAddModalBtn = document.getElementById('close-add-modal');
|
|
const closeRecordModalBtn = document.getElementById('close-record-modal');
|
|
const closeAuthModalBtn = document.getElementById('close-auth-modal'); // Auth close
|
|
|
|
const dashboardPage = document.getElementById('dashboard');
|
|
const settingsPage = document.getElementById('settings-page');
|
|
|
|
const addMilkForm = document.getElementById('add-milk-form');
|
|
const recordConsumptionForm = document.getElementById('record-consumption-form');
|
|
const settingsForm = document.getElementById('settings-form');
|
|
const authForm = document.getElementById('auth-form'); // Auth form
|
|
const loginBtn = document.getElementById('login-btn'); // Login button
|
|
const registerBtn = document.getElementById('register-btn'); // Register button
|
|
|
|
const milkListContainer = document.getElementById('milk-list');
|
|
const noMilkMessage = document.getElementById('no-milk-message');
|
|
const consumeDateInput = document.getElementById('consume-date');
|
|
const authStatusDiv = document.getElementById('auth-status'); // Auth status display
|
|
const authErrorP = document.getElementById('auth-error'); // Auth error display
|
|
|
|
// --- API Helper ---
|
|
async function fetchApi(endpoint, options = {}) {
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
...options.headers, // Allow overriding headers
|
|
};
|
|
|
|
// Add JWT token if available
|
|
if (jwtToken) {
|
|
headers['Authorization'] = `Bearer ${jwtToken}`;
|
|
}
|
|
|
|
const config = {
|
|
...options,
|
|
headers: headers,
|
|
};
|
|
|
|
// Construct full API URL (assuming API is served from the same origin)
|
|
const url = `/api${endpoint}`; // Prepend /api
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
|
|
// Handle unauthorized errors (e.g., expired token)
|
|
if (response.status === 401 || response.status === 422) { // 422 Unprocessable Entity (often used by flask-jwt for missing/bad token)
|
|
console.error('Authentication error:', response.status);
|
|
handleLogout(true); // Force logout and show login modal
|
|
throw new Error('Authentication Required'); // Stop further processing
|
|
}
|
|
|
|
// Get response body (if any)
|
|
const responseData = response.headers.get('Content-Type')?.includes('application/json')
|
|
? await response.json() // Parse JSON if applicable
|
|
: null; // Handle non-JSON responses if necessary
|
|
|
|
if (!response.ok) {
|
|
// Try to get error message from response body
|
|
const errorMessage = responseData?.error || responseData?.message || `HTTP error ${response.status}`;
|
|
console.error(`API Error (${response.status}) on ${endpoint}:`, errorMessage, responseData);
|
|
throw new Error(errorMessage);
|
|
}
|
|
|
|
console.log(`API Success on ${endpoint}: Status ${response.status}`);
|
|
return responseData; // Return JSON data or null
|
|
|
|
} catch (error) {
|
|
console.error(`Workspace failed for ${endpoint}:`, error);
|
|
// Display error to user? (e.g., a general banner)
|
|
displayGlobalError(`请求失败: ${error.message}`);
|
|
throw error; // Re-throw error so calling function knows it failed
|
|
}
|
|
}
|
|
|
|
// --- Authentication Functions ---
|
|
function showLoginModal() {
|
|
clearAuthForm();
|
|
authErrorP.style.display = 'none';
|
|
openModal(authModal);
|
|
}
|
|
|
|
function clearAuthForm() {
|
|
const usernameInput = document.getElementById('auth-username');
|
|
const passwordInput = document.getElementById('auth-password');
|
|
if (usernameInput) usernameInput.value = '';
|
|
if (passwordInput) passwordInput.value = '';
|
|
}
|
|
|
|
async function handleLogin(event) {
|
|
event?.preventDefault(); // Prevent default if called from button click
|
|
const username = document.getElementById('auth-username').value;
|
|
const password = document.getElementById('auth-password').value;
|
|
authErrorP.style.display = 'none';
|
|
|
|
if (!username || !password) {
|
|
displayAuthError('请输入用户名和密码');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const data = await fetchApi('/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
if (data && data.access_token) {
|
|
jwtToken = data.access_token;
|
|
localStorage.setItem('jwtToken', jwtToken);
|
|
console.log('Login successful');
|
|
closeModal(authModal);
|
|
initializeApp(); // Reload data and update UI for logged-in user
|
|
} else {
|
|
displayAuthError('登录失败,请检查凭证');
|
|
}
|
|
} catch (error) {
|
|
// Error display handled by fetchApi or display specific message
|
|
displayAuthError(`登录失败: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async function handleRegister(event) {
|
|
event?.preventDefault();
|
|
const username = document.getElementById('auth-username').value;
|
|
const password = document.getElementById('auth-password').value;
|
|
authErrorP.style.display = 'none';
|
|
|
|
if (!username || !password) {
|
|
displayAuthError('请输入用户名和密码');
|
|
return;
|
|
}
|
|
if (password.length < 4) { // Example basic validation
|
|
displayAuthError('密码长度至少需要4位');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await fetchApi('/auth/register', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
// Registration successful, now attempt login
|
|
alert('注册成功!请现在登录。');
|
|
// Maybe prefill username?
|
|
document.getElementById('auth-password').value = ''; // Clear password
|
|
// Or automatically log in: await handleLogin();
|
|
|
|
} catch (error) {
|
|
displayAuthError(`注册失败: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
function handleLogout(showLogin = false) {
|
|
jwtToken = null;
|
|
currentUserId = null;
|
|
localStorage.removeItem('jwtToken');
|
|
console.log('Logged out');
|
|
// Reset UI
|
|
milkListContainer.innerHTML = '';
|
|
noMilkMessage.textContent = '请先登录以查看或添加牛奶记录。';
|
|
noMilkMessage.style.display = 'block';
|
|
updateOverview('-- L / -- 盒', '--', '--');
|
|
updateAuthStatus();
|
|
document.getElementById('settings-form').reset(); // Clear settings form
|
|
hideMainContent(); // Hide main buttons/content if logged out
|
|
|
|
if (showLogin) {
|
|
showLoginModal();
|
|
}
|
|
}
|
|
|
|
function updateAuthStatus() {
|
|
if (jwtToken) {
|
|
// Try to get username if needed (decode token client-side - simple way)
|
|
// Or make a quick /api/user/profile call, but might fail if token JUST expired
|
|
let username = '已登录'; // Placeholder
|
|
try {
|
|
// Basic decoding (assumes standard JWT structure) - NOT FOR VERIFICATION
|
|
const payload = JSON.parse(atob(jwtToken.split('.')[1]));
|
|
currentUserId = payload.sub; // 'sub' usually holds the user ID/identity
|
|
// Fetch username from profile API if needed for display
|
|
username = `用户 ${currentUserId}`; // Placeholder until profile loaded
|
|
fetchApi('/user/profile').then(profile => {
|
|
if(profile && profile.username){
|
|
authStatusDiv.textContent = `已登录: ${profile.username}`;
|
|
}
|
|
}).catch(() => {}); // Ignore errors here, keep placeholder
|
|
} catch (e) { console.error("Error decoding token:", e); }
|
|
|
|
authStatusDiv.textContent = username;
|
|
logoutBtn.style.display = 'inline-block';
|
|
showMainContent();
|
|
} else {
|
|
authStatusDiv.textContent = '未登录';
|
|
logoutBtn.style.display = 'none';
|
|
hideMainContent();
|
|
// Optionally show login modal immediately if no token on load
|
|
// showLoginModal();
|
|
}
|
|
}
|
|
|
|
function hideMainContent(){
|
|
// Hide elements that require login
|
|
if(actionsContainer) {
|
|
actionsContainer.style.display = 'none'; // <-- 修改这里,隐藏容器
|
|
}
|
|
// (下面几行也可以删掉)
|
|
// if(addMilkBtn) addMilkBtn.style.display = 'none';
|
|
// if(recordConsumptionBtn) recordConsumptionBtn.style.display = 'none';
|
|
// if(settingsBtn) settingsBtn.style.display = 'none';
|
|
// Could also hide the summary container etc.
|
|
}
|
|
|
|
function showMainContent(){
|
|
// Show elements after login
|
|
if(actionsContainer) {
|
|
actionsContainer.style.display = 'flex'; // <-- 修改这里,显示容器 (用 flex 因为 CSS 里是 flex 布局)
|
|
}
|
|
// if(addMilkBtn) addMilkBtn.style.display = 'inline-block';
|
|
// if(recordConsumptionBtn) recordConsumptionBtn.style.display = 'inline-block';
|
|
// if(settingsBtn) settingsBtn.style.display = 'inline-block';
|
|
}
|
|
|
|
function displayAuthError(message) {
|
|
authErrorP.textContent = message;
|
|
authErrorP.style.display = 'block';
|
|
}
|
|
function displayGlobalError(message){
|
|
// Implement a more robust global error display if needed
|
|
console.error("Global Error:", message);
|
|
alert(`操作失败: ${message}`); // Simple alert for now
|
|
}
|
|
|
|
// --- Core Data Loading & Rendering ---
|
|
async function loadMilkData() {
|
|
console.log("Fetching milk data and overview...");
|
|
if (!jwtToken) {
|
|
console.log("No token found, skipping data load.");
|
|
noMilkMessage.textContent = '请先登录以查看或添加牛奶记录。';
|
|
noMilkMessage.style.display = 'block';
|
|
milkListContainer.innerHTML = '';
|
|
updateOverview('-- L / -- 盒', '--', '--');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Fetch overview and milk list in parallel
|
|
const [overviewData, milkBatches] = await Promise.all([
|
|
fetchApi('/overview'),
|
|
fetchApi('/milk?onlyActive=false') // Get all non-deleted batches initially
|
|
]);
|
|
|
|
milkDataCache = milkBatches || []; // Store fetched data
|
|
|
|
// Update Overview Section
|
|
if (overviewData) {
|
|
const nearest = overviewData.nearestExpiry;
|
|
const nearestStr = nearest
|
|
? `${nearest.note || '牛奶'} - ${nearest.expiryDate} (${nearest.daysRemaining}天)`
|
|
: '无临期牛奶';
|
|
updateOverview(
|
|
`${overviewData.totalQuantityLiters} L / ${overviewData.totalQuantityItemsApprox} 盒`,
|
|
nearestStr,
|
|
overviewData.estimatedFinishDate || '--'
|
|
);
|
|
} else {
|
|
updateOverview('-- L / -- 盒', '--', '--');
|
|
}
|
|
|
|
// Render Milk List
|
|
milkListContainer.innerHTML = ''; // Clear existing list
|
|
if (milkBatches && milkBatches.length > 0) {
|
|
noMilkMessage.style.display = 'none';
|
|
milkBatches.sort((a, b) => new Date(a.expiryDate) - new Date(b.expiryDate)); // Ensure sorted
|
|
milkBatches.forEach(renderMilkCard);
|
|
populateConsumeDropdown(milkBatches.filter(b => b.remainingVolume > 0.001 && b.status !== 'expired')); // Populate with active, non-expired
|
|
} else {
|
|
noMilkMessage.textContent = '还没有添加牛奶记录哦。';
|
|
noMilkMessage.style.display = 'block';
|
|
populateConsumeDropdown([]); // Clear dropdown
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Failed to load initial data:", error);
|
|
noMilkMessage.textContent = '加载牛奶数据失败,请稍后重试。';
|
|
noMilkMessage.style.display = 'block';
|
|
milkListContainer.innerHTML = '';
|
|
updateOverview('错误', '错误', '错误');
|
|
// Error should have been handled by fetchApi, maybe logout occurred
|
|
}
|
|
}
|
|
|
|
function renderMilkCard(milk) {
|
|
const card = document.createElement('article');
|
|
card.className = `milk-card ${milk.status || 'normal'}`; // 'normal', 'warning', 'expired'
|
|
card.dataset.id = milk.id;
|
|
|
|
const progressValue = milk.initialTotalVolume > 0 ? (milk.remainingVolume / milk.initialTotalVolume) * 100 : 0;
|
|
let expiryText;
|
|
if (milk.daysRemaining === undefined || milk.daysRemaining === null){
|
|
expiryText = `过期: ${milk.expiryDate}`;
|
|
} else if (milk.daysRemaining < 0){
|
|
expiryText = `已过期 ${Math.abs(milk.daysRemaining)} 天 (${milk.expiryDate})`;
|
|
} else if (milk.daysRemaining === 0){
|
|
expiryText = `今天过期 (${milk.expiryDate})`;
|
|
} else {
|
|
expiryText = `剩 ${milk.daysRemaining} 天 (${milk.expiryDate})`;
|
|
}
|
|
|
|
|
|
card.innerHTML = `
|
|
<div class="card-header">
|
|
<span class="milk-note">${milk.note || '牛奶'} (${milk.volumePerItem}${milk.volumeUnit})</span>
|
|
<span class="milk-expiry">${expiryText}</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<span class="milk-quantity">剩余: ${milk.remainingVolume.toFixed(1)}${milk.volumeUnit} / ${milk.remainingItemsApprox.toFixed(1)} 盒</span>
|
|
<progress value="${progressValue}" max="100" title="${progressValue.toFixed(0)}%"></progress>
|
|
</div>
|
|
<div class="card-footer">
|
|
<span>生产: ${milk.productionDate || '--'}</span>
|
|
<div>
|
|
<button class="card-action-btn consume-from-card-btn" data-id="${milk.id}" title="记录消耗"${milk.status === 'expired' || milk.remainingVolume < 0.001 ? ' disabled' : ''}>消耗</button>
|
|
<button class="card-action-btn delete-milk-btn" data-id="${milk.id}" title="删除记录">删除</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
milkListContainer.appendChild(card);
|
|
|
|
// Add event listeners for buttons on this card
|
|
const consumeBtn = card.querySelector('.consume-from-card-btn');
|
|
if(consumeBtn){
|
|
consumeBtn.addEventListener('click', (e) => {
|
|
if(!e.target.disabled) {
|
|
openRecordConsumptionModal(milk.id);
|
|
}
|
|
});
|
|
}
|
|
const deleteBtn = card.querySelector('.delete-milk-btn');
|
|
if(deleteBtn){
|
|
deleteBtn.addEventListener('click', () => deleteMilkBatch(milk.id, milk.note));
|
|
}
|
|
}
|
|
|
|
function populateConsumeDropdown(activeBatches) {
|
|
const selectMilk = document.getElementById('select-milk');
|
|
selectMilk.innerHTML = '<option value="" disabled selected>请选择...</option>'; // Clear existing
|
|
|
|
if (activeBatches && activeBatches.length > 0) {
|
|
// Sort by expiry, soonest first
|
|
activeBatches.sort((a, b) => new Date(a.expiryDate) - new Date(b.expiryDate));
|
|
activeBatches.forEach(milk => {
|
|
const option = document.createElement('option');
|
|
option.value = milk.id;
|
|
option.textContent = `${milk.note || '牛奶'} (${milk.volumePerItem}${milk.volumeUnit}) - Exp: ${milk.expiryDate} (剩 ${milk.remainingVolume.toFixed(1)}${milk.volumeUnit})`;
|
|
selectMilk.appendChild(option);
|
|
});
|
|
}
|
|
}
|
|
|
|
function updateOverview(totalQtyStr, nearestExpStr, estimatedFinishStr) {
|
|
document.getElementById('total-quantity').textContent = totalQtyStr;
|
|
document.getElementById('nearest-expiry').textContent = nearestExpStr;
|
|
document.getElementById('estimated-finish').textContent = estimatedFinishStr;
|
|
}
|
|
|
|
// --- Form Handling Functions ---
|
|
|
|
async function handleAddMilkSubmit(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(addMilkForm);
|
|
const data = Object.fromEntries(formData.entries());
|
|
|
|
// Basic validation & data prep
|
|
const payload = {
|
|
productionDate: data.productionDate || null,
|
|
shelfLife: data.shelfLife || null,
|
|
shelfUnit: data.shelfUnit || null,
|
|
expiryDate: data.expiryDate || null,
|
|
quantity: parseInt(data.quantity, 10),
|
|
volumePerItem: parseFloat(data.volumePerItem),
|
|
unit: data.unit,
|
|
note: data.note || null,
|
|
};
|
|
|
|
if (!payload.quantity || !payload.volumePerItem || !payload.unit) {
|
|
alert("请填写数量、单件容量和单位。"); return;
|
|
}
|
|
if (!payload.expiryDate && !(payload.productionDate && payload.shelfLife)) {
|
|
alert("请提供过期日期,或同时提供生产日期和保质期。"); return;
|
|
}
|
|
if (payload.expiryDate && payload.productionDate && payload.shelfLife) {
|
|
// If all 3 provided, maybe prefer expiryDate or warn? Let API handle/prefer expiry.
|
|
payload.productionDate = null; // Example: Clear others if expiryDate is set
|
|
payload.shelfLife = null;
|
|
payload.shelfUnit = null;
|
|
}
|
|
|
|
|
|
console.log("Submitting Add Milk:", payload);
|
|
|
|
try {
|
|
await fetchApi('/milk', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
});
|
|
alert('牛奶入库成功!');
|
|
closeModal(addMilkModal);
|
|
loadMilkData(); // Refresh list and overview
|
|
} catch (error) {
|
|
// Error already logged by fetchApi
|
|
alert(`入库失败: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async function handleRecordConsumptionSubmit(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(recordConsumptionForm);
|
|
const data = Object.fromEntries(formData.entries());
|
|
|
|
const payload = {
|
|
milkBatchId: parseInt(data.milkBatchId, 10),
|
|
amountConsumed: parseFloat(data.amountConsumed),
|
|
unitConsumed: data.unitConsumed,
|
|
dateConsumed: data.dateConsumed ? new Date(data.dateConsumed).toISOString() : new Date().toISOString(),
|
|
};
|
|
|
|
if (!payload.milkBatchId || isNaN(payload.milkBatchId) || !payload.amountConsumed || !payload.unitConsumed) {
|
|
alert("请选择牛奶、填写消耗量和单位。"); return;
|
|
}
|
|
|
|
console.log("Submitting Record Consumption:", payload);
|
|
|
|
try {
|
|
await fetchApi('/consumption', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
});
|
|
alert('消耗记录成功!');
|
|
closeModal(recordConsumptionModal);
|
|
loadMilkData(); // Refresh list and overview
|
|
} catch (error) {
|
|
alert(`记录失败: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async function loadSettings() {
|
|
console.log("Loading user settings...");
|
|
if (!jwtToken) return; // Should not happen if settings button is shown only when logged in
|
|
|
|
try {
|
|
const profile = await fetchApi('/user/profile');
|
|
if (profile) {
|
|
document.getElementById('expiry-warning-days').value = profile.expiryWarningDays ?? 2;
|
|
document.getElementById('low-stock-threshold').value = profile.lowStockThresholdValue ?? '';
|
|
document.getElementById('low-stock-unit').value = profile.lowStockThresholdUnit ?? 'ml';
|
|
|
|
const notifications = profile.notificationsEnabled || {};
|
|
document.querySelector('input[name="enableExpiryWarning"]').checked = notifications.expiry ?? true;
|
|
document.querySelector('input[name="enableExpiredAlert"]').checked = notifications.expired ?? true;
|
|
document.querySelector('input[name="enableLowStock"]').checked = notifications.lowStock ?? true;
|
|
|
|
// Update auth status with correct username if not already done
|
|
if (profile.username && authStatusDiv.textContent.startsWith('用户')) {
|
|
authStatusDiv.textContent = `已登录: ${profile.username}`;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load settings:", error);
|
|
alert('加载设置失败。');
|
|
// Don't switch page if load fails
|
|
showPage('dashboard');
|
|
}
|
|
}
|
|
|
|
|
|
async function handleSettingsSubmit(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(settingsForm);
|
|
|
|
const payload = {
|
|
expiryWarningDays: parseInt(formData.get('expiryWarningDays'), 10) || 2,
|
|
lowStockThresholdValue: formData.get('lowStockThresholdValue') ? parseFloat(formData.get('lowStockThresholdValue')) : null,
|
|
lowStockThresholdUnit: formData.get('lowStockThresholdValue') ? formData.get('lowStockThresholdUnit') : null, // Only set unit if value exists
|
|
notificationsEnabled: {
|
|
expiry: formData.has('enableExpiryWarning'),
|
|
expired: formData.has('enableExpiredAlert'), // Match key used in User.to_dict
|
|
lowStock: formData.has('enableLowStock'),
|
|
}
|
|
};
|
|
// Basic validation for threshold
|
|
if(payload.lowStockThresholdValue !== null && payload.lowStockThresholdValue < 0){
|
|
alert("低库存阈值不能为负数。"); return;
|
|
}
|
|
if(payload.lowStockThresholdValue !== null && !payload.lowStockThresholdUnit){
|
|
alert("设置低库存阈值时请选择单位。"); return;
|
|
}
|
|
|
|
|
|
console.log("Saving Settings:", payload);
|
|
|
|
try {
|
|
await fetchApi('/user/profile', {
|
|
method: 'PUT',
|
|
body: JSON.stringify(payload),
|
|
});
|
|
alert('设置已保存!');
|
|
showPage('dashboard'); // Go back to dashboard
|
|
} catch (error) {
|
|
alert(`保存设置失败: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async function deleteMilkBatch(batchId, milkNote) {
|
|
const note = milkNote || `ID ${batchId}`;
|
|
if (confirm(`确定要删除这条牛奶记录吗?\n(${note})`)) {
|
|
console.log("Deleting milk batch:", batchId);
|
|
try {
|
|
await fetchApi(`/milk/${batchId}`, { method: 'DELETE' });
|
|
alert('记录已删除。');
|
|
loadMilkData(); // Refresh the list
|
|
} catch (error) {
|
|
alert(`删除失败: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// --- Modal Control Functions ---
|
|
function openModal(modalElement) {
|
|
if (modalElement) modalElement.style.display = 'block';
|
|
}
|
|
|
|
function closeModal(modalElement) {
|
|
if (modalElement) modalElement.style.display = 'none';
|
|
}
|
|
|
|
function openRecordConsumptionModal(preselectedMilkId = null) {
|
|
// Refresh dropdown with currently active batches from cache
|
|
const activeBatches = milkDataCache.filter(b => !b.isDeleted && b.remainingVolume > 0.001 && b.status !== 'expired');
|
|
populateConsumeDropdown(activeBatches);
|
|
|
|
const selectMilk = document.getElementById('select-milk');
|
|
if (preselectedMilkId && selectMilk) {
|
|
selectMilk.value = preselectedMilkId; // Try to pre-select
|
|
} else if (selectMilk && selectMilk.options.length > 1) {
|
|
selectMilk.selectedIndex = 1; // Select the first available milk (soonest expiry) if none preselected
|
|
}
|
|
|
|
// Reset form fields
|
|
recordConsumptionForm.reset();
|
|
consumeDateInput.value = new Date().toISOString().split('T')[0]; // Reset date to today
|
|
openModal(recordConsumptionModal);
|
|
}
|
|
|
|
// --- Page Navigation ---
|
|
function showPage(pageId) {
|
|
document.querySelectorAll('.page').forEach(page => page.style.display = 'none');
|
|
dashboardPage.style.display = 'none';
|
|
|
|
const pageToShow = document.getElementById(pageId);
|
|
if (pageToShow) {
|
|
// If settings page, load settings first
|
|
if (pageId === 'settings-page') {
|
|
loadSettings(); // Load settings when showing the page
|
|
}
|
|
pageToShow.style.display = 'block';
|
|
} else {
|
|
dashboardPage.style.display = 'block'; // Default to dashboard
|
|
}
|
|
}
|
|
|
|
|
|
// --- Event Listeners ---
|
|
if (addMilkBtn) {
|
|
addMilkBtn.addEventListener('click', () => {
|
|
addMilkForm.reset(); // Clear form
|
|
openModal(addMilkModal);
|
|
});
|
|
}
|
|
if (recordConsumptionBtn) {
|
|
recordConsumptionBtn.addEventListener('click', () => openRecordConsumptionModal());
|
|
}
|
|
if (settingsBtn) {
|
|
settingsBtn.addEventListener('click', () => showPage('settings-page'));
|
|
}
|
|
if (backToDashboardBtn) {
|
|
backToDashboardBtn.addEventListener('click', () => showPage('dashboard'));
|
|
}
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener('click', () => handleLogout(true)); // Show login after logout
|
|
}
|
|
|
|
// Auth Modal Buttons
|
|
if (loginBtn) loginBtn.addEventListener('click', handleLogin);
|
|
if (registerBtn) registerBtn.addEventListener('click', handleRegister);
|
|
|
|
|
|
// Close Modal Buttons
|
|
if (closeAddModalBtn) closeAddModalBtn.addEventListener('click', () => closeModal(addMilkModal));
|
|
if (closeRecordModalBtn) closeRecordModalBtn.addEventListener('click', () => closeModal(recordConsumptionModal));
|
|
if (closeAuthModalBtn) closeAuthModalBtn.addEventListener('click', () => closeModal(authModal));
|
|
|
|
|
|
// Close Modal on outside click
|
|
window.addEventListener('click', (event) => {
|
|
if (event.target == addMilkModal) closeModal(addMilkModal);
|
|
if (event.target == recordConsumptionModal) closeModal(recordConsumptionModal);
|
|
if (event.target == authModal) closeModal(authModal);
|
|
});
|
|
|
|
// Form Submissions
|
|
if (addMilkForm) addMilkForm.addEventListener('submit', handleAddMilkSubmit);
|
|
if (recordConsumptionForm) recordConsumptionForm.addEventListener('submit', handleRecordConsumptionSubmit);
|
|
if (settingsForm) settingsForm.addEventListener('submit', handleSettingsSubmit);
|
|
|
|
|
|
// Quick Consume Buttons Logic
|
|
document.querySelectorAll('.consume-preset').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const amount = this.dataset.amount;
|
|
const unit = this.dataset.unit;
|
|
document.getElementById('consume-amount').value = amount;
|
|
document.getElementById('consume-unit').value = unit;
|
|
});
|
|
});
|
|
|
|
// --- Initial App Load ---
|
|
function initializeApp(){
|
|
console.log("Initializing app...");
|
|
updateAuthStatus();
|
|
if(jwtToken){
|
|
showPage('dashboard'); // Show dashboard if logged in
|
|
loadMilkData(); // Load data if logged in
|
|
} else {
|
|
// Optionally show login immediately on load if not logged in
|
|
showLoginModal();
|
|
hideMainContent();
|
|
noMilkMessage.textContent = '请先登录以查看或添加牛奶记录。';
|
|
noMilkMessage.style.display = 'block';
|
|
}
|
|
// Set current year in footer
|
|
document.getElementById('current-year').textContent = new Date().getFullYear();
|
|
// Set default consume date
|
|
consumeDateInput.value = new Date().toISOString().split('T')[0];
|
|
}
|
|
|
|
initializeApp(); // Start the app
|
|
|
|
}); // End DOMContentLoaded
|