Add NFC chore submission feature and enhance family settings

- Introduced NFC support for chore submissions, allowing specific person credit after Head of Household approval.
- Updated family settings to include NFC base URL, scan cooldown, and confirmation page options.
- Enhanced chore management with options for anyone to complete chores and NFC link generation.
- Improved API endpoints for handling NFC tokens and chore submissions.
- Updated readme to reflect new NFC features and settings.
This commit is contained in:
2026-03-31 10:28:27 -05:00
parent 9a78c4564e
commit f14de0b7e1
21 changed files with 779 additions and 21 deletions
+120 -1
View File
@@ -36,6 +36,15 @@
$el.text(message);
}
function escapeHtml(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
var pendingPersonId = null;
var pinModal = null;
@@ -179,7 +188,10 @@
currency_symbol: $('#currency_symbol').val(),
currency_name: $('#currency_name').val(),
currency_permanence: $('#currency_permanence').val(),
week_starts_on: parseInt($('#week_starts_on').val(), 10)
week_starts_on: parseInt($('#week_starts_on').val(), 10),
nfc_base_url: ($('#nfc_base_url').val() || '').trim(),
nfc_show_confirmation: $('#nfc_show_confirmation').is(':checked'),
nfc_scan_cooldown_seconds: parseInt($('#nfc_scan_cooldown_seconds').val(), 10) || 0
};
postJson('/family_settings_save.php', payload)
.then(function () {
@@ -191,6 +203,36 @@
$('#familySettingsFeedback').removeClass('d-none');
});
});
$('#btnDisableAllChoreNfc').on('click', function () {
if (!window.confirm('Disable NFC links for all chores?')) {
return;
}
postJson('/family_nfc_admin.php', { action: 'disable_all_chore_nfc' })
.then(function () {
showAlert($('#familySettingsFeedback'), 'warning', 'All chore NFC links disabled.');
$('#familySettingsFeedback').removeClass('d-none');
})
.catch(function (err) {
showAlert($('#familySettingsFeedback'), 'danger', err.message || 'Could not disable NFC links');
$('#familySettingsFeedback').removeClass('d-none');
});
});
$('#btnRotateAllPersonNfcTokens').on('click', function () {
if (!window.confirm('Rotate NFC submitter tokens for all people? Existing person-token links will stop working.')) {
return;
}
postJson('/family_nfc_admin.php', { action: 'rotate_all_person_tokens' })
.then(function () {
showAlert($('#familySettingsFeedback'), 'success', 'All person NFC tokens rotated.');
$('#familySettingsFeedback').removeClass('d-none');
})
.catch(function (err) {
showAlert($('#familySettingsFeedback'), 'danger', err.message || 'Could not rotate tokens');
$('#familySettingsFeedback').removeClass('d-none');
});
});
}
function collectBillDaysFromDom() {
@@ -285,6 +327,7 @@
schedule: $('#chore_schedule').val(),
recurrence_days: parseInt($('#chore_recurrence_days').val(), 10) || 7,
assignee_ids: assignees,
anyone_can_complete: $('#chore_anyone_can_complete').is(':checked'),
lists: choreListPayloadFromForm()
};
postJson('/chore_save.php', payload)
@@ -353,6 +396,67 @@
window.alert(err.message || 'Could not reject');
});
});
$(document).on('click', '.btn-chore-nfc-generate', function () {
var id = $(this).data('id');
if (!id) {
return;
}
postJson('/chore_nfc_rotate.php', { id: id })
.then(function (data) {
var links = Array.isArray(data.links) ? data.links : [];
var html = '<div class="small text-muted mb-1"><strong>NFC links (copy per person)</strong></div>';
if (!links.length) {
html += '<div class="small text-muted">No links were returned.</div>';
} else {
links.forEach(function (row) {
html += '<label class="form-label small mb-1">' + escapeHtml(row.person_name || row.person_id || 'Person') + '</label>';
html += '<div class="input-group input-group-sm mb-2">';
html += '<input type="text" class="form-control chore-nfc-link-input" readonly value="' + escapeHtml(row.url || '') + '">';
html += '<button class="btn btn-outline-secondary btn-copy-nfc-link" type="button">Copy</button>';
html += '</div>';
});
}
var $links = $('#chore_nfc_links_' + id);
$links.html(html).removeClass('d-none');
showAlert($('#chore_nfc_feedback_' + id), 'success', 'Generated new NFC links.');
$('#chore_nfc_feedback_' + id).removeClass('d-none');
})
.catch(function (err) {
showAlert($('#chore_nfc_feedback_' + id), 'danger', err.message || 'Could not generate links');
$('#chore_nfc_feedback_' + id).removeClass('d-none');
});
});
$(document).on('click', '.btn-copy-nfc-link', function () {
var $input = $(this).closest('.input-group').find('.chore-nfc-link-input');
var val = $input.val() || '';
if (!val) {
return;
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(String(val));
} else {
$input.trigger('focus').trigger('select');
document.execCommand('copy');
}
});
$(document).on('click', '.btn-chore-nfc-toggle', function () {
var id = $(this).data('id');
var enable = String($(this).data('enable')) === '1';
if (!id) {
return;
}
postJson('/chore_nfc_toggle.php', { id: id, enabled: enable })
.then(function () {
window.location.reload();
})
.catch(function (err) {
showAlert($('#chore_nfc_feedback_' + id), 'danger', err.message || 'Could not update NFC status');
$('#chore_nfc_feedback_' + id).removeClass('d-none');
});
});
}
function groceryReloadKeepParams() {
@@ -967,6 +1071,21 @@
window.alert(err.message || 'Could not reset PIN');
});
});
$(document).on('click', '.btn-rotate-person-nfc-token', function () {
var id = $(this).data('id');
var name = $(this).data('name') || 'this person';
if (!id || !window.confirm('Rotate NFC submitter token for ' + name + '? Existing personal NFC links will stop working.')) {
return;
}
postJson('/person_nfc_token_rotate.php', { person_id: id })
.then(function () {
window.alert('Token rotated for ' + name + '. Generate fresh chore NFC links as needed.');
})
.catch(function (err) {
window.alert(err.message || 'Could not rotate NFC token');
});
});
}
$(function () {