diff --git a/app/Views/administrator/trophy_final.php b/app/Views/administrator/trophy_final.php index 12aa98f..9f76843 100644 --- a/app/Views/administrator/trophy_final.php +++ b/app/Views/administrator/trophy_final.php @@ -14,6 +14,47 @@ $totalConfirmed = array_sum(array_column($classResults, 'confirmed')); $totalSurprise = array_sum(array_column($classResults, 'surprises')); $totalMissed = array_sum(array_column($classResults, 'missed')); $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; ?> @@ -57,10 +216,16 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted with the year-end result based on the average of Fall & Spring scores.

-
+ +
+ + + Back @@ -75,18 +240,27 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
- + %
+
@@ -111,7 +285,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
0 ? round($totalPredicted / $totalStudents * 100) . '% of students' : '—'], - [$totalActual, 'Actual (Year)', 'warning', 'dark', $totalStudents > 0 ? round($totalActual / $totalStudents * 100) . '% of students' : '—'], + [$totalActual, 'Actual (Year)', 'warning', 'dark', $totalStudents > 0 ? round($totalActual / $totalStudents * 100) . '% of students' : '—'], [$totalConfirmed, 'Confirmed', 'success', null, $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted * 100) . '% of predicted' : '—'], [$totalSurprise, 'Surprises', 'info', 'dark', 'Not in prediction'], [$totalMissed, 'Missed', 'orange', null, 'Were predicted'], @@ -147,19 +321,29 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted predicted actual + 0): ?> confirmed + 0): ?> surprise 1 ? 's' : '' ?> + 0): ?> missed
+
- Fall ≥ - Year ≥ + + Fall ≥ + + + + Year ≥ + + Accuracy: @@ -168,6 +352,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
@@ -183,12 +368,16 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted + 'table-success', 'surprise' => 'table-primary', 'missed' => 'table-warning', @@ -203,9 +392,9 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted - + - +
Status
—' ?>—' ?> —' ?>—' ?>—' ?> Yes' @@ -237,6 +426,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
Prediction Accuracy Summary —
+
@@ -253,6 +443,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted + @@ -271,6 +462,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted + @@ -292,7 +484,6 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
-
@@ -301,7 +492,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
- +
@@ -312,6 +503,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
@@ -324,6 +516,93 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+ +
+
+ Winner Gender Breakdown +
+ +
+
+
+
+
Total Winners
+
+
Actual year-end trophy winners
+
+
+ +
+
+
Boys
+
+
%
+
+
+ +
+
+
Girls
+
+
%
+
+
+ +
+
+
Unknown / Other
+
+
%
+
+
+
+ +
+
+ 0): ?> +
+ Boys % +
+ +
+ Girls % +
+ + 0): ?> +
+ Other % +
+ + +
+ No winners +
+ +
+
+
+
+
@@ -340,7 +619,6 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted

-

Student Detail

Year ≥
Total
@@ -357,6 +635,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted + 0 ? round($totalConfirmed / $totalPredicted foreach ($cls['students'] as $s): if (!in_array($s['status'], ['confirmed', 'surprise'], true)) continue; $rank++; - $isMale = strtolower($s['gender'] ?? '') === 'male'; + $genderNorm = strtolower(trim((string)($s['gender'] ?? ''))); + $isMale = in_array($genderNorm, ['male', 'm', 'boy'], true); $statusLabel = match ($s['status']) { 'confirmed' => '✓ Confirmed', 'surprise' => '↑ Surprise', @@ -378,18 +658,17 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted - + - + - +
Status
-

Prediction Accuracy Summary

@@ -407,6 +686,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted + @@ -423,6 +703,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted + @@ -438,17 +719,18 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
Year ≥
Total
-

Charts

+

Trophy Counts per Class

- +
+

Prediction Accuracy per Class

- +

Overall Outcome Breakdown

@@ -456,8 +738,69 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted
+
+

Winner Gender Breakdown

+ + + + + + + + + + + + + + + + + + + + + + 0): ?> + + + + + + + + + + + + + + + +
GroupWinnersPercentage
Boys%
Girls%
Unknown / Other%
Total Winners 0 ? '100.0%' : '0.0%' ?>
+
+
+ +
+ + +
+ +
+
+
+ + + +
+ +
+ + +
+ @@ -473,6 +816,7 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted $cSurprise = []; $cMissed = []; $cAccuracy = []; + foreach ($classResults as $cls) { $cLabels[] = $cls['section_name']; $cPredicted[] = $cls['predicted_count']; @@ -483,94 +827,105 @@ $overallAccuracy = $totalPredicted > 0 ? round($totalConfirmed / $totalPredicted $cAccuracy[] = $cls['accuracy']; } ?> - var labels = ; + + var labels = ; var predicted = ; - var actual = ; + var actual = ; var confirmed = ; - var surprises = ; - var missed = ; - var accuracy = ; + var surprises = ; + var missed = ; + var accuracy = ; - /* ── Chart 1: grouped bar – counts per class ── */ - new Chart(document.getElementById('chart-counts'), { - type: 'bar', - data: { - labels: labels, - datasets: [ - { label: 'Predicted', data: predicted, backgroundColor: '#4A90E2', borderRadius: 3 }, - { label: 'Actual', data: actual, backgroundColor: '#f0a500', borderRadius: 3 }, - { label: 'Confirmed', data: confirmed, backgroundColor: '#28a745', borderRadius: 3 }, - { label: 'Surprises', data: surprises, backgroundColor: '#17a2b8', borderRadius: 3 }, - { label: 'Missed', data: missed, backgroundColor: '#fd7e14', borderRadius: 3 }, - ] - }, - options: { - responsive: true, - maintainAspectRatio: true, - plugins: { legend: { position: 'bottom' } }, - scales: { - x: { grid: { color: '#f0f0f0' } }, - y: { beginAtZero: true, ticks: { stepSize: 1 }, grid: { color: '#f0f0f0' } } - } - } - }); - - /* ── Chart 2: accuracy % per class ── */ - new Chart(document.getElementById('chart-accuracy'), { - type: 'bar', - data: { - labels: labels, - datasets: [{ - label: 'Accuracy %', - data: accuracy, - backgroundColor: accuracy.map(function(a) { - return a >= 80 ? '#28a745' : a >= 50 ? '#f0a500' : '#dc3545'; - }), - borderRadius: 3 - }] - }, - options: { - responsive: true, - maintainAspectRatio: true, - plugins: { legend: { display: false } }, - scales: { - y: { - beginAtZero: true, max: 100, - ticks: { callback: function(v) { return v + '%'; } }, - grid: { color: '#f0f0f0' } + if (document.getElementById('chart-counts')) { + new Chart(document.getElementById('chart-counts'), { + type: 'bar', + data: { + labels: labels, + datasets: [ + { label: 'Predicted', data: predicted, backgroundColor: '#4A90E2', borderRadius: 3 }, + { label: 'Actual', data: actual, backgroundColor: '#f0a500', borderRadius: 3 }, + { label: 'Confirmed', data: confirmed, backgroundColor: '#28a745', borderRadius: 3 }, + { label: 'Surprises', data: surprises, backgroundColor: '#17a2b8', borderRadius: 3 }, + { label: 'Missed', data: missed, backgroundColor: '#fd7e14', borderRadius: 3 }, + ] + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { legend: { position: 'bottom' } }, + scales: { + x: { grid: { color: '#f0f0f0' } }, + y: { beginAtZero: true, ticks: { stepSize: 1 }, grid: { color: '#f0f0f0' } } } } - } - }); + }); + } - /* ── Chart 3: doughnut overall outcome breakdown ── */ - new Chart(document.getElementById('chart-breakdown'), { - type: 'doughnut', - data: { - labels: ['Confirmed', 'Surprises', 'Missed'], - datasets: [{ - data: [, , ], - backgroundColor: ['#28a745', '#17a2b8', '#fd7e14'], - borderWidth: 2 - }] - }, - options: { - responsive: true, - maintainAspectRatio: true, - plugins: { - legend: { position: 'bottom' }, - tooltip: { - callbacks: { - label: function(ctx) { - var total = ctx.dataset.data.reduce(function(a, b) { return a + b; }, 0); - var pct = total > 0 ? Math.round(ctx.parsed / total * 100) : 0; - return ctx.label + ': ' + ctx.parsed + ' (' + pct + '%)'; + if (document.getElementById('chart-accuracy')) { + new Chart(document.getElementById('chart-accuracy'), { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Accuracy %', + data: accuracy, + backgroundColor: accuracy.map(function(a) { + return a >= 80 ? '#28a745' : a >= 50 ? '#f0a500' : '#dc3545'; + }), + borderRadius: 3 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { legend: { display: false } }, + scales: { + y: { + beginAtZero: true, + max: 100, + ticks: { + callback: function(v) { + return v + '%'; + } + }, + grid: { color: '#f0f0f0' } + } + } + } + }); + } + + if (document.getElementById('chart-breakdown')) { + new Chart(document.getElementById('chart-breakdown'), { + type: 'doughnut', + data: { + labels: ['Confirmed', 'Surprises', 'Missed'], + datasets: [{ + data: [, , ], + backgroundColor: ['#28a745', '#17a2b8', '#fd7e14'], + borderWidth: 2 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { position: 'bottom' }, + tooltip: { + callbacks: { + label: function(ctx) { + var total = ctx.dataset.data.reduce(function(a, b) { + return a + b; + }, 0); + var pct = total > 0 ? Math.round(ctx.parsed / total * 100) : 0; + return ctx.label + ': ' + ctx.parsed + ' (' + pct + '%)'; + } } } } } - } - }); + }); + } })(); function captureCharts() { @@ -579,20 +934,41 @@ function captureCharts() { 'chart-accuracy': 'print-chart-accuracy', 'chart-breakdown': 'print-chart-breakdown', }; + Object.keys(map).forEach(function(canvasId) { var canvas = document.getElementById(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() { + document.body.classList.remove('print-stickers-mode'); captureCharts(); 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(); + } +}); endSection() ?> -endSection() ?> +endSection() ?> \ No newline at end of file