fix external kids for events

This commit is contained in:
root
2026-05-05 22:37:32 -04:00
parent 92016f90d0
commit 1f51be91e4
+57 -267
View File
@@ -181,8 +181,7 @@
Add non-school participant
</button>
<?php endif; ?>
<div class="form-text">After adding participants, click Submit to save them to the database.</div>
<div id="externalParticipantsHidden"></div>
<div class="form-text">Enrolled students are saved when you click Submit. Non-school participants are saved immediately and will then appear in all browsers.</div>
</div>
<div class="d-flex flex-wrap gap-2 mt-3">
@@ -290,8 +289,14 @@
($charge['external_parent_lastname'] ?? '')
);
$externalParentPhone = $charge['external_parent_phone'] ?? '';
$externalParentEmail = $charge['external_parent_email'] ?? '';
$standardParentName = trim($charge['parent_firstname'] . ' ' . $charge['parent_lastname']);
$parentColumnName = $externalParentLabel ?: ($standardParentName ?: '—');
$externalInfoValue = trim(implode(' ', array_filter([
$externalParentPhone,
$externalParentEmail,
$externalNote,
])));
?>
<td data-sort-value="<?= esc(strtolower($parentColumnName)) ?>"><?= esc($parentColumnName) ?></td>
<td data-sort-value="<?= esc(strtolower($displayName)) ?>">
@@ -300,10 +305,14 @@
<small class="text-muted d-block"><?= esc($externalNote) ?></small>
<?php endif; ?>
</td>
<td data-sort-value="<?= esc(strtolower($externalParentPhone ?: '')) ?>">
<td data-sort-value="<?= esc(strtolower($externalInfoValue)) ?>">
<?php if ($externalParentPhone): ?>
<div class="text-muted small"><?= esc($externalParentPhone) ?></div>
<?php else: ?>
<?php endif; ?>
<?php if ($externalParentEmail): ?>
<div class="text-muted small"><?= esc($externalParentEmail) ?></div>
<?php endif; ?>
<?php if (!$externalParentPhone && !$externalParentEmail): ?>
<span class="text-muted small">—</span>
<?php endif; ?>
</td>
@@ -524,186 +533,11 @@ function loadStudentsWithCharges() {
loadStudentsWithCharges();
}
const $externalHidden = $('#externalParticipantsHidden');
const $externalSaveButton = $('#externalParticipantSave');
const storageKey = 'eventChargesExternalParticipants';
let externalDrafts = [];
let editingKey = null;
const escapeHtml = (value) => {
const s = String(value ?? '');
return s.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
};
function buildExternalRow(entry) {
const key = entry.key;
const isPaid = !!entry.paid;
const waiverSigned = !!entry.waiverSigned;
const firstEsc = escapeHtml(entry.firstname);
const lastEsc = escapeHtml(entry.lastname);
const noteEsc = escapeHtml(entry.note);
const parentFirstEsc = escapeHtml(entry.parentFirstname);
const parentLastEsc = escapeHtml(entry.parentLastname);
const parentPhoneEsc = escapeHtml(entry.parentPhone);
const parentEmailEsc = escapeHtml(entry.parentEmail);
const label = (firstEsc || lastEsc)
? `${firstEsc || ''} ${lastEsc || ''}`.trim()
: 'Unnamed participant';
const parentLabel = (parentFirstEsc || parentLastEsc)
? `<div class="text-muted small">Parent: ${parentFirstEsc} ${parentLastEsc}</div>`
: '';
const phoneLabel = parentPhoneEsc
? `<div class="text-muted small">${parentPhoneEsc}</div>`
: '';
const parentNameForRow = entry.parentDisplayName || '—';
const amountDisplay = ((entry.eventAmount ?? 0) || 0.0).toFixed(2);
const $wrapper = $(`
<div data-key="${key}">
<input type="hidden" name="external_participants[${key}][firstname]" value="${firstEsc}">
<input type="hidden" name="external_participants[${key}][lastname]" value="${lastEsc}">
<input type="hidden" name="external_participants[${key}][note]" value="${noteEsc}">
<input type="hidden" name="external_participants[${key}][parent_firstname]" value="${parentFirstEsc}">
<input type="hidden" name="external_participants[${key}][parent_lastname]" value="${parentLastEsc}">
<input type="hidden" name="external_participants[${key}][parent_phone]" value="${parentPhoneEsc}">
<input type="hidden" name="external_participants[${key}][parent_email]" value="${parentEmailEsc}">
<input type="hidden" name="external_participants[${key}][waiver_signed]" value="${waiverSigned ? '1' : '0'}">
<input type="hidden" name="external_participants[${key}][paid]" value="${isPaid ? '1' : '0'}">
</div>
`);
$externalHidden.append($wrapper);
const $tableBody = $(`#eventTable_${entry.eventId} tbody`);
if (!$tableBody.length) {
return;
}
const createdDisplay = new Date().toLocaleString();
const $previewRow = $(`
<tr class="external-preview table-warning" data-key="${key}" data-event-id="${entry.eventId}">
<td data-sort-value="">—</td>
<td data-sort-value="${escapeHtml(String(parentNameForRow).toLowerCase())}">${escapeHtml(parentNameForRow)}</td>
<td data-sort-value="${escapeHtml(String(`${entry.firstname || ''} ${entry.lastname || ''}`.trim()).toLowerCase())}">
<strong>${label}</strong>
${noteEsc ? `<div class="text-muted small">${noteEsc}</div>` : ''}
</td>
<td data-sort-value="${escapeHtml(String(entry.parentPhone || '').toLowerCase())}">
${phoneLabel}
${phoneLabel ? '' : '<span class="text-muted small">—</span>'}
</td>
<td data-sort-value="external">External</td>
<td data-sort-value="${amountDisplay}">$${amountDisplay}</td>
<td data-sort-value="${new Date().toISOString()}">${createdDisplay}</td>
<td class="text-center" data-sort-value="${waiverSigned ? 'signed' : 'unsigned'}">
<div class="form-check d-inline-flex align-items-center gap-2 justify-content-center m-0">
<input class="form-check-input external-waiver-toggle" type="checkbox" data-key="${key}" ${waiverSigned ? 'checked' : ''}>
<span class="small text-muted external-waiver-label">${waiverSigned ? 'Signed' : 'Unsigned'}</span>
</div>
</td>
<td class="text-center" data-sort-value="${isPaid ? 'paid' : 'unpaid'}">
<span class="badge ${isPaid ? 'bg-success' : 'bg-danger'} external-paid-badge">${isPaid ? 'Paid' : 'Unpaid'}</span>
</td>
<td class="text-center" data-sort-value="${isPaid ? 'paid' : 'unpaid'}">
<div class="form-check d-inline-flex align-items-center gap-2 justify-content-center m-0">
<input class="form-check-input external-paid-toggle" type="checkbox" data-key="${key}" ${isPaid ? 'checked' : ''}>
<span class="small text-muted">Paid</span>
</div>
</td>
<td class="text-end d-flex gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm edit-external-participant" data-key="${key}">
Edit
</button>
<button type="button" class="btn btn-outline-danger btn-sm remove-external-participant" data-key="${key}">
Remove
</button>
</td>
</tr>
`);
$tableBody.append($previewRow);
applyActiveSort($tableBody.closest('table').get(0));
}
function persistDrafts() {
localStorage.setItem(storageKey, JSON.stringify(externalDrafts));
}
function saveExternalDraft(entry) {
entry.key = entry.key ?? `${Date.now()}_${Math.random().toString(36).slice(2)}`;
entry.paid = !!entry.paid;
entry.waiverSigned = !!entry.waiverSigned;
removeExternalEntryFromDom(entry.key);
externalDrafts = externalDrafts.filter((existing) => existing.key !== entry.key);
externalDrafts.push(entry);
persistDrafts();
buildExternalRow(entry);
}
function removeExternalEntryFromDom(key) {
$(`tr.external-preview[data-key="${key}"]`).remove();
$externalHidden.find(`div[data-key="${key}"]`).remove();
}
function removeDraft(key) {
externalDrafts = externalDrafts.filter((entry) => entry.key !== key);
persistDrafts();
}
function loadDrafts() {
const raw = localStorage.getItem(storageKey);
if (!raw) {
return;
}
try {
const saved = JSON.parse(raw);
if (!Array.isArray(saved)) {
return;
}
externalDrafts = saved;
const currentEvent = $('#event_id').val();
externalDrafts.forEach((entry) => {
if (String(entry.eventId) === String(currentEvent)) {
buildExternalRow(entry);
}
});
} catch (e) {
console.error('Failed to load external drafts', e);
}
}
function getDraftByKey(key) {
return externalDrafts.find((entry) => entry.key === key);
}
function openExternalModalForEntry(key) {
const entry = getDraftByKey(key);
if (!entry) {
return;
}
editingKey = key;
$externalSaveButton.text('Save');
$('#modalExternalFirstName').val(entry.firstname);
$('#modalExternalLastName').val(entry.lastname);
$('#modalExternalNote').val(entry.note);
$('#modalParentFirstName').val(entry.parentFirstname);
$('#modalParentLastName').val(entry.parentLastname);
$('#modalParentPhone').val(entry.parentPhone);
$('#modalParentEmail').val(entry.parentEmail);
if (modalInstance) {
modalInstance.show();
}
}
const modalElement = document.getElementById('externalParticipantModal');
const modalInstance = modalElement ? new bootstrap.Modal(modalElement) : null;
if (modalElement) {
modalElement.addEventListener('hidden.bs.modal', () => {
editingKey = null;
$externalSaveButton.text('Add to list');
});
}
function clearModalInputs() {
$('#modalExternalFirstName').val('');
$('#modalExternalLastName').val('');
@@ -714,6 +548,46 @@ function loadStudentsWithCharges() {
$('#modalParentEmail').val('');
}
function appendHiddenField(form, name, value) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
}
function escapeHtml(value) {
const s = String(value ?? '');
return s.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function submitExternalParticipant(payload) {
const form = document.createElement('form');
form.method = 'post';
form.action = '<?= site_url('payment/event_charges') ?>';
appendHiddenField(form, '<?= csrf_token() ?>', '<?= csrf_hash() ?>');
appendHiddenField(form, 'school_year', '<?= esc($school_year) ?>');
appendHiddenField(form, 'semester', '<?= esc($semester) ?>');
appendHiddenField(form, 'event_id', payload.eventId);
appendHiddenField(form, 'external_participants[new][firstname]', payload.firstname);
appendHiddenField(form, 'external_participants[new][lastname]', payload.lastname);
appendHiddenField(form, 'external_participants[new][note]', payload.note);
appendHiddenField(form, 'external_participants[new][parent_firstname]', payload.parentFirstname);
appendHiddenField(form, 'external_participants[new][parent_lastname]', payload.parentLastname);
appendHiddenField(form, 'external_participants[new][parent_phone]', payload.parentPhone);
appendHiddenField(form, 'external_participants[new][parent_email]', payload.parentEmail);
appendHiddenField(form, 'external_participants[new][waiver_signed]', '0');
appendHiddenField(form, 'external_participants[new][paid]', '0');
document.body.appendChild(form);
form.submit();
}
function getSortValue(row, columnIndex, sortType) {
const cell = row.children[columnIndex];
if (!cell) {
@@ -910,7 +784,6 @@ function loadStudentsWithCharges() {
alert('Select an event before adding a non-school participant.');
return;
}
const eventAmount = parseFloat($('#event_id option:selected').data('amount')) || 0;
const explicitParentName = `${parentFirst} ${parentLast}`.trim();
if (!explicitParentName) {
alert('Please enter the external kid parent name.');
@@ -920,9 +793,7 @@ function loadStudentsWithCharges() {
alert('Please enter the external kid parent email.');
return;
}
const parentName = explicitParentName;
const draftEntry = editingKey ? getDraftByKey(editingKey) : null;
const entry = {
submitExternalParticipant({
firstname: firstName,
lastname: lastName,
note: note,
@@ -930,91 +801,10 @@ function loadStudentsWithCharges() {
parentLastname: parentLast,
parentPhone: parentPhone,
parentEmail: parentEmail,
eventId: (draftEntry && draftEntry.eventId) ? draftEntry.eventId : eventId,
eventAmount: (draftEntry && draftEntry.eventAmount) ? draftEntry.eventAmount : eventAmount,
parentDisplayName: (draftEntry && draftEntry.parentDisplayName) ? draftEntry.parentDisplayName : parentName,
waiverSigned: (draftEntry && typeof draftEntry.waiverSigned !== 'undefined') ? !!draftEntry.waiverSigned : false,
paid: (draftEntry && typeof draftEntry.paid !== 'undefined') ? !!draftEntry.paid : false,
};
if (editingKey) {
entry.key = editingKey;
}
saveExternalDraft(entry);
editingKey = null;
$externalSaveButton.text('Add to list');
eventId: eventId
});
clearModalInputs();
if (modalInstance) {
modalInstance.hide();
}
});
$(document).on('click', '.remove-external-participant', function() {
const $entry = $(this).closest('[data-key]');
const key = $entry.data('key');
removeExternalEntryFromDom(key);
if (key) {
removeDraft(key);
}
});
$(document).on('click', '.edit-external-participant', function() {
const key = $(this).data('key');
if (key) {
openExternalModalForEntry(key);
}
});
$(document).on('change', '.external-paid-toggle', function() {
const key = $(this).data('key');
const paid = !!this.checked;
if (!key) {
return;
}
const entry = getDraftByKey(key);
if (entry) {
entry.paid = paid;
persistDrafts();
}
$externalHidden
.find(`div[data-key="${key}"] input[name="external_participants[${key}][paid]"]`)
.val(paid ? '1' : '0');
const $badge = $(`tr.external-preview[data-key="${key}"] .external-paid-badge`);
if (paid) {
$badge.removeClass('bg-danger').addClass('bg-success').text('Paid');
} else {
$badge.removeClass('bg-success').addClass('bg-danger').text('Unpaid');
}
const $row = $(`tr.external-preview[data-key="${key}"]`);
$row.children().eq(8).attr('data-sort-value', paid ? 'paid' : 'unpaid');
$row.children().eq(9).attr('data-sort-value', paid ? 'paid' : 'unpaid');
applyActiveSort($row.closest('table').get(0));
});
$(document).on('change', '.external-waiver-toggle', function() {
const key = $(this).data('key');
const signed = !!this.checked;
if (!key) {
return;
}
const entry = getDraftByKey(key);
if (entry) {
entry.waiverSigned = signed;
persistDrafts();
}
$externalHidden
.find(`div[data-key="${key}"] input[name="external_participants[${key}][waiver_signed]"]`)
.val(signed ? '1' : '0');
const $label = $(`tr.external-preview[data-key="${key}"] .external-waiver-label`);
if (signed) {
$label.text('Signed');
} else {
$label.text('Unsigned');
}
const $row = $(`tr.external-preview[data-key="${key}"]`);
$row.children().eq(7).attr('data-sort-value', signed ? 'signed' : 'unsigned');
applyActiveSort($row.closest('table').get(0));
});
$('#event-participant-form').on('submit', function() {
localStorage.removeItem(storageKey);
});
loadDrafts();
});
</script>
<?= $this->endSection() ?>