| ${esc(m.member_number)} |
${esc(m.name)} |
${esc(m.balance_display)} |
${m.created_at ? m.created_at.slice(0, 10) : ''} |
Statement
${m.balance === 0
? ``
: ''}
|
`).join('');
}
// Edit member modal
function openEditModal(id, name, number, overdraftOverride) {
editMemberId = id;
document.getElementById('edit-number').value = number;
document.getElementById('edit-name').value = name;
document.getElementById('edit-pin').value = '';
setMsg('editMsg', '', '');
const policy = cfg.overdraft_policy || 'never';
const overrideRow = document.getElementById('editOverdraftRow');
const overrideCheck = document.getElementById('edit-overdraft');
const overrideLabel = document.getElementById('editOverdraftLabel');
if (policy === 'staff_override' || (policy === 'admin_override' && currentUser.role === 'admin')) {
overrideLabel.textContent = 'Allow overdraft for this member';
overrideCheck.checked = (overdraftOverride === 1);
overrideRow.classList.remove('hidden');
} else if (policy === 'staff_block') {
overrideLabel.textContent = 'Block overdraft for this member';
overrideCheck.checked = (overdraftOverride === 0);
overrideRow.classList.remove('hidden');
} else {
overrideRow.classList.add('hidden');
}
document.getElementById('editModal').classList.remove('hidden');
document.getElementById('edit-name').focus();
}
function closeEditModal() {
editMemberId = null;
document.getElementById('editModal').classList.add('hidden');
}
async function saveEdit() {
if (!editMemberId) return;
const body = {
member_number: document.getElementById('edit-number').value.trim(),
name: document.getElementById('edit-name').value.trim(),
};
const pin = document.getElementById('edit-pin').value;
if (pin) body.pin = pin;
const policy = cfg.overdraft_policy || 'never';
const overrideRow = document.getElementById('editOverdraftRow');
if (!overrideRow.classList.contains('hidden')) {
const checked = document.getElementById('edit-overdraft').checked;
if (policy === 'staff_block') {
body.overdraft_override = checked ? 0 : null;
} else {
body.overdraft_override = checked ? 1 : null;
}
}
try {
await apiFetch(`/members/${editMemberId}`, {
method: 'PUT', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
closeEditModal();
searchMembers();
} catch (err) { setMsg('editMsg', err.message, 'err'); }
}
async function deleteMember(id, name) {
if (!confirm(`Delete member "${name}"?\n\nThis permanently removes their account and transaction history.`)) return;
try {
await apiFetch(`/members/${id}`, { method: 'DELETE' });
searchMembers();
} catch (err) { alert(err.message); }
}
// ---------------------------------------------------------------------------
// Cashier stats widgets
// ---------------------------------------------------------------------------
function onStatsPeriodChange() {
const period = document.getElementById('statsPeriod').value;
const custom = document.getElementById('statsCustomRange');
if (period === 'custom') {
custom.classList.remove('hidden');
// default custom range to current month
const today = new Date();
const y = today.getFullYear(), m = String(today.getMonth()+1).padStart(2,'0');
if (!document.getElementById('statsFrom').value)
document.getElementById('statsFrom').value = `${y}-${m}-01`;
if (!document.getElementById('statsTo').value)
document.getElementById('statsTo').value = today.toISOString().slice(0,10);
} else {
custom.classList.add('hidden');
loadCashierStats();
}
}
async function loadCashierStats() {
const period = document.getElementById('statsPeriod').value;
let url = `/cashier/stats?period=${period}`;
if (period === 'custom') {
const from = document.getElementById('statsFrom').value;
const to = document.getElementById('statsTo').value;
if (!from || !to) return;
url += `&from_date=${from}&to_date=${to}`;
}
try {
const d = await apiFetch(url);
document.getElementById('statCredit').textContent = d.outstanding_credit_display;
const setCol = (valId, cntId, stat, cls) => {
const el = document.getElementById(valId);
el.textContent = stat.display;
el.className = 'stats-col-value' + (cls ? ' ' + cls : '');
if (cntId) document.getElementById(cntId).textContent =
stat.count === 1 ? '1 transaction' : `${stat.count} transactions`;
};
setCol('statsTopups', 'statsTopupsCount', d.topups, 'stats-positive');
setCol('statsWithdrawals', 'statsWithdrawalsCount', d.withdrawals, 'stats-negative');
setCol('statsCharges', 'statsChargesCount', d.charges, 'stats-negative');
const netEl = document.getElementById('statsNet');
netEl.textContent = (d.net.negative ? '−' : '+') + d.net.display;
netEl.className = 'stats-col-value ' + (d.net.negative ? 'stats-negative' : 'stats-positive');
} catch (e) { /* silently ignore — widgets are non-critical */ }
}
// ---------------------------------------------------------------------------
// Cashier view
// ---------------------------------------------------------------------------
async function cashierSearchMembers() {
const q = document.getElementById('cashierSearch').value.trim();
try {
const members = await apiFetch(q ? `/members?q=${encodeURIComponent(q)}` : '/members');
document.getElementById('cashierMemberList').innerHTML = members.map(m => `