545 lines
23 KiB
PHP
Executable File
545 lines
23 KiB
PHP
Executable File
<?= $this->extend('layout/management_layout') ?>
|
||
<?= $this->section('content') ?>
|
||
|
||
<?php
|
||
$page_title = $title ?? 'Notified Attendance Violations';
|
||
$school_year = $school_year ?? '';
|
||
$semester = $semester ?? '';
|
||
$show_actions = false; // notified page hides actions by design
|
||
?>
|
||
|
||
<div class="container-fluid px-0">
|
||
<h2 class="text-center mt-4 mb-3"><?= esc($page_title) ?></h2>
|
||
<div class="d-flex justify-content-center mb-2">
|
||
<div class="col-12 col-lg-10">
|
||
<?= $this->include('partials/academic_filter') ?>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($msg = session()->getFlashdata('message')): ?>
|
||
<div class="alert alert-success mx-3"><?= esc($msg) ?></div>
|
||
<?php endif; ?>
|
||
|
||
<?php if ($err = session()->getFlashdata('error')): ?>
|
||
<div class="alert alert-danger mx-3"><?= esc($err) ?></div>
|
||
<?php endif; ?>
|
||
|
||
<div class="row g-0">
|
||
<div class="col-12">
|
||
<div class="card border-0 rounded-3 shadow-sm">
|
||
|
||
<div class="card-header d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||
<h5 class="mt-2 mb-0">Notified Attendance Violations</h5>
|
||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||
<span class="badge bg-primary">School Year: <?= esc($school_year) ?></span>
|
||
<?php if ($semester !== ''): ?>
|
||
<span class="badge bg-secondary">Semester: <?= esc($semester) ?></span>
|
||
<?php endif; ?>
|
||
<a href="<?= site_url('attendance/violations') ?>" class="btn btn-outline-dark btn-sm">
|
||
<i class="fas fa-hourglass-half"></i> View Pending
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-body">
|
||
<?php if (empty($students)): ?>
|
||
<div class="alert alert-info mb-0">No notified violations found.</div>
|
||
<?php else: ?>
|
||
<div class="table-responsive">
|
||
<table id="notifiedTable" class="table table-striped table-hover align-middle w-100">
|
||
<thead class="table-dark">
|
||
<tr>
|
||
<th>Student</th>
|
||
<th>Grade</th>
|
||
<th>Last Incident</th>
|
||
<th>Reason</th>
|
||
<th>Note</th>
|
||
<th>Parent</th>
|
||
<th>Notified On</th>
|
||
<th>Details</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($students as $st): ?>
|
||
<?php
|
||
// ID may be under 'student_id' (from tracking) or 'id' (from students)
|
||
$studId = (int)($st['student_id'] ?? $st['id'] ?? 0);
|
||
|
||
// Robust name resolution (name, student_name, or first/last)
|
||
$studName =
|
||
($st['name'] ?? null)
|
||
?: ($st['student_name'] ?? null)
|
||
?: trim(
|
||
($st['firstname'] ?? $st['first_name'] ?? '') . ' ' .
|
||
($st['lastname'] ?? $st['last_name'] ?? '')
|
||
);
|
||
if ($studName === '') {
|
||
$studName = $studId ? ('Student #'.$studId) : '—';
|
||
}
|
||
|
||
// Grade/Class name resolution
|
||
$className = $st['class_section_name']
|
||
?? $st['class_name']
|
||
?? $st['className']
|
||
?? $st['grade']
|
||
?? '';
|
||
|
||
// Dates and notified meta
|
||
$lastDate = $st['last_date'] ?? $st['date'] ?? null; // DATETIME
|
||
$notified = !empty($st['is_notified']);
|
||
$notifCnt = (int)($st['notif_counter'] ?? 0);
|
||
$reason = trim((string)($st['reason'] ?? ''));
|
||
|
||
// Term fields (fallback to page vars if absent)
|
||
$sy = (string)($st['school_year'] ?? $school_year);
|
||
$sem = (string)($st['semester'] ?? $semester);
|
||
|
||
// Row color (optional)
|
||
$viColor = (string)($st['color'] ?? '#0d6efd');
|
||
|
||
// Parent display info (if available)
|
||
$parentEmail = $st['parent_email'] ?? '';
|
||
$parentName = $st['parent_name'] ?? '';
|
||
$parentPhone = $st['parent_phone'] ?? '';
|
||
$parentLabel = $parentName ?: ($parentEmail ?: 'View');
|
||
|
||
// Note from attendance_tracking
|
||
$note = trim((string)($st['note'] ?? ''));
|
||
$noteShort = $note !== '' ? (mb_strlen($note) > 120 ? (mb_substr($note, 0, 120) . '…') : $note) : '';
|
||
$incidentYmd = $lastDate ? substr((string)$lastDate, 0, 10) : '';
|
||
$code = (string)($st['reason'] ?? $st['violation_code'] ?? '');
|
||
$notifiedAt = $st['updated_at'] ?? $st['date'] ?? null;
|
||
?>
|
||
<tr style="border-left:6px solid <?= esc($viColor) ?>;">
|
||
<td>
|
||
<a
|
||
href="<?= site_url('family') . '?student_id=' . (int)$studId ?>"
|
||
class="text-decoration-none"
|
||
data-family-student-id="<?= (int)$studId ?>"
|
||
title="Open Family Card">
|
||
<?= esc($studName) ?>
|
||
</a>
|
||
</td>
|
||
<td><?= esc($className) ?></td>
|
||
<td data-order="<?= $incidentYmd !== '' ? esc($incidentYmd) : '' ?>">
|
||
<?= $incidentYmd !== '' ? esc(date('m-d-Y', strtotime($incidentYmd))) : '—' ?>
|
||
</td>
|
||
<td><?= $reason !== '' ? esc($reason) : '—' ?></td>
|
||
|
||
|
||
<td style="max-width:280px;">
|
||
<?php if ($note !== ''): ?>
|
||
<div class="small text-wrap mb-1">
|
||
<?= nl2br(esc($noteShort)) ?>
|
||
</div>
|
||
<?php if (mb_strlen($note) > 120): ?>
|
||
<button
|
||
type="button"
|
||
class="btn btn-sm btn-outline-secondary me-1 mb-1"
|
||
data-bs-toggle="tooltip"
|
||
data-bs-placement="top"
|
||
title="<?= esc($note) ?>"
|
||
>
|
||
View full
|
||
</button>
|
||
<?php endif; ?>
|
||
<button
|
||
type="button"
|
||
class="btn btn-sm btn-outline-primary mb-1 add-note-btn"
|
||
data-student-id="<?= (int)$studId ?>"
|
||
data-student-name="<?= esc($studName) ?>"
|
||
data-code="<?= esc($code) ?>"
|
||
data-incident-date="<?= esc($incidentYmd) ?>"
|
||
data-note="<?= esc($note) ?>"
|
||
data-return-url="<?= current_url() ?>"
|
||
>
|
||
Edit note
|
||
</button>
|
||
<?php else: ?>
|
||
<button
|
||
type="button"
|
||
class="btn btn-sm btn-outline-primary add-note-btn"
|
||
data-student-id="<?= (int)$studId ?>"
|
||
data-student-name="<?= esc($studName) ?>"
|
||
data-code="<?= esc($code) ?>"
|
||
data-incident-date="<?= esc($incidentYmd) ?>"
|
||
data-note=""
|
||
data-return-url="<?= current_url() ?>"
|
||
>
|
||
Add note
|
||
</button>
|
||
<?php endif; ?>
|
||
</td>
|
||
|
||
<!-- Parent column -->
|
||
<td>
|
||
<?php if ($parentName !== ''): ?>
|
||
<a
|
||
href="<?= site_url('family') . '?student_id=' . (int)$studId ?>"
|
||
class="text-decoration-none"
|
||
data-family-student-id="<?= (int)$studId ?>"
|
||
title="Open Family Card">
|
||
<?= esc($parentName) ?>
|
||
</a>
|
||
<?php else: ?>
|
||
<span class="text-muted">—</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
|
||
<td>
|
||
<?= $notifiedAt ? esc(local_date($notifiedAt, 'm-d-Y')) : '—' ?>
|
||
</td>
|
||
|
||
<td class="text-nowrap">
|
||
<a href="<?= site_url('attendance/view/' . $studId) . '?code=' . urlencode($code) . ($lastDate ? '&incident_date=' . urlencode(substr((string)$lastDate,0,10)) : '') . '&include_notified=1' ?>" class="btn btn-sm btn-info">
|
||
<i class="fas fa-eye"></i> Details
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Add/Edit Comment/Note Modal -->
|
||
<div class="modal fade" id="notifyCommentModal" tabindex="-1" aria-labelledby="notifyCommentModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<form method="post" action="<?= site_url('attendance/save-note') ?>" class="modal-content">
|
||
<?= csrf_field() ?>
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="notifyCommentModalLabel">Add Comment / Note</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
<div class="mb-2 small text-muted" id="noteMeta">
|
||
<div><strong>Student Name:</strong> <span id="noteStudentName">—</span></div>
|
||
<div><strong>Code:</strong> <span id="noteCode">—</span> <strong>Date:</strong> <span id="noteDate">—</span></div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="note" class="form-label">Comment / Note</label>
|
||
<textarea id="note" name="note" class="form-control" rows="5" required placeholder="Type any follow-up, call outcome, or special circumstances..."></textarea>
|
||
</div>
|
||
|
||
<!-- Hidden fields to carry context back -->
|
||
<input type="hidden" name="student_id" id="noteStudentId">
|
||
<input type="hidden" name="code" id="noteCodeInput">
|
||
<input type="hidden" name="incident_date" id="noteIncidentDate">
|
||
<input type="hidden" name="to" id="noteTo">
|
||
<input type="hidden" name="subject" id="noteSubject">
|
||
<input type="hidden" name="variant" id="noteVariant">
|
||
<input type="hidden" name="return_url" id="noteReturnUrl" value="<?= current_url() ?>">
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fas fa-save"></i> Save Note
|
||
</button>
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<?= $this->endSection() ?>
|
||
|
||
<?= $this->section('scripts') ?>
|
||
<script>
|
||
(function(){
|
||
// simple HTML escaper for dynamic strings
|
||
function esc(s) {
|
||
return String(s ?? '')
|
||
.replaceAll('&','&').replaceAll('<','<')
|
||
.replaceAll('>','>').replaceAll('"','"').replaceAll("'", "'");
|
||
}
|
||
|
||
// Initialize Bootstrap tooltips (for "View full" note)
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
if (window.bootstrap) {
|
||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||
tooltipTriggerList.forEach(function (el) { new bootstrap.Tooltip(el); });
|
||
}
|
||
});
|
||
|
||
function parentCard(p, label) {
|
||
if (!p) return '';
|
||
const name = esc(p.name || '');
|
||
const email = esc(p.email || '');
|
||
const phone = esc(p.phone || '');
|
||
return `
|
||
<div class="col-12 col-md-6">
|
||
<div class="card shadow-sm mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<strong>${esc(label)} Parent</strong>
|
||
${email ? `<a class="btn btn-sm btn-outline-primary" href="mailto:${email}">
|
||
<i class="fas fa-envelope"></i> Email</a>` : ''}
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-1"><span class="text-muted">Name:</span> ${name || '—'}</div>
|
||
<div class="mb-1"><span class="text-muted">Email:</span> ${email || '—'}</div>
|
||
<div class="mb-1"><span class="text-muted">Phone:</span> ${phone || '—'}</div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function renderParentsModal(data) {
|
||
const body = document.getElementById('parentModalBody');
|
||
if (!data || (!data.primary && !data.secondary)) {
|
||
body.innerHTML = `<div class="alert alert-warning mb-0">No parent information available for this student.</div>`;
|
||
return;
|
||
}
|
||
const primary = data.primary ? {
|
||
name: (data.primary.firstname ? `${data.primary.firstname} ${data.primary.lastname||''}`.trim() : (data.primary.name || '')),
|
||
email: data.primary.email || '',
|
||
phone: data.primary.cellphone || data.primary.phone || ''
|
||
} : null;
|
||
|
||
const secondary = data.secondary ? {
|
||
name: (data.secondary.firstname ? `${data.secondary.firstname} ${data.secondary.lastname||''}`.trim() : (data.secondary.name || '')),
|
||
email: data.secondary.email || '',
|
||
phone: data.secondary.cellphone || data.secondary.phone || ''
|
||
} : null;
|
||
|
||
body.innerHTML = `<div class="row">
|
||
${primary ? parentCard(primary, 'First') : ''}
|
||
${secondary ? parentCard(secondary, 'Second') : ''}
|
||
</div>`;
|
||
}
|
||
|
||
// Wire up modal open/fetch
|
||
document.addEventListener('click', function(e) {
|
||
const parentBtn = e.target.closest('.show-parents');
|
||
if (!parentBtn) return;
|
||
|
||
e.preventDefault();
|
||
|
||
const modalEl = document.getElementById('parentModal');
|
||
const bodyEl = document.getElementById('parentModalBody');
|
||
const sid = parentBtn.dataset.studentId || '';
|
||
|
||
if (!sid) {
|
||
if (bodyEl) bodyEl.innerHTML = '<div class="alert alert-warning mb-0">Missing student id for this row.</div>';
|
||
if (window.bootstrap) new bootstrap.Modal(modalEl).show();
|
||
return;
|
||
}
|
||
|
||
bodyEl.innerHTML = `
|
||
<div class="text-center text-muted my-4">
|
||
<div class="spinner-border" role="status" aria-hidden="true"></div>
|
||
<div class="mt-2">Loading parent info…</div>
|
||
</div>`;
|
||
|
||
if (window.bootstrap) new bootstrap.Modal(modalEl).show();
|
||
|
||
let url = parentBtn.dataset.fetchUrl;
|
||
if (!url || !url.includes('student_id=')) {
|
||
url = '<?= site_url('attendance/parents-info') ?>?student_id=' + encodeURIComponent(sid);
|
||
}
|
||
|
||
fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||
.then(async (r) => {
|
||
let data = null;
|
||
try { data = await r.json(); } catch (e) {}
|
||
if (!r.ok) throw new Error((data && data.error) ? data.error : ('HTTP ' + r.status));
|
||
return data;
|
||
})
|
||
.then((data) => renderParentsModal(data))
|
||
.catch((err) => {
|
||
bodyEl.innerHTML = '<div class="alert alert-danger mb-0">Unable to load parent info: ' + esc(err?.message || err) + '</div>';
|
||
});
|
||
});
|
||
|
||
// Compute offset if a fixed/sticky top navbar exists to prevent overlap
|
||
function getFixedHeaderOffset() {
|
||
let total = 0;
|
||
const stack = [];
|
||
const header = document.querySelector('header.navbar.sticky-top, header.navbar.fixed-top');
|
||
if (header) stack.push(header);
|
||
const mgmt = document.getElementById('navbarManagement');
|
||
if (mgmt && (mgmt.classList.contains('sticky-top') || mgmt.classList.contains('fixed-top'))) stack.push(mgmt);
|
||
document.querySelectorAll('.navbar.sticky-top, .navbar.fixed-top').forEach(el => { if (!stack.includes(el)) stack.push(el); });
|
||
stack.forEach(el => { const h = el.offsetHeight || el.getBoundingClientRect().height || 0; total += Math.max(0, Math.round(h)); });
|
||
return total;
|
||
}
|
||
|
||
// ===== DataTables init (with FixedHeader) =====
|
||
function initDataTable() {
|
||
if (!window.jQuery || !jQuery.fn || !jQuery.fn.DataTable) return false;
|
||
|
||
const $ = jQuery;
|
||
const table = $('#notifiedTable');
|
||
if (!table.length) return true;
|
||
|
||
// Column indexes after header changes:
|
||
// 0 Student, 1 Grade, 2 Last Incident, 3 Reason, 4 Note, 5 Notified On, 6 Parent, 7 Details
|
||
table.DataTable({
|
||
paging: true,
|
||
pageLength: 25,
|
||
lengthMenu: [[10,25,50,100,-1],[10,25,50,100,'All']],
|
||
ordering: true,
|
||
order: [[2, 'desc'], [0, 'asc']], // Last Incident desc then Student asc
|
||
searching: true,
|
||
info: true,
|
||
responsive: true,
|
||
autoWidth: false,
|
||
columnDefs: [
|
||
{ targets: [6, 7], orderable: false, searchable: false }, // Parent, Details
|
||
{ targets: [4], orderable: false } // Note not sortable
|
||
],
|
||
fixedHeader: {
|
||
header: true,
|
||
headerOffset: getFixedHeaderOffset()
|
||
},
|
||
dom: "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" +
|
||
"<'row'<'col-sm-12'tr>>" +
|
||
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||
language: {
|
||
search: "Quick search:",
|
||
lengthMenu: "Show _MENU_ rows",
|
||
info: "Showing _START_–_END_ of _TOTAL_",
|
||
infoEmpty: "No rows to show",
|
||
zeroRecords: "No matching records",
|
||
paginate: { previous: "‹", next: "›" }
|
||
}
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
function ensureDataTablesThenInit() {
|
||
const head = document.head;
|
||
|
||
function loadScript(src, id) {
|
||
return new Promise((resolve, reject) => {
|
||
if (id && document.getElementById(id)) return resolve();
|
||
const s = document.createElement('script');
|
||
if (id) s.id = id;
|
||
s.src = src;
|
||
s.onload = resolve;
|
||
s.onerror = reject;
|
||
head.appendChild(s);
|
||
});
|
||
}
|
||
function loadCss(href, id) {
|
||
return new Promise((resolve) => {
|
||
if (id && document.getElementById(id)) return resolve();
|
||
const l = document.createElement('link');
|
||
if (id) l.id = id;
|
||
l.rel = 'stylesheet';
|
||
l.href = href;
|
||
l.onload = resolve;
|
||
head.appendChild(l);
|
||
});
|
||
}
|
||
|
||
const hasDT = !!(window.jQuery && jQuery.fn && jQuery.fn.DataTable);
|
||
const hasFH = !!(hasDT && jQuery.fn.dataTable && jQuery.fn.dataTable.FixedHeader);
|
||
|
||
if (hasDT && hasFH) {
|
||
initDataTable();
|
||
return;
|
||
}
|
||
|
||
const tasks = [];
|
||
if (!hasDT) {
|
||
if (!window.jQuery) tasks.push(loadScript('https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js', 'jq-core'));
|
||
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net@1.13.10/js/jquery.dataTables.min.js', 'dt-core'));
|
||
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.10/js/dataTables.bootstrap5.min.js', 'dt-bs5'));
|
||
tasks.push(loadCss('https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.10/css/dataTables.bootstrap5.min.css', 'dt-bs5-css'));
|
||
}
|
||
if (!hasFH) {
|
||
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader@3.4.0/js/dataTables.fixedHeader.min.js', 'dt-fixedheader'));
|
||
tasks.push(loadCss('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader-bs5@3.4.0/css/fixedHeader.bootstrap5.min.css', 'dt-fixedheader-css'));
|
||
}
|
||
|
||
Promise.all(tasks)
|
||
.then(() => setTimeout(initDataTable, 0))
|
||
.catch(() => console.warn('DataTables assets failed to load; table will render without enhancements.'));
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', ensureDataTablesThenInit);
|
||
})();
|
||
|
||
// ---------- Add/Edit Note Modal ----------
|
||
document.addEventListener('click', function(e) {
|
||
const btn = e.target.closest('.add-note-btn');
|
||
if (!btn) return;
|
||
|
||
const modalEl = document.getElementById('notifyCommentModal');
|
||
if (!modalEl) return;
|
||
|
||
// Populate fields
|
||
document.getElementById('noteStudentId').value = btn.dataset.studentId || '';
|
||
document.getElementById('noteStudentName').textContent = btn.dataset.studentName || '—';
|
||
document.getElementById('noteCode').textContent = btn.dataset.code || '—';
|
||
document.getElementById('noteCodeInput').value = btn.dataset.code || '';
|
||
document.getElementById('noteIncidentDate').value= btn.dataset.incidentDate || '';
|
||
document.getElementById('noteDate').textContent = btn.dataset.incidentDate || '—';
|
||
document.getElementById('noteReturnUrl').value = btn.dataset.returnUrl || '<?= current_url() ?>';
|
||
document.getElementById('note').value = btn.dataset.note || '';
|
||
|
||
// Optional: clear extra meta
|
||
document.getElementById('noteTo').value = '';
|
||
document.getElementById('noteSubject').value = '';
|
||
document.getElementById('noteVariant').value = '';
|
||
|
||
if (window.bootstrap) {
|
||
new bootstrap.Modal(modalEl).show();
|
||
} else {
|
||
modalEl.classList.add('show');
|
||
}
|
||
});
|
||
|
||
(function autoOpenCommentModal(){
|
||
<?php $cm = session('open_comment_modal'); ?>
|
||
<?php if ($cm): ?>
|
||
const btn = document.createElement('button');
|
||
btn.type = 'button';
|
||
btn.className = 'd-none add-note-btn';
|
||
btn.dataset.studentId = '<?= (int)($cm['student_id'] ?? 0) ?>';
|
||
btn.dataset.studentName = '<?= esc($cm['student_name'] ?? '', 'js') ?>';
|
||
btn.dataset.code = '<?= esc($cm['code'] ?? '', 'js') ?>';
|
||
btn.dataset.incidentDate = '<?= esc(substr((string)($cm['incident_date'] ?? ''), 0, 10), 'js') ?>';
|
||
btn.dataset.note = '';
|
||
btn.dataset.returnUrl = '<?= current_url() ?>';
|
||
document.body.appendChild(btn);
|
||
btn.click();
|
||
document.body.removeChild(btn);
|
||
<?php session()->remove('open_comment_modal'); ?>
|
||
<?php endif; ?>
|
||
})();
|
||
</script>
|
||
|
||
<!-- Shared Parent Modal -->
|
||
<div class="modal fade" id="parentModal" tabindex="-1" aria-labelledby="parentModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="parentModalLabel">Parent Information</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div id="parentModalBody">
|
||
<div class="text-center text-muted my-4">
|
||
<div class="spinner-border" role="status" aria-hidden="true"></div>
|
||
<div class="mt-2">Loading parent info…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer justify-content-between">
|
||
<div class="small text-muted">First & Second contacts are shown if available.</div>
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?= $this->endSection() ?>
|