fix external kids for events
This commit is contained in:
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
};
|
||||
|
||||
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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
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() ?>
|
||||
|
||||
Reference in New Issue
Block a user