Visitor sign in kiosk: multi-site, badge printing, WWCC expiry warnings, admin accounts with 2FA

This commit is contained in:
2026-09-01 16:08:27 +10:00
parent cccba98d97
commit c21123885d
7 changed files with 211 additions and 65 deletions
+55 -1
View File
@@ -268,6 +268,7 @@ async function loadRecurring() {
<button class="ghost" data-pass="${p.id}">Print card</button>
<button class="ghost" data-pin="${p.id}">New PIN</button>
<button class="ghost" data-edit-freq="${p.id}">Edit</button>
<button class="ghost danger" data-remove-freq="${p.id}">Remove</button>
</td>
</tr>`
),
@@ -289,6 +290,15 @@ async function loadRecurring() {
openRecurringModal(await api(`/frequent/${btn.dataset.editFreq}`));
})
);
$$('[data-remove-freq]').forEach((btn) =>
btn.addEventListener('click', async () => {
try {
confirmRemoveFrequent(await api(`/frequent/${btn.dataset.removeFreq}`));
} catch (err) {
toast(err.message, true);
}
})
);
}
function hostOptions(selectedId) {
@@ -316,18 +326,25 @@ function siteOptions(selectedId, { anyLabel = 'Any site' } = {}) {
);
}
function openModal(title, bodyHtml, onSave, { saveLabel = 'Save', hideCancel = false, onOpen, onClose } = {}) {
function openModal(
title,
bodyHtml,
onSave,
{ saveLabel = 'Save', hideCancel = false, destructive = false, onOpen, onClose } = {}
) {
$('#modal-title').textContent = title;
$('#modal-body').innerHTML = bodyHtml;
$('#modal-save').textContent = saveLabel;
$('#modal-cancel').hidden = hideCancel;
const modal = $('#modal');
modal.classList.toggle('destructive', destructive);
modal.returnValue = '';
modal.showModal();
onOpen?.();
modal.onclose = () => {
$('#modal-cancel').hidden = false;
$('#modal-save').textContent = 'Save';
modal.classList.remove('destructive');
onClose?.();
if (modal.returnValue === 'save') onSave?.(new FormData($('#modal-form')));
};
@@ -494,6 +511,43 @@ function openRecurringModal(person = null) {
);
}
/**
* Removing a saved visitor is permanent, so the confirmation spells out what
* happens: the record and its PIN go, the visit history stays. Deactivating is
* offered alongside for the common case of someone who has simply stopped coming.
*/
function confirmRemoveFrequent(person) {
const name = `${person.firstName} ${person.lastName}`;
openModal(
`Remove ${name}?`,
`${
person.onSite
? `<p class="notice">${esc(person.firstName)} is signed in right now. Removing the
record will not sign them out — their visit stays open and they can still sign out
with their last name and mobile number.</p>`
: ''
}
<ul class="plain-list">
<li>Their saved record, PIN and photo are deleted for good.</li>
<li>Their ${person.visitCount} past ${person.visitCount === 1 ? 'visit stays' : 'visits stay'} in the visit log.</li>
<li>${esc(person.phone)}${person.email ? ` and ${esc(person.email)}` : ''} become available for someone else.</li>
<li>Any card they are carrying stops working.</li>
</ul>
<p class="hint">If they might come back, untick <strong>Active</strong> in Edit instead —
that keeps the record and their history intact.</p>`,
async () => {
try {
const result = await api(`/frequent/${person.id}?force=1`, { method: 'DELETE' });
toast(`${result.name} removed. ${result.visitsKept} past visit(s) kept in the log.`);
loadRecurring();
} catch (err) {
toast(err.message, true);
}
},
{ saveLabel: 'Remove permanently', destructive: true }
);
}
function showPin(pin, id) {
openModal(
'PIN issued',