fix external kids for events
This commit is contained in:
@@ -181,8 +181,7 @@
|
|||||||
Add non-school participant
|
Add non-school participant
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="form-text">After adding participants, click Submit to save them to the database.</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 id="externalParticipantsHidden"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||||
@@ -290,8 +289,14 @@
|
|||||||
($charge['external_parent_lastname'] ?? '')
|
($charge['external_parent_lastname'] ?? '')
|
||||||
);
|
);
|
||||||
$externalParentPhone = $charge['external_parent_phone'] ?? '';
|
$externalParentPhone = $charge['external_parent_phone'] ?? '';
|
||||||
|
$externalParentEmail = $charge['external_parent_email'] ?? '';
|
||||||
$standardParentName = trim($charge['parent_firstname'] . ' ' . $charge['parent_lastname']);
|
$standardParentName = trim($charge['parent_firstname'] . ' ' . $charge['parent_lastname']);
|
||||||
$parentColumnName = $externalParentLabel ?: ($standardParentName ?: '—');
|
$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($parentColumnName)) ?>"><?= esc($parentColumnName) ?></td>
|
||||||
<td data-sort-value="<?= esc(strtolower($displayName)) ?>">
|
<td data-sort-value="<?= esc(strtolower($displayName)) ?>">
|
||||||
@@ -300,10 +305,14 @@
|
|||||||
<small class="text-muted d-block"><?= esc($externalNote) ?></small>
|
<small class="text-muted d-block"><?= esc($externalNote) ?></small>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td data-sort-value="<?= esc(strtolower($externalParentPhone ?: '')) ?>">
|
<td data-sort-value="<?= esc(strtolower($externalInfoValue)) ?>">
|
||||||
<?php if ($externalParentPhone): ?>
|
<?php if ($externalParentPhone): ?>
|
||||||
<div class="text-muted small"><?= esc($externalParentPhone) ?></div>
|
<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>
|
<span class="text-muted small">—</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
@@ -524,186 +533,11 @@ function loadStudentsWithCharges() {
|
|||||||
loadStudentsWithCharges();
|
loadStudentsWithCharges();
|
||||||
}
|
}
|
||||||
|
|
||||||
const $externalHidden = $('#externalParticipantsHidden');
|
|
||||||
const $externalSaveButton = $('#externalParticipantSave');
|
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 modalElement = document.getElementById('externalParticipantModal');
|
||||||
const modalInstance = modalElement ? new bootstrap.Modal(modalElement) : null;
|
const modalInstance = modalElement ? new bootstrap.Modal(modalElement) : null;
|
||||||
if (modalElement) {
|
|
||||||
modalElement.addEventListener('hidden.bs.modal', () => {
|
|
||||||
editingKey = null;
|
|
||||||
$externalSaveButton.text('Add to list');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function clearModalInputs() {
|
function clearModalInputs() {
|
||||||
$('#modalExternalFirstName').val('');
|
$('#modalExternalFirstName').val('');
|
||||||
$('#modalExternalLastName').val('');
|
$('#modalExternalLastName').val('');
|
||||||
@@ -714,6 +548,46 @@ function loadStudentsWithCharges() {
|
|||||||
$('#modalParentEmail').val('');
|
$('#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) {
|
function getSortValue(row, columnIndex, sortType) {
|
||||||
const cell = row.children[columnIndex];
|
const cell = row.children[columnIndex];
|
||||||
if (!cell) {
|
if (!cell) {
|
||||||
@@ -910,7 +784,6 @@ function loadStudentsWithCharges() {
|
|||||||
alert('Select an event before adding a non-school participant.');
|
alert('Select an event before adding a non-school participant.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const eventAmount = parseFloat($('#event_id option:selected').data('amount')) || 0;
|
|
||||||
const explicitParentName = `${parentFirst} ${parentLast}`.trim();
|
const explicitParentName = `${parentFirst} ${parentLast}`.trim();
|
||||||
if (!explicitParentName) {
|
if (!explicitParentName) {
|
||||||
alert('Please enter the external kid parent name.');
|
alert('Please enter the external kid parent name.');
|
||||||
@@ -920,9 +793,7 @@ function loadStudentsWithCharges() {
|
|||||||
alert('Please enter the external kid parent email.');
|
alert('Please enter the external kid parent email.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parentName = explicitParentName;
|
submitExternalParticipant({
|
||||||
const draftEntry = editingKey ? getDraftByKey(editingKey) : null;
|
|
||||||
const entry = {
|
|
||||||
firstname: firstName,
|
firstname: firstName,
|
||||||
lastname: lastName,
|
lastname: lastName,
|
||||||
note: note,
|
note: note,
|
||||||
@@ -930,91 +801,10 @@ function loadStudentsWithCharges() {
|
|||||||
parentLastname: parentLast,
|
parentLastname: parentLast,
|
||||||
parentPhone: parentPhone,
|
parentPhone: parentPhone,
|
||||||
parentEmail: parentEmail,
|
parentEmail: parentEmail,
|
||||||
eventId: (draftEntry && draftEntry.eventId) ? draftEntry.eventId : eventId,
|
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');
|
|
||||||
clearModalInputs();
|
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>
|
</script>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user