add trophy winner name print

This commit is contained in:
root
2026-05-30 03:58:59 -04:00
parent 079c869477
commit 89913d7473
+400 -24
View File
@@ -14,6 +14,47 @@ $totalConfirmed = array_sum(array_column($classResults, 'confirmed'));
$totalSurprise = array_sum(array_column($classResults, 'surprises')); $totalSurprise = array_sum(array_column($classResults, 'surprises'));
$totalMissed = array_sum(array_column($classResults, 'missed')); $totalMissed = array_sum(array_column($classResults, 'missed'));
$overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted * 100) : ($totalActual === 0 ? 100 : 0); $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted * 100) : ($totalActual === 0 ? 100 : 0);
// Winner gender breakdown.
// "Winner" means actual year-end trophy winner.
$totalWinnerBoys = 0;
$totalWinnerGirls = 0;
$totalWinnerOther = 0;
// Sticker names.
// 2 columns x 10 rows = 20 stickers per page.
// Stickers print NAME ONLY.
$winnerStickerNames = [];
foreach ($classResults as $cls) {
foreach (($cls['students'] ?? []) as $s) {
if (empty($s['actual'])) {
continue;
}
$gender = strtolower(trim((string)($s['gender'] ?? '')));
if (in_array($gender, ['male', 'm', 'boy', 'boys'], true)) {
$totalWinnerBoys++;
} elseif (in_array($gender, ['female', 'f', 'girl', 'girls'], true)) {
$totalWinnerGirls++;
} else {
$totalWinnerOther++;
}
$name = trim((string)($s['name'] ?? ''));
if ($name !== '') {
$winnerStickerNames[] = $name;
}
}
}
$totalWinners = $totalWinnerBoys + $totalWinnerGirls + $totalWinnerOther;
$winnerBoysPct = $totalWinners > 0 ? round(($totalWinnerBoys / $totalWinners) * 100, 1) : 0;
$winnerGirlsPct = $totalWinners > 0 ? round(($totalWinnerGirls / $totalWinners) * 100, 1) : 0;
$winnerOtherPct = $totalWinners > 0 ? round(($totalWinnerOther / $totalWinners) * 100, 1) : 0;
?> ?>
<style> <style>
@@ -23,23 +64,141 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
.status-none { color:#adb5bd; } .status-none { color:#adb5bd; }
.print-only { display: none !important; } .print-only { display: none !important; }
.winner-sticker-print-area { display: none; }
@page {
size: Letter portrait;
margin: 0.35in;
}
@media print { @media print {
.no-print { display: none !important; } .no-print { display: none !important; }
.print-only { display: block !important; } .print-only { display: block !important; }
.screen-only { display: none !important; } .screen-only { display: none !important; }
body { font-size: 11px; } body { font-size: 11px; }
.container-fluid { padding: 0 !important; } .container-fluid { padding: 0 !important; }
h2, h3 { font-size: 13px; margin-bottom: .3rem; } h2, h3 { font-size: 13px; margin-bottom: .3rem; }
.print-page-break { break-before: page; } .print-page-break { break-before: page; }
table { width: 100%; border-collapse: collapse; font-size: 10px; } table { width: 100%; border-collapse: collapse; font-size: 10px; }
table th, table td { border: 1px solid #bbb; padding: 3px 5px; } table th, table td { border: 1px solid #bbb; padding: 3px 5px; }
table thead { background: #333 !important; color: #fff !important; table thead {
-webkit-print-color-adjust: exact; print-color-adjust: exact; } background: #333 !important;
table thead th { position: static !important; top: auto !important; box-shadow: none !important; } color: #fff !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
table thead th {
position: static !important;
top: auto !important;
box-shadow: none !important;
}
.s-confirmed { color: #198754; font-weight: bold; } .s-confirmed { color: #198754; font-weight: bold; }
.s-surprise { color: #0d6efd; font-weight: bold; } .s-surprise { color: #0d6efd; font-weight: bold; }
.s-missed { color: #fd7e14; font-weight: bold; } .s-missed { color: #fd7e14; font-weight: bold; }
body.print-stickers-mode {
margin: 0 !important;
padding: 0 !important;
background: #fff !important;
}
body.print-stickers-mode * {
box-shadow: none !important;
}
body.print-stickers-mode header,
body.print-stickers-mode nav,
body.print-stickers-mode aside,
body.print-stickers-mode footer,
body.print-stickers-mode .navbar,
body.print-stickers-mode .sidebar,
body.print-stickers-mode .topbar,
body.print-stickers-mode .app-header,
body.print-stickers-mode .main-header,
body.print-stickers-mode .layout-header,
body.print-stickers-mode .management-header,
body.print-stickers-mode .page-header,
body.print-stickers-mode .breadcrumb,
body.print-stickers-mode .brand,
body.print-stickers-mode .logo,
body.print-stickers-mode .header,
body.print-stickers-mode .no-print,
body.print-stickers-mode .screen-only,
body.print-stickers-mode .print-only {
display: none !important;
}
body.print-stickers-mode .container-fluid > *:not(#winnerStickerPrintArea) {
display: none !important;
}
body.print-stickers-mode .container-fluid {
display: block !important;
visibility: visible !important;
padding: 0 !important;
margin: 0 !important;
width: 100% !important;
max-width: none !important;
}
body.print-stickers-mode #winnerStickerPrintArea {
display: block !important;
visibility: visible !important;
position: static !important;
width: 100% !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
overflow: visible !important;
}
body.print-stickers-mode #winnerStickerPrintArea,
body.print-stickers-mode #winnerStickerPrintArea * {
visibility: visible !important;
}
body.print-stickers-mode .sticker-page {
display: grid !important;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(10, 1fr);
gap: 0;
width: 100%;
height: 10.3in;
page-break-after: always;
break-after: page;
}
body.print-stickers-mode .sticker-page:last-child {
page-break-after: auto;
break-after: auto;
}
body.print-stickers-mode .sticker-cell {
display: flex !important;
justify-content: center;
align-items: center;
text-align: center;
border: none;
padding: 0;
overflow: hidden;
min-height: 0;
box-sizing: border-box;
}
body.print-stickers-mode .sticker-name {
display: block !important;
font-size: 20pt;
font-weight: 700;
line-height: 1.1;
color: #000 !important;
}
body.print-stickers-mode .sticker-empty {
visibility: hidden !important;
}
} }
</style> </style>
@@ -57,10 +216,16 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
with the <strong>year-end result</strong> based on the average of Fall &amp; Spring scores. with the <strong>year-end result</strong> based on the average of Fall &amp; Spring scores.
</p> </p>
</div> </div>
<div class="d-flex gap-2">
<div class="d-flex gap-2 flex-wrap">
<button onclick="printWithCharts()" class="btn btn-outline-secondary btn-sm"> <button onclick="printWithCharts()" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-printer-fill me-1"></i>Print <i class="bi bi-printer-fill me-1"></i>Print
</button> </button>
<button onclick="printWinnerStickers()" class="btn btn-warning btn-sm">
<i class="bi bi-tags-fill me-1"></i>Print Winner Stickers
</button>
<a href="<?= site_url('administrator/trophy?' . http_build_query(['school_year' => $selectedYear, 'percentile' => $selectedPercentile])) ?>" <a href="<?= site_url('administrator/trophy?' . http_build_query(['school_year' => $selectedYear, 'percentile' => $selectedPercentile])) ?>"
class="btn btn-outline-secondary btn-sm"> class="btn btn-outline-secondary btn-sm">
<i class="bi bi-arrow-left me-1"></i>Back <i class="bi bi-arrow-left me-1"></i>Back
@@ -75,18 +240,27 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<label class="form-label mb-1 small fw-semibold">School Year</label> <label class="form-label mb-1 small fw-semibold">School Year</label>
<select name="school_year" class="form-select form-select-sm" style="min-width:130px;"> <select name="school_year" class="form-select form-select-sm" style="min-width:130px;">
<?php foreach ($years as $yr): ?> <?php foreach ($years as $yr): ?>
<option value="<?= esc($yr) ?>" <?= $yr === $selectedYear ? 'selected' : '' ?>><?= esc($yr) ?></option> <option value="<?= esc($yr) ?>" <?= $yr === $selectedYear ? 'selected' : '' ?>>
<?= esc($yr) ?>
</option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<label class="form-label mb-1 small fw-semibold">Percentile</label> <label class="form-label mb-1 small fw-semibold">Percentile</label>
<div class="input-group input-group-sm" style="width:110px;"> <div class="input-group input-group-sm" style="width:110px;">
<input type="number" name="percentile" class="form-control" <input type="number"
min="1" max="99" step="1" value="<?= (int)$selectedPercentile ?>"> name="percentile"
class="form-control"
min="1"
max="99"
step="1"
value="<?= (int)$selectedPercentile ?>">
<span class="input-group-text">%</span> <span class="input-group-text">%</span>
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button type="submit" class="btn btn-primary btn-sm">Apply</button> <button type="submit" class="btn btn-primary btn-sm">Apply</button>
</div> </div>
@@ -147,19 +321,29 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<span class="fw-bold fs-6"><?= esc($cls['section_name']) ?></span> <span class="fw-bold fs-6"><?= esc($cls['section_name']) ?></span>
<span class="badge bg-primary"><?= $cls['predicted_count'] ?> predicted</span> <span class="badge bg-primary"><?= $cls['predicted_count'] ?> predicted</span>
<span class="badge bg-warning text-dark"><?= $cls['actual_count'] ?> actual</span> <span class="badge bg-warning text-dark"><?= $cls['actual_count'] ?> actual</span>
<?php if ($cls['confirmed'] > 0): ?> <?php if ($cls['confirmed'] > 0): ?>
<span class="badge bg-success"><?= $cls['confirmed'] ?> confirmed</span> <span class="badge bg-success"><?= $cls['confirmed'] ?> confirmed</span>
<?php endif; ?> <?php endif; ?>
<?php if ($cls['surprises'] > 0): ?> <?php if ($cls['surprises'] > 0): ?>
<span class="badge bg-info text-dark"><?= $cls['surprises'] ?> surprise<?= $cls['surprises'] > 1 ? 's' : '' ?></span> <span class="badge bg-info text-dark"><?= $cls['surprises'] ?> surprise<?= $cls['surprises'] > 1 ? 's' : '' ?></span>
<?php endif; ?> <?php endif; ?>
<?php if ($cls['missed'] > 0): ?> <?php if ($cls['missed'] > 0): ?>
<span class="badge" style="background:#fd7e14"><?= $cls['missed'] ?> missed</span> <span class="badge" style="background:#fd7e14"><?= $cls['missed'] ?> missed</span>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="d-flex gap-3 small align-items-center"> <div class="d-flex gap-3 small align-items-center">
<span class="text-muted">Fall &#8805; <strong><?= $cls['fall_threshold'] !== null ? number_format((float)$cls['fall_threshold'], 1) : '—' ?></strong></span> <span class="text-muted">
<span class="text-muted">Year &#8805; <strong><?= $cls['year_threshold'] !== null ? number_format((float)$cls['year_threshold'], 1) : '—' ?></strong></span> Fall &#8805;
<strong><?= $cls['fall_threshold'] !== null ? number_format((float)$cls['fall_threshold'], 1) : '—' ?></strong>
</span>
<span class="text-muted">
Year &#8805;
<strong><?= $cls['year_threshold'] !== null ? number_format((float)$cls['year_threshold'], 1) : '—' ?></strong>
</span>
<span class="fw-semibold"> <span class="fw-semibold">
Accuracy: Accuracy:
<span class="<?= $cls['accuracy'] >= 80 ? 'text-success' : ($cls['accuracy'] >= 50 ? 'text-warning' : 'text-danger') ?>"> <span class="<?= $cls['accuracy'] >= 80 ? 'text-success' : ($cls['accuracy'] >= 50 ? 'text-warning' : 'text-danger') ?>">
@@ -168,6 +352,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
</span> </span>
</div> </div>
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">
<table class="table table-sm table-hover mb-0 align-middle" data-no-mgmt-sticky> <table class="table table-sm table-hover mb-0 align-middle" data-no-mgmt-sticky>
<thead class="table-light"> <thead class="table-light">
@@ -183,11 +368,15 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<th class="text-center">Status</th> <th class="text-center">Status</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php $rank = 0; foreach ($cls['students'] as $s): <?php $rank = 0; foreach ($cls['students'] as $s):
if ($s['status'] === 'none') continue; if ($s['status'] === 'none') continue;
$rank++; $rank++;
$isMale = strtolower($s['gender'] ?? '') === 'male';
$genderNorm = strtolower(trim((string)($s['gender'] ?? '')));
$isMale = in_array($genderNorm, ['male', 'm', 'boy'], true);
$rowBg = match ($s['status']) { $rowBg = match ($s['status']) {
'confirmed' => 'table-success', 'confirmed' => 'table-success',
'surprise' => 'table-primary', 'surprise' => 'table-primary',
@@ -237,6 +426,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<div class="card-header bg-dark text-white fw-semibold py-2"> <div class="card-header bg-dark text-white fw-semibold py-2">
<i class="bi bi-bar-chart-fill me-2"></i>Prediction Accuracy Summary — <?= esc($selectedYear) ?> <i class="bi bi-bar-chart-fill me-2"></i>Prediction Accuracy Summary — <?= esc($selectedYear) ?>
</div> </div>
<div class="card-body p-0"> <div class="card-body p-0">
<table class="table table-sm table-bordered mb-0 align-middle" data-no-mgmt-sticky> <table class="table table-sm table-bordered mb-0 align-middle" data-no-mgmt-sticky>
<thead class="table-dark"> <thead class="table-dark">
@@ -253,6 +443,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<th class="text-end pe-2">Year ≥</th> <th class="text-end pe-2">Year ≥</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($classResults as $cls): ?> <?php foreach ($classResults as $cls): ?>
<tr> <tr>
@@ -271,6 +462,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
<tfoot class="table-secondary fw-semibold"> <tfoot class="table-secondary fw-semibold">
<tr> <tr>
<td class="ps-2">Total</td> <td class="ps-2">Total</td>
@@ -292,7 +484,6 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<!-- Charts --> <!-- Charts -->
<div class="row g-4 mt-1 mb-4"> <div class="row g-4 mt-1 mb-4">
<!-- Grouped bar: predicted / actual / confirmed / surprises / missed per class -->
<div class="col-12 col-lg-8"> <div class="col-12 col-lg-8">
<div class="border rounded p-3 h-100"> <div class="border rounded p-3 h-100">
<div class="small fw-semibold text-muted mb-2"> <div class="small fw-semibold text-muted mb-2">
@@ -301,7 +492,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<canvas id="chart-counts" style="max-height:280px;"></canvas> <canvas id="chart-counts" style="max-height:280px;"></canvas>
</div> </div>
</div> </div>
<!-- Bar: accuracy % per class + doughnut overall breakdown -->
<div class="col-12 col-lg-4"> <div class="col-12 col-lg-4">
<div class="row g-3 h-100"> <div class="row g-3 h-100">
<div class="col-12"> <div class="col-12">
@@ -312,6 +503,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<canvas id="chart-accuracy" style="max-height:130px;"></canvas> <canvas id="chart-accuracy" style="max-height:130px;"></canvas>
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="border rounded p-3"> <div class="border rounded p-3">
<div class="small fw-semibold text-muted mb-2"> <div class="small fw-semibold text-muted mb-2">
@@ -324,6 +516,93 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
</div> </div>
</div> </div>
<!-- Winner gender summary -->
<div class="card shadow-sm mt-2 mb-4">
<div class="card-header bg-dark text-white fw-semibold py-2">
<i class="bi bi-gender-ambiguous me-2"></i>Winner Gender Breakdown
</div>
<div class="card-body">
<div class="row g-3 align-items-stretch">
<div class="col-12 col-lg-3">
<div class="border rounded p-3 text-center h-100">
<div class="text-muted small mb-1">Total Winners</div>
<div class="display-6 fw-bold"><?= (int)$totalWinners ?></div>
<div class="small text-muted">Actual year-end trophy winners</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="border rounded p-3 text-center h-100">
<div class="text-muted small mb-1">Boys</div>
<div class="display-6 fw-bold text-primary"><?= (int)$totalWinnerBoys ?></div>
<div class="fw-semibold"><?= number_format($winnerBoysPct, 1) ?>%</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="border rounded p-3 text-center h-100">
<div class="text-muted small mb-1">Girls</div>
<div class="display-6 fw-bold" style="color:#E47AB0;"><?= (int)$totalWinnerGirls ?></div>
<div class="fw-semibold"><?= number_format($winnerGirlsPct, 1) ?>%</div>
</div>
</div>
<div class="col-12 col-lg-3">
<div class="border rounded p-3 text-center h-100">
<div class="text-muted small mb-1">Unknown / Other</div>
<div class="display-6 fw-bold text-secondary"><?= (int)$totalWinnerOther ?></div>
<div class="fw-semibold"><?= number_format($winnerOtherPct, 1) ?>%</div>
</div>
</div>
</div>
<div class="mt-3">
<div class="progress" style="height:26px;">
<?php if ($totalWinners > 0): ?>
<div class="progress-bar bg-primary"
role="progressbar"
style="width: <?= $winnerBoysPct ?>%;"
aria-valuenow="<?= $winnerBoysPct ?>"
aria-valuemin="0"
aria-valuemax="100">
Boys <?= number_format($winnerBoysPct, 1) ?>%
</div>
<div class="progress-bar"
role="progressbar"
style="width: <?= $winnerGirlsPct ?>%; background:#E47AB0;"
aria-valuenow="<?= $winnerGirlsPct ?>"
aria-valuemin="0"
aria-valuemax="100">
Girls <?= number_format($winnerGirlsPct, 1) ?>%
</div>
<?php if ($totalWinnerOther > 0): ?>
<div class="progress-bar bg-secondary"
role="progressbar"
style="width: <?= $winnerOtherPct ?>%;"
aria-valuenow="<?= $winnerOtherPct ?>"
aria-valuemin="0"
aria-valuemax="100">
Other <?= number_format($winnerOtherPct, 1) ?>%
</div>
<?php endif; ?>
<?php else: ?>
<div class="progress-bar bg-secondary"
role="progressbar"
style="width: 100%;"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100">
No winners
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div><!-- /screen-only --> </div><!-- /screen-only -->
<!-- ══ PRINT VIEW ════════════════════════════════════════════════════════ --> <!-- ══ PRINT VIEW ════════════════════════════════════════════════════════ -->
@@ -340,7 +619,6 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
</p> </p>
</div> </div>
<!-- All students flat table -->
<h3 style="margin-bottom:4px;">Student Detail</h3> <h3 style="margin-bottom:4px;">Student Detail</h3>
<table data-no-mgmt-sticky> <table data-no-mgmt-sticky>
<thead> <thead>
@@ -357,6 +635,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<th style="text-align:center;">Status</th> <th style="text-align:center;">Status</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php <?php
$rank = 0; $rank = 0;
@@ -364,7 +643,8 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
foreach ($cls['students'] as $s): foreach ($cls['students'] as $s):
if (!in_array($s['status'], ['confirmed', 'surprise'], true)) continue; if (!in_array($s['status'], ['confirmed', 'surprise'], true)) continue;
$rank++; $rank++;
$isMale = strtolower($s['gender'] ?? '') === 'male'; $genderNorm = strtolower(trim((string)($s['gender'] ?? '')));
$isMale = in_array($genderNorm, ['male', 'm', 'boy'], true);
$statusLabel = match ($s['status']) { $statusLabel = match ($s['status']) {
'confirmed' => '✓ Confirmed', 'confirmed' => '✓ Confirmed',
'surprise' => '↑ Surprise', 'surprise' => '↑ Surprise',
@@ -389,7 +669,6 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
</tbody> </tbody>
</table> </table>
<!-- Accuracy summary (new page) -->
<div class="print-page-break"></div> <div class="print-page-break"></div>
<h3 style="margin-bottom:4px;">Prediction Accuracy Summary</h3> <h3 style="margin-bottom:4px;">Prediction Accuracy Summary</h3>
<table data-no-mgmt-sticky style="margin-bottom:14px;"> <table data-no-mgmt-sticky style="margin-bottom:14px;">
@@ -407,6 +686,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
<th style="text-align:right;">Year ≥</th> <th style="text-align:right;">Year ≥</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($classResults as $cls): ?> <?php foreach ($classResults as $cls): ?>
<tr> <tr>
@@ -423,6 +703,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td style="font-weight:bold;">Total</td> <td style="font-weight:bold;">Total</td>
@@ -438,13 +719,14 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
</tfoot> </tfoot>
</table> </table>
<!-- Charts as images (populated by JS before printing) -->
<div class="print-page-break"></div> <div class="print-page-break"></div>
<h3 style="margin-bottom:6px;">Charts</h3> <h3 style="margin-bottom:6px;">Charts</h3>
<div style="margin-bottom:14px;"> <div style="margin-bottom:14px;">
<p style="font-size:10px;font-weight:bold;margin:0 0 4px;">Trophy Counts per Class</p> <p style="font-size:10px;font-weight:bold;margin:0 0 4px;">Trophy Counts per Class</p>
<img id="print-chart-counts" style="width:100%;max-height:220px;object-fit:contain;" src="" alt=""> <img id="print-chart-counts" style="width:100%;max-height:220px;object-fit:contain;" src="" alt="">
</div> </div>
<div style="display:flex;gap:16px;margin-bottom:14px;"> <div style="display:flex;gap:16px;margin-bottom:14px;">
<div style="flex:1;"> <div style="flex:1;">
<p style="font-size:10px;font-weight:bold;margin:0 0 4px;">Prediction Accuracy per Class</p> <p style="font-size:10px;font-weight:bold;margin:0 0 4px;">Prediction Accuracy per Class</p>
@@ -456,8 +738,69 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
</div> </div>
</div> </div>
<div style="margin-top:10px;border:1px solid #bbb;padding:8px;">
<p style="font-size:11px;font-weight:bold;margin:0 0 6px;">Winner Gender Breakdown</p>
<table data-no-mgmt-sticky>
<thead>
<tr>
<th>Group</th>
<th style="text-align:center;">Winners</th>
<th style="text-align:center;">Percentage</th>
</tr>
</thead>
<tbody>
<tr>
<td>Boys</td>
<td style="text-align:center;"><?= (int)$totalWinnerBoys ?></td>
<td style="text-align:center;"><?= number_format($winnerBoysPct, 1) ?>%</td>
</tr>
<tr>
<td>Girls</td>
<td style="text-align:center;"><?= (int)$totalWinnerGirls ?></td>
<td style="text-align:center;"><?= number_format($winnerGirlsPct, 1) ?>%</td>
</tr>
<?php if ($totalWinnerOther > 0): ?>
<tr>
<td>Unknown / Other</td>
<td style="text-align:center;"><?= (int)$totalWinnerOther ?></td>
<td style="text-align:center;"><?= number_format($winnerOtherPct, 1) ?>%</td>
</tr>
<?php endif; ?>
</tbody>
<tfoot>
<tr>
<td style="font-weight:bold;">Total Winners</td>
<td style="text-align:center;font-weight:bold;"><?= (int)$totalWinners ?></td>
<td style="text-align:center;font-weight:bold;"><?= $totalWinners > 0 ? '100.0%' : '0.0%' ?></td>
</tr>
</tfoot>
</table>
</div>
</div><!-- /print-only --> </div><!-- /print-only -->
<!-- Winner sticker print area: names only, no header, no score, no class -->
<div id="winnerStickerPrintArea" class="winner-sticker-print-area">
<?php if (!empty($winnerStickerNames)): ?>
<?php foreach (array_chunk($winnerStickerNames, 20) as $chunk): ?>
<div class="sticker-page">
<?php foreach ($chunk as $winnerName): ?>
<div class="sticker-cell">
<div class="sticker-name"><?= esc($winnerName) ?></div>
</div>
<?php endforeach; ?>
<?php for ($i = 0, $remaining = 20 - count($chunk); $i < $remaining; $i++): ?>
<div class="sticker-cell sticker-empty"></div>
<?php endfor; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php endif; ?> <?php endif; ?>
</div> </div>
@@ -473,6 +816,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
$cSurprise = []; $cSurprise = [];
$cMissed = []; $cMissed = [];
$cAccuracy = []; $cAccuracy = [];
foreach ($classResults as $cls) { foreach ($classResults as $cls) {
$cLabels[] = $cls['section_name']; $cLabels[] = $cls['section_name'];
$cPredicted[] = $cls['predicted_count']; $cPredicted[] = $cls['predicted_count'];
@@ -483,6 +827,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
$cAccuracy[] = $cls['accuracy']; $cAccuracy[] = $cls['accuracy'];
} }
?> ?>
var labels = <?= json_encode($cLabels) ?>; var labels = <?= json_encode($cLabels) ?>;
var predicted = <?= json_encode($cPredicted) ?>; var predicted = <?= json_encode($cPredicted) ?>;
var actual = <?= json_encode($cActual) ?>; var actual = <?= json_encode($cActual) ?>;
@@ -491,7 +836,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
var missed = <?= json_encode($cMissed) ?>; var missed = <?= json_encode($cMissed) ?>;
var accuracy = <?= json_encode($cAccuracy) ?>; var accuracy = <?= json_encode($cAccuracy) ?>;
/* ── Chart 1: grouped bar counts per class ── */ if (document.getElementById('chart-counts')) {
new Chart(document.getElementById('chart-counts'), { new Chart(document.getElementById('chart-counts'), {
type: 'bar', type: 'bar',
data: { data: {
@@ -514,8 +859,9 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
} }
} }
}); });
}
/* ── Chart 2: accuracy % per class ── */ if (document.getElementById('chart-accuracy')) {
new Chart(document.getElementById('chart-accuracy'), { new Chart(document.getElementById('chart-accuracy'), {
type: 'bar', type: 'bar',
data: { data: {
@@ -535,15 +881,21 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
plugins: { legend: { display: false } }, plugins: { legend: { display: false } },
scales: { scales: {
y: { y: {
beginAtZero: true, max: 100, beginAtZero: true,
ticks: { callback: function(v) { return v + '%'; } }, max: 100,
ticks: {
callback: function(v) {
return v + '%';
}
},
grid: { color: '#f0f0f0' } grid: { color: '#f0f0f0' }
} }
} }
} }
}); });
}
/* ── Chart 3: doughnut overall outcome breakdown ── */ if (document.getElementById('chart-breakdown')) {
new Chart(document.getElementById('chart-breakdown'), { new Chart(document.getElementById('chart-breakdown'), {
type: 'doughnut', type: 'doughnut',
data: { data: {
@@ -562,7 +914,9 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
tooltip: { tooltip: {
callbacks: { callbacks: {
label: function(ctx) { label: function(ctx) {
var total = ctx.dataset.data.reduce(function(a, b) { return a + b; }, 0); var total = ctx.dataset.data.reduce(function(a, b) {
return a + b;
}, 0);
var pct = total > 0 ? Math.round(ctx.parsed / total * 100) : 0; var pct = total > 0 ? Math.round(ctx.parsed / total * 100) : 0;
return ctx.label + ': ' + ctx.parsed + ' (' + pct + '%)'; return ctx.label + ': ' + ctx.parsed + ' (' + pct + '%)';
} }
@@ -571,6 +925,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
} }
} }
}); });
}
})(); })();
function captureCharts() { function captureCharts() {
@@ -579,19 +934,40 @@ function captureCharts() {
'chart-accuracy': 'print-chart-accuracy', 'chart-accuracy': 'print-chart-accuracy',
'chart-breakdown': 'print-chart-breakdown', 'chart-breakdown': 'print-chart-breakdown',
}; };
Object.keys(map).forEach(function(canvasId) { Object.keys(map).forEach(function(canvasId) {
var canvas = document.getElementById(canvasId); var canvas = document.getElementById(canvasId);
var img = document.getElementById(map[canvasId]); var img = document.getElementById(map[canvasId]);
if (canvas && img) img.src = canvas.toDataURL('image/png');
if (canvas && img) {
img.src = canvas.toDataURL('image/png');
}
}); });
} }
function printWithCharts() { function printWithCharts() {
document.body.classList.remove('print-stickers-mode');
captureCharts(); captureCharts();
window.print(); window.print();
} }
window.addEventListener('beforeprint', captureCharts); function printWinnerStickers() {
document.body.classList.add('print-stickers-mode');
setTimeout(function () {
window.print();
setTimeout(function () {
document.body.classList.remove('print-stickers-mode');
}, 1000);
}, 250);
}
window.addEventListener('beforeprint', function () {
if (!document.body.classList.contains('print-stickers-mode')) {
captureCharts();
}
});
</script> </script>
<?= $this->endSection() ?> <?= $this->endSection() ?>