chore: move js to single file
This commit is contained in:
+3
-132
@@ -231,138 +231,9 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="assets/js/nav.js"></script>
|
||||
<script>
|
||||
function esc(str) {
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
async function openApiKeyModal() {
|
||||
document.getElementById('apiKeyInput').value = '';
|
||||
document.getElementById('apiKeyError').textContent = '';
|
||||
const statusEl = document.getElementById('apiKeyStatus');
|
||||
statusEl.textContent = 'Checking…';
|
||||
try {
|
||||
const [keyData, costData] = await Promise.all([
|
||||
fetch('/api/settings.php?key=clickatell_api_key').then(r => r.json()),
|
||||
fetch('/api/settings.php?key=clickatell_cost_per_sms').then(r => r.json()),
|
||||
]);
|
||||
statusEl.innerHTML = keyData.set
|
||||
? `API key set: <code>${esc(keyData.hint)}</code> — enter a new key to replace it.`
|
||||
: '<span class="text-warning">No API key configured.</span> Enter one below to enable SMS sending.';
|
||||
document.getElementById('costPerSmsInput').value = costData.value ?? '0.04849';
|
||||
} catch {
|
||||
statusEl.textContent = 'Could not load current settings.';
|
||||
}
|
||||
}
|
||||
|
||||
async function saveApiKey() {
|
||||
const key = document.getElementById('apiKeyInput').value.trim();
|
||||
const cost = document.getElementById('costPerSmsInput').value.trim();
|
||||
const errEl = document.getElementById('apiKeyError');
|
||||
const btn = document.getElementById('apiKeySaveBtn');
|
||||
errEl.textContent = '';
|
||||
if (!key && !cost) { errEl.textContent = 'Please enter at least one value.'; return; }
|
||||
const costNum = parseFloat(cost);
|
||||
if (cost && (isNaN(costNum) || costNum < 0)) { errEl.textContent = 'Cost must be a positive number.'; return; }
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const saves = [];
|
||||
if (key) saves.push(fetch('/api/settings.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: 'clickatell_api_key', value: key }) }));
|
||||
if (cost) saves.push(fetch('/api/settings.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: 'clickatell_cost_per_sms', value: cost }) }));
|
||||
const results = await Promise.all(saves);
|
||||
for (const res of results) { if (!res.ok) throw new Error((await res.json()).error ?? 'Save failed'); }
|
||||
bootstrap.Modal.getInstance(document.getElementById('apiKeyModal')).hide();
|
||||
loadStats();
|
||||
} catch (e) {
|
||||
errEl.textContent = e.message;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
let spendView = 'balance';
|
||||
let spendData = { balance: null, currency: null, estimatedCost: null };
|
||||
|
||||
function formatCurrency(amount, currency) {
|
||||
if (amount == null || Number.isNaN(Number(amount))) return '—';
|
||||
const sym = currency === 'GBP' ? '£' : currency === 'USD' ? '$' : currency === 'EUR' ? '€' : (currency ?? '');
|
||||
return sym + Number(amount).toFixed(2);
|
||||
}
|
||||
|
||||
function renderSpendCard() {
|
||||
const labelEl = document.getElementById('stat-spend-label');
|
||||
const valueEl = document.getElementById('stat-spend');
|
||||
if (!labelEl || !valueEl) return;
|
||||
|
||||
if (spendView === 'cost') {
|
||||
labelEl.textContent = 'EST. COST';
|
||||
valueEl.textContent = formatCurrency(spendData.estimatedCost, spendData.currency);
|
||||
return;
|
||||
}
|
||||
|
||||
labelEl.textContent = 'Balance';
|
||||
valueEl.textContent = formatCurrency(spendData.balance, spendData.currency);
|
||||
}
|
||||
|
||||
function toggleSpendCard() {
|
||||
spendView = spendView === 'balance' ? 'cost' : 'balance';
|
||||
renderSpendCard();
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const [att, log, bal, cost, admin] = await Promise.all([
|
||||
fetch('/api/attendees.php').then(r => r.json()),
|
||||
fetch('/api/delivery-log.php').then(r => r.json()),
|
||||
fetch('/api/balance.php').then(r => r.json()),
|
||||
fetch('/api/settings.php?key=clickatell_cost_per_sms').then(r => r.json()),
|
||||
fetch('/api/admin.php').then(r => r.json()),
|
||||
]);
|
||||
document.getElementById('stat-attendees').textContent = att.total ?? '—';
|
||||
document.getElementById('stat-sent').textContent = log.stats?.total_sent ?? '—';
|
||||
const rate = log.stats?.success_rate;
|
||||
document.getElementById('stat-rate').textContent = rate != null ? rate + '%' : '—';
|
||||
spendData.balance = bal.balance != null ? Number(bal.balance) : null;
|
||||
spendData.currency = bal.currency ?? 'GBP';
|
||||
|
||||
const realSent = Number(admin.delivery_log_real_sent);
|
||||
const costPerSms = Number(cost.value);
|
||||
spendData.estimatedCost = Number.isFinite(realSent) && Number.isFinite(costPerSms)
|
||||
? realSent * costPerSms
|
||||
: null;
|
||||
|
||||
renderSpendCard();
|
||||
} catch { /* DB unavailable — stats stay as dashes */ }
|
||||
}
|
||||
document.getElementById('spendCard')?.addEventListener('click', toggleSpendCard);
|
||||
document.getElementById('spendCard')?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
toggleSpendCard();
|
||||
}
|
||||
});
|
||||
loadStats();
|
||||
setInterval(() => loadStats(), 10_000);
|
||||
</script>
|
||||
<script>
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const r = await fetch('/api/admin.php', { redirect: 'manual' });
|
||||
if (r.type === 'opaqueredirect' || !r.ok) { window.location.reload(); return; }
|
||||
const d = await r.json();
|
||||
document.getElementById('sandboxBanner').style.display = d.sandbox_mode ? '' : 'none';
|
||||
document.querySelectorAll('.admin-nav-link').forEach(el => el.classList.toggle('d-none', !d.is_admin));
|
||||
const adminSection = document.getElementById('adminSection');
|
||||
if (adminSection) adminSection.classList.toggle('d-none', !d.is_admin);
|
||||
const name = d.name || d.username;
|
||||
if (name) {
|
||||
document.querySelectorAll('.nav-username').forEach(el => { el.textContent = name; });
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
checkAuth();
|
||||
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') checkAuth(); });
|
||||
</script>
|
||||
<script src="assets/js/app.js"></script>
|
||||
|
||||
|
||||
<nav class="bottom-nav" aria-label="Navigation">
|
||||
<a href="index.html" class="bottom-nav-item active">
|
||||
<i class="fa-solid fa-house"></i>
|
||||
|
||||
Reference in New Issue
Block a user