Files
eggtracker/nginx/html/js/budget.js
derekc 492e1fd68f Initial commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 22:27:58 -08:00

164 lines
6.1 KiB
JavaScript

let feedData = [];
async function loadBudget() {
const msg = document.getElementById('msg');
try {
const [stats, purchases] = await Promise.all([
API.get('/api/stats/budget'),
API.get('/api/feed'),
]);
// All-time stats
document.getElementById('b-cost-total').textContent = fmtMoney(stats.total_feed_cost);
document.getElementById('b-eggs-total').textContent = stats.total_eggs_alltime;
document.getElementById('b-cpe').textContent = fmtMoneyFull(stats.cost_per_egg);
document.getElementById('b-cpd').textContent = fmtMoney(stats.cost_per_dozen);
// Last 30 days
document.getElementById('b-cost-30d').textContent = fmtMoney(stats.total_feed_cost_30d);
document.getElementById('b-eggs-30d').textContent = stats.total_eggs_30d;
document.getElementById('b-cpe-30d').textContent = fmtMoneyFull(stats.cost_per_egg_30d);
document.getElementById('b-cpd-30d').textContent = fmtMoney(stats.cost_per_dozen_30d);
feedData = purchases;
renderTable();
} catch (err) {
showMessage(msg, `Failed to load budget data: ${err.message}`, 'error');
}
}
function renderTable() {
const tbody = document.getElementById('feed-body');
const tfoot = document.getElementById('feed-foot');
if (feedData.length === 0) {
tbody.innerHTML = '<tr class="empty-row"><td colspan="6">No feed purchases logged yet.</td></tr>';
tfoot.innerHTML = '';
return;
}
tbody.innerHTML = feedData.map(e => {
const total = (parseFloat(e.bags) * parseFloat(e.price_per_bag)).toFixed(2);
return `
<tr data-id="${e.id}">
<td>${fmtDate(e.date)}</td>
<td>${parseFloat(e.bags)}</td>
<td>${fmtMoney(e.price_per_bag)}</td>
<td>${fmtMoney(total)}</td>
<td class="notes">${e.notes || ''}</td>
<td class="actions">
<button class="btn btn-ghost btn-sm" onclick="startEdit(${e.id})">Edit</button>
<button class="btn btn-danger btn-sm" onclick="deleteEntry(${e.id})">Delete</button>
</td>
</tr>
`;
}).join('');
// Total row
const grandTotal = feedData.reduce((sum, e) => sum + parseFloat(e.bags) * parseFloat(e.price_per_bag), 0);
tfoot.innerHTML = `
<tr class="total-row">
<td colspan="3">Total</td>
<td>${fmtMoney(grandTotal)}</td>
<td colspan="2">${feedData.length} purchases</td>
</tr>
`;
}
function startEdit(id) {
const entry = feedData.find(e => e.id === id);
const row = document.querySelector(`tr[data-id="${id}"]`);
row.innerHTML = `
<td><input type="date" value="${entry.date}"></td>
<td><input type="number" min="0.01" step="0.01" value="${parseFloat(entry.bags)}" style="width:80px;"></td>
<td><input type="number" min="0.01" step="0.01" value="${parseFloat(entry.price_per_bag)}" style="width:90px;"></td>
<td>—</td>
<td><input type="text" value="${entry.notes || ''}" placeholder="Notes"></td>
<td class="actions">
<button class="btn btn-primary btn-sm" onclick="saveEdit(${id})">Save</button>
<button class="btn btn-ghost btn-sm" onclick="renderTable()">Cancel</button>
</td>
`;
}
async function saveEdit(id) {
const msg = document.getElementById('msg');
const row = document.querySelector(`tr[data-id="${id}"]`);
const inputs = row.querySelectorAll('input');
const [dateInput, bagsInput, priceInput, notesInput] = inputs;
try {
const updated = await API.put(`/api/feed/${id}`, {
date: dateInput.value,
bags: parseFloat(bagsInput.value),
price_per_bag: parseFloat(priceInput.value),
notes: notesInput.value.trim() || null,
});
const idx = feedData.findIndex(e => e.id === id);
feedData[idx] = updated;
renderTable();
loadBudget();
showMessage(msg, 'Purchase updated.');
} catch (err) {
showMessage(msg, `Error: ${err.message}`, 'error');
}
}
async function deleteEntry(id) {
if (!confirm('Delete this purchase?')) return;
const msg = document.getElementById('msg');
try {
await API.del(`/api/feed/${id}`);
feedData = feedData.filter(e => e.id !== id);
renderTable();
loadBudget();
showMessage(msg, 'Purchase deleted.');
} catch (err) {
showMessage(msg, `Error: ${err.message}`, 'error');
}
}
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('feed-form');
const msg = document.getElementById('msg');
const bagsInput = document.getElementById('bags');
const priceInput = document.getElementById('price');
const totalDisplay = document.getElementById('total-display');
setToday(document.getElementById('date'));
// Live total calculation
function updateTotal() {
const bags = parseFloat(bagsInput.value) || 0;
const price = parseFloat(priceInput.value) || 0;
totalDisplay.value = bags && price ? fmtMoney(bags * price) : '';
}
bagsInput.addEventListener('input', updateTotal);
priceInput.addEventListener('input', updateTotal);
form.addEventListener('submit', async (e) => {
e.preventDefault();
const data = {
date: document.getElementById('date').value,
bags: parseFloat(bagsInput.value),
price_per_bag: parseFloat(priceInput.value),
notes: document.getElementById('notes').value.trim() || null,
};
try {
await API.post('/api/feed', data);
showMessage(msg, 'Purchase saved!');
form.reset();
totalDisplay.value = '';
setToday(document.getElementById('date'));
loadBudget();
} catch (err) {
showMessage(msg, `Error: ${err.message}`, 'error');
}
});
loadBudget();
});