Files
yolkbook/nginx/html/js/flock.js
derekc ce1e9c5134 Implement reliability improvements across frontend
- api.js: add exponential backoff retry (3 attempts, 500/1000/2000ms) for
  GET requests on network errors and 5xx responses; mutating methods are
  not retried since they are not idempotent
- api.js: add offline indicator — fixed pill banner appears at bottom of
  page when navigator goes offline, disappears when back online
- style.css: add styles for offline banner and session expiry warning
- auth.js: show amber warning banner below nav when session expires within
  24 hours (with exact hours remaining); dismissible with X button
- auth.js: fix password min-length client-side check from 6 to 10 to
  match the backend
- log.js, flock.js, budget.js: disable submit button during async request
  and re-enable in finally block to prevent double-submits and make loading
  state visible
- dashboard.js: fix chart date labels to use user's configured timezone
  instead of the browser's local timezone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:09:36 -07:00

124 lines
4.1 KiB
JavaScript

let flockData = [];
async function loadFlock() {
const msg = document.getElementById('msg');
try {
const [current, history] = await Promise.all([
API.get('/api/flock/current'),
API.get('/api/flock'),
]);
document.getElementById('current-count').textContent = current?.chicken_count ?? '—';
document.getElementById('current-date').textContent = current ? fmtDate(current.date) : 'No data';
flockData = history;
renderTable();
} catch (err) {
showMessage(msg, `Failed to load flock data: ${err.message}`, 'error');
}
}
function renderTable() {
const tbody = document.getElementById('flock-body');
if (flockData.length === 0) {
tbody.innerHTML = '<tr class="empty-row"><td colspan="4">No flock history yet.</td></tr>';
return;
}
tbody.innerHTML = flockData.map(e => `
<tr data-id="${e.id}">
<td>${fmtDate(e.date)}</td>
<td>${e.chicken_count}</td>
<td class="notes">${escHtml(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('');
}
function startEdit(id) {
const entry = flockData.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" value="${entry.chicken_count}" style="width:80px;"></td>
<td><input type="text" value="${escHtml(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 [dateInput, countInput, notesInput] = row.querySelectorAll('input');
try {
const updated = await API.put(`/api/flock/${id}`, {
date: dateInput.value,
chicken_count: parseInt(countInput.value, 10),
notes: notesInput.value.trim() || null,
});
const idx = flockData.findIndex(e => e.id === id);
flockData[idx] = updated;
renderTable();
loadFlock(); // refresh current-flock callout
showMessage(msg, 'Entry updated.');
} catch (err) {
showMessage(msg, `Error: ${err.message}`, 'error');
}
}
async function deleteEntry(id) {
if (!confirm('Delete this flock entry?')) return;
const msg = document.getElementById('msg');
try {
await API.del(`/api/flock/${id}`);
flockData = flockData.filter(e => e.id !== id);
renderTable();
loadFlock();
showMessage(msg, 'Entry deleted.');
} catch (err) {
showMessage(msg, `Error: ${err.message}`, 'error');
}
}
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('flock-form');
const msg = document.getElementById('msg');
setToday(document.getElementById('date'));
form.addEventListener('submit', async (e) => {
e.preventDefault();
const btn = form.querySelector('[type=submit]');
const data = {
date: document.getElementById('date').value,
chicken_count: parseInt(document.getElementById('count').value, 10),
notes: document.getElementById('notes').value.trim() || null,
};
btn.disabled = true;
try {
await API.post('/api/flock', data);
showMessage(msg, 'Flock change saved!');
form.reset();
setToday(document.getElementById('date'));
loadFlock();
} catch (err) {
showMessage(msg, `Error: ${err.message}`, 'error');
} finally {
btn.disabled = false;
}
});
loadFlock();
});