recreate project
This commit is contained in:
@@ -0,0 +1,582 @@
|
||||
<?php
|
||||
// Trophy prediction label (chance to win trophy)
|
||||
function trophyChanceLabel($risk)
|
||||
{
|
||||
return match ($risk) {
|
||||
'Achieved Trophy' => '<span class="text-success fw-bold">🏅 Trophy Achieved</span>',
|
||||
'Top 3 Trophy' => '<span class="text-primary fw-bold">🥇 Top 3 Trophy</span>',
|
||||
'Low' => '<span class="text-success fw-bold">🏆 Trophy within Reach</span>',
|
||||
'Medium' => '<span class="text-warning fw-bold">⚠️ Needs Strong Effort</span>',
|
||||
'High' => '<span class="text-danger fw-bold">❌ Unlikely to Win Trophy</span>',
|
||||
'Unreachable' => '<span class="text-dark fw-bold bg-warning px-2 rounded">❌ Not Possible</span>',
|
||||
'New student' => '<span class="text-muted fst-italic">⏳ No Data Yet</span>',
|
||||
default => '<span class="text-body">Unknown</span>',
|
||||
};
|
||||
}
|
||||
|
||||
// Fail risk label (chance of failing)
|
||||
function failRiskLabel($risk)
|
||||
{
|
||||
return match ($risk) {
|
||||
'Low' => '<span class="text-success fw-bold">✅ Safe</span>',
|
||||
'Medium' => '<span class="text-warning fw-bold">⚠️ Some Risk</span>',
|
||||
'High' => '<span class="text-danger fw-bold">❗ At Risk</span>',
|
||||
'Unlikely to pass' => '<span class="text-white fw-bold bg-danger px-2 rounded">❌ Cannot Pass</span>',
|
||||
'New student' => '<span class="text-muted fst-italic">⏳ No Data Yet</span>',
|
||||
default => '<span class="text-body">Unknown</span>',
|
||||
};
|
||||
}
|
||||
|
||||
// Status badge (pass/fail/undetermined)
|
||||
function statusBadge($status)
|
||||
{
|
||||
return match ($status) {
|
||||
'Passed' => '<span class="badge bg-success">✅ Passed</span>',
|
||||
'Failed' => '<span class="badge bg-danger">❌ Failed</span>',
|
||||
'Can pass' => '<span class="badge bg-primary">✅ Can pass</span>',
|
||||
'May fail' => '<span class="badge bg-warning text-dark">⚠️ May fail</span>',
|
||||
'Undetermined' => '<span class="badge bg-secondary">⏳ Undetermined</span>',
|
||||
default => '<span class="badge bg-light text-dark">N/A</span>',
|
||||
};
|
||||
}
|
||||
|
||||
// Helper to output safe numeric (for data-order); returns '' for non-numeric/null
|
||||
function orderNum($v)
|
||||
{
|
||||
return is_numeric($v) ? (float)$v : '';
|
||||
}
|
||||
?>
|
||||
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="container-fluid"><!-- full width page -->
|
||||
<h2 class="text-center mt-4 mb-3">Student Success & Risk Report</h2>
|
||||
|
||||
<?php
|
||||
// Build class section map and trophy counts
|
||||
$sectionMap = [];
|
||||
if (!empty($classSections) && is_array($classSections)) {
|
||||
foreach ($classSections as $sec) {
|
||||
$sectionMap[(string)($sec['class_section_id'] ?? '')] = $sec['class_section_name'] ?? ('Section ' . ($sec['class_section_id'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
$trophyCounts = [];
|
||||
$totalTrophies = 0;
|
||||
if (!empty($students) && is_array($students)) {
|
||||
foreach ($students as $s) {
|
||||
if (!empty($s['trophy_awarded'])) {
|
||||
$cid = (string)($s['class_section_id'] ?? '');
|
||||
if (!isset($trophyCounts[$cid])) $trophyCounts[$cid] = 0;
|
||||
$trophyCounts[$cid]++;
|
||||
$totalTrophies++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build status + risk breakdowns for charts
|
||||
$statusCounts = ['Passed' => 0, 'Failed' => 0, 'Can pass' => 0, 'May fail' => 0, 'Undetermined' => 0];
|
||||
$riskBuckets = ['Low' => 0, 'Medium' => 0, 'High' => 0, 'Unlikely to pass' => 0, 'New student' => 0];
|
||||
foreach ($students as $s) {
|
||||
$status = $s['status'] ?? 'Undetermined';
|
||||
if (isset($statusCounts[$status])) {
|
||||
$statusCounts[$status]++;
|
||||
} else {
|
||||
$statusCounts['Undetermined']++;
|
||||
}
|
||||
|
||||
$risk = $s['failure_risk'] ?? 'New student';
|
||||
if (isset($riskBuckets[$risk])) {
|
||||
$riskBuckets[$risk]++;
|
||||
} else {
|
||||
$riskBuckets['New student']++;
|
||||
}
|
||||
}
|
||||
$totalStudents = array_sum($statusCounts);
|
||||
$passRate = $totalStudents > 0 ? round(($statusCounts['Passed'] / $totalStudents) * 100, 1) : 0;
|
||||
$failRate = $totalStudents > 0 ? round(($statusCounts['Failed'] / $totalStudents) * 100, 1) : 0;
|
||||
$pendingCount = $statusCounts['Undetermined'] + $statusCounts['Can pass'] + $statusCounts['May fail'];
|
||||
$undeterminedRate = $totalStudents > 0 ? round(($pendingCount / $totalStudents) * 100, 1) : 0;
|
||||
?>
|
||||
|
||||
<div class="mb-4 text-center">
|
||||
<p class="text-muted small mb-1">Active Students</p>
|
||||
<div class="display-4 fw-semibold"><?= (int)$totalStudents ?></div>
|
||||
<p class="text-muted small mb-0">Reflects only currently active students, so removals update the count instantly.</p>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-3 mb-4 align-items-end">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<label for="school_year" class="form-label">School Year</label>
|
||||
<input type="text" class="form-control" id="school_year" name="school_year" value="<?= esc($school_year) ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<label for="class_section_id" class="form-label">Class Section</label>
|
||||
<div class="input-group">
|
||||
<select class="form-select" name="class_section_id" id="class_section_id">
|
||||
<option value="">-- All Sections --</option>
|
||||
<?php foreach ($classSections as $section): ?>
|
||||
<option value="<?= esc($section['class_section_id']) ?>" <?= (string)$selectedClassSectionId === (string)$section['class_section_id'] ? 'selected' : '' ?>>
|
||||
<?= esc($section['class_section_name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary">Filter</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header py-2 d-flex justify-content-between align-items-center">
|
||||
<strong>Active Student Snapshot</strong>
|
||||
<span class="badge bg-primary"><?= (int)$totalStudents ?> active</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<div class="flex-fill">
|
||||
<div class="small text-muted">Pass rate</div>
|
||||
<div class="fs-3 text-success fw-semibold"><?= $passRate ?>%</div>
|
||||
<div class="text-muted small"><?= (int)$statusCounts['Passed'] ?> students</div>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="small text-muted">Failing</div>
|
||||
<div class="fs-4 text-danger fw-semibold"><?= (int)$statusCounts['Failed'] ?></div>
|
||||
<div class="text-muted small"><?= $failRate ?>% of roster</div>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="small text-muted">Pending / no data</div>
|
||||
<div class="fs-4 text-secondary fw-semibold"><?= (int)$pendingCount ?></div>
|
||||
<div class="text-muted small"><?= $undeterminedRate ?>% of roster</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<p class="mb-1 small text-muted">Analyst notes</p>
|
||||
<ul class="mb-0 small">
|
||||
<li>Focus on <?= (int)$riskBuckets['High'] ?> high-risk students; prioritize their action plans.</li>
|
||||
<li><?= (int)$statusCounts['Failed'] ?> students are currently failing—pair them with tutoring or parental outreach.</li>
|
||||
<li><?= (int)$totalTrophies ?> trophy-eligible students; celebrate them to boost morale.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header py-2"><strong>Pass vs Fail</strong></div>
|
||||
<div class="card-body">
|
||||
<canvas id="passFailChart" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header py-2"><strong>Fail Risk Mix</strong></div>
|
||||
<div class="card-body">
|
||||
<canvas id="riskChart" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header py-2">
|
||||
<strong>Trophies Summary</strong>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="small text-muted mb-2">
|
||||
If a class section has no students meeting the trophy threshold, the top 3 ranked students in that section receive trophies.
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-striped mb-2 text-center align-middle no-mgmt-sticky" data-no-mgmt-sticky="1">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="text-start">Class Section</th>
|
||||
<?php if (!empty($trophyCounts)): ?>
|
||||
<?php
|
||||
// Order summary by class section name ascending (A–Z)
|
||||
$orderCids = array_keys($trophyCounts);
|
||||
usort($orderCids, function ($a, $b) use ($sectionMap) {
|
||||
$na = $sectionMap[(string)$a] ?? (string)$a;
|
||||
$nb = $sectionMap[(string)$b] ?? (string)$b;
|
||||
return strcasecmp($na, $nb);
|
||||
});
|
||||
?>
|
||||
<?php foreach ($orderCids as $cid): ?>
|
||||
<th><?= esc($sectionMap[(string)$cid] ?? ('Section ' . $cid)) ?></th>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<th class="text-muted fst-italic">No trophies yet.</th>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($trophyCounts)): ?>
|
||||
<!-- Trophy count row -->
|
||||
<tr>
|
||||
<th class="text-start">Trophy Counts</th>
|
||||
<?php foreach ($orderCids as $cid): ?>
|
||||
<td><?= (int)$trophyCounts[$cid] ?></td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
|
||||
<!-- Total row -->
|
||||
<tr class="fw-bold table-light">
|
||||
<th class="text-start">Total Trophies</th>
|
||||
<td colspan="<?= count($trophyCounts) ?>" class="text-center">
|
||||
<?= (int)$totalTrophies ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="2" class="text-muted fst-italic">No data available</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive"><!-- keeps header styling + horizontal scroll -->
|
||||
<table id="reportTable" class="table table-bordered table-striped align-middle w-100 no-mgmt-sticky no-dt-fixedheader" data-no-mgmt-sticky="1">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th style="width:60px">#</th>
|
||||
<th>School ID</th>
|
||||
<th>First Name</th>
|
||||
<th>Last Name</th>
|
||||
<th>Class</th>
|
||||
<th>Fall Score</th>
|
||||
<th>Spring Score</th>
|
||||
<th>Final Average</th>
|
||||
<th>Needed for Trophy</th>
|
||||
<th>Chance to Win Trophy</th>
|
||||
<th>Needed to Pass (60+)</th>
|
||||
<th>Risk of Failing</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $i = 1;
|
||||
foreach ($students as $student): ?>
|
||||
<tr>
|
||||
<td class="row-index"><?= $i++ ?></td>
|
||||
<td><?= esc($student['school_id']) ?></td>
|
||||
<td>
|
||||
<?php $sid = (int)($student['student_id'] ?? 0); ?>
|
||||
<?php if ($sid): ?>
|
||||
<a href="#" class="text-decoration-none" data-family-student-id="<?= $sid ?>"><?= esc($student['firstname']) ?></a>
|
||||
<?php else: ?>
|
||||
<?= esc($student['firstname']) ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!empty($sid)): ?>
|
||||
<a href="#" class="text-decoration-none" data-family-student-id="<?= $sid ?>"><?= esc($student['lastname']) ?></a>
|
||||
<?php else: ?>
|
||||
<?= esc($student['lastname']) ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
||||
<?php
|
||||
$cid = (string)($student['class_section_id'] ?? '');
|
||||
$className = $sectionMap[$cid] ?? (!empty($cid) ? ('Section ' . $cid) : '—');
|
||||
?>
|
||||
<td data-order="<?= esc($className) ?>"><?= esc($className) ?></td>
|
||||
|
||||
<!-- Numeric columns with data-order to guarantee correct sorting -->
|
||||
<td data-order="<?= orderNum($student['fall_score']) ?>">
|
||||
<?= esc($student['fall_score']) ?>
|
||||
</td>
|
||||
<td data-order="<?= orderNum($student['spring_score']) ?>">
|
||||
<?= esc($student['spring_score']) ?>
|
||||
</td>
|
||||
<td data-order="<?= orderNum($student['final_average']) ?>">
|
||||
<?= esc($student['final_average']) ?>
|
||||
</td>
|
||||
<td data-order="<?= orderNum($student['required_spring_score']) ?>">
|
||||
<?= esc($student['required_spring_score']) ?>
|
||||
</td>
|
||||
|
||||
<!-- HTML/badge columns (not sortable) -->
|
||||
<td><?= trophyChanceLabel($student['winning_risk']) ?></td>
|
||||
|
||||
<td data-order="<?= orderNum($student['required_spring_to_pass']) ?>">
|
||||
<?= esc($student['required_spring_to_pass']) ?>
|
||||
</td>
|
||||
|
||||
<td><?= failRiskLabel($student['failure_risk']) ?></td>
|
||||
<td><?= statusBadge($student['status']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<style>
|
||||
.table-dark th {
|
||||
background-color: #343a40;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dataTables_wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#reportTable {
|
||||
width: 100% !important;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* Prevent header text from overlapping rows; allow wrapping */
|
||||
table.dataTable thead th,
|
||||
table.dataTable thead td {
|
||||
white-space: normal !important;
|
||||
line-height: 1.25;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
/* Ensure body rows have enough height and don't overlap */
|
||||
#reportTable tbody th,
|
||||
#reportTable tbody td {
|
||||
white-space: normal;
|
||||
line-height: 1.35;
|
||||
vertical-align: middle;
|
||||
padding-top: .5rem;
|
||||
padding-bottom: .5rem;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
/* With scrollX, keep default overflow and remove table margins to prevent overlap */
|
||||
.dataTables_wrapper .dataTables_scrollHead table {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_scrollBody table {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
/* Subtle separation so first data row doesn't appear under the header */
|
||||
.dataTables_wrapper .dataTables_scrollBody {
|
||||
padding-top: 4px; /* subtle separation from header */
|
||||
}
|
||||
|
||||
/* Extra separation between header and body containers */
|
||||
.dataTables_wrapper .dataTables_scrollHead {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* Hard override: never apply sticky headers to DataTables-managed tables */
|
||||
.dataTables_wrapper table thead th,
|
||||
.dataTables_wrapper table thead td {
|
||||
position: static !important;
|
||||
top: auto !important;
|
||||
z-index: auto !important;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter input {
|
||||
width: 16em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- DataTables assets are already loaded in management_layout; avoid duplicate includes here. -->
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
const statusCounts = <?= json_encode($statusCounts) ?>;
|
||||
const riskBuckets = <?= json_encode($riskBuckets) ?>;
|
||||
|
||||
function ready(fn) {
|
||||
if (document.readyState !== 'loading') fn();
|
||||
else document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
|
||||
function renderCharts() {
|
||||
if (!window.Chart) return false;
|
||||
|
||||
const passFailEl = document.getElementById('passFailChart');
|
||||
const riskEl = document.getElementById('riskChart');
|
||||
if (!passFailEl || !riskEl) return true;
|
||||
|
||||
const pfCtx = passFailEl.getContext('2d');
|
||||
const riskCtx = riskEl.getContext('2d');
|
||||
|
||||
if (window._passFailChart) window._passFailChart.destroy();
|
||||
if (window._riskChart) window._riskChart.destroy();
|
||||
|
||||
window._passFailChart = new Chart(pfCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: Object.keys(statusCounts),
|
||||
datasets: [{
|
||||
data: Object.values(statusCounts),
|
||||
backgroundColor: ['#198754', '#dc3545', '#0d6efd', '#ffc107', '#6c757d'],
|
||||
borderWidth: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
legend: { position: 'bottom' }
|
||||
},
|
||||
responsive: true,
|
||||
cutout: '65%'
|
||||
}
|
||||
});
|
||||
|
||||
window._riskChart = new Chart(riskCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: Object.keys(riskBuckets),
|
||||
datasets: [{
|
||||
label: 'Students',
|
||||
data: Object.values(riskBuckets),
|
||||
backgroundColor: ['#198754', '#ffc107', '#dc3545', '#6610f2', '#adb5bd']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: { beginAtZero: true, ticks: { precision: 0 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
ready(function() {
|
||||
if (renderCharts()) return;
|
||||
var tries = 0,
|
||||
timer = setInterval(function() {
|
||||
tries++;
|
||||
if (renderCharts() || tries > 20) clearInterval(timer);
|
||||
}, 100);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Wait until jQuery + DataTables are actually available, then init once.
|
||||
function ready(fn) {
|
||||
if (document.readyState !== 'loading') fn();
|
||||
else document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
|
||||
function initReport() {
|
||||
if (!window.jQuery || !jQuery.fn || !jQuery.fn.dataTable) return false;
|
||||
|
||||
var $ = jQuery;
|
||||
var $table = $('#reportTable');
|
||||
if (!$table.length) return true; // nothing to do
|
||||
|
||||
// Re-init safety
|
||||
if ($.fn.dataTable.isDataTable($table[0])) {
|
||||
$table.DataTable().destroy();
|
||||
}
|
||||
|
||||
$.fn.dataTable.ext.errMode = 'console';
|
||||
|
||||
var dt = $table.DataTable({
|
||||
stateSave: true,
|
||||
pageLength: 100,
|
||||
lengthMenu: [10, 25, 50, 100],
|
||||
scrollX: true,
|
||||
scrollCollapse: true,
|
||||
autoWidth: true,
|
||||
orderCellsTop: true,
|
||||
fixedHeader: false,
|
||||
// Sort by Last Name (col 3), then First Name (col 2)
|
||||
order: [
|
||||
[3, 'asc'],
|
||||
[2, 'asc']
|
||||
],
|
||||
columnDefs: [{
|
||||
targets: 0,
|
||||
orderable: false,
|
||||
searchable: false
|
||||
}, // row #
|
||||
{
|
||||
targets: [9, 11, 12],
|
||||
orderable: false
|
||||
}, // Trophy, Fail Risk, Status
|
||||
// Treat numeric columns as numbers (DataTables also respects data-order attr)
|
||||
{
|
||||
targets: [5, 6, 7, 8, 10],
|
||||
type: 'num'
|
||||
}
|
||||
],
|
||||
dom: '<"row mb-2"<"col-sm-6"l><"col-sm-6"f>>t<"row mt-2"<"col-sm-6"i><"col-sm-6"p>>'
|
||||
});
|
||||
|
||||
// Re-number the first column on sort/search/paginate
|
||||
$table.on('order.dt search.dt draw.dt', function() {
|
||||
var info = dt.page.info();
|
||||
dt.column(0, {
|
||||
search: 'applied',
|
||||
order: 'applied',
|
||||
page: 'current'
|
||||
})
|
||||
.nodes()
|
||||
.each(function(cell, i) {
|
||||
cell.innerHTML = i + 1 + info.start;
|
||||
});
|
||||
});
|
||||
|
||||
// Initial numbering + minor layout adjust
|
||||
dt.draw(false);
|
||||
// Adjust columns after initial render and once fonts/assets settle
|
||||
setTimeout(function() {
|
||||
dt.columns.adjust().draw(false);
|
||||
}, 0);
|
||||
setTimeout(function() {
|
||||
dt.columns.adjust().draw(false);
|
||||
}, 200);
|
||||
setTimeout(function() {
|
||||
dt.columns.adjust().draw(false);
|
||||
}, 600);
|
||||
|
||||
// One more pass on next frame for browsers that lay out fonts asynchronously
|
||||
if (window.requestAnimationFrame) {
|
||||
requestAnimationFrame(function(){
|
||||
dt.columns.adjust().draw(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Also adjust on window load/resize to avoid header/body misalignment
|
||||
window.addEventListener('load', function() {
|
||||
dt.columns.adjust().draw(false);
|
||||
});
|
||||
window.addEventListener('resize', function() {
|
||||
dt.columns.adjust().draw(false);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ready(function() {
|
||||
// Try immediately; if assets are deferred, retry a few times.
|
||||
if (initReport()) return;
|
||||
var tries = 0,
|
||||
timer = setInterval(function() {
|
||||
tries++;
|
||||
if (initReport() || tries > 20) clearInterval(timer); // ~2s max
|
||||
}, 100);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
Reference in New Issue
Block a user