<?php
declare(strict_types=1);
session_start();
require_once '../config.php';
require_once __DIR__ . '/helpers.php';
if (!isset($_SESSION['user_id'])) { header('Location: ../login.php'); exit; }
$conn = getDBConnection();
if (!$conn instanceof mysqli) { http_response_code(500); exit('Database connection failed.'); }
$classes = examsGetClasses($conn);
$settings = examsGetSettings($conn);
$boundaries = [];
$gb = $conn->query("SELECT grade, min_score, max_score FROM grade_boundaries ORDER BY max_score DESC");
while ($gb && $row = $gb->fetch_assoc()) { $boundaries[] = $row; }
$papers = [];
$result = $conn->query("SELECT id, title, class_name, term, academic_year, total_marks FROM exam_papers ORDER BY created_at DESC");
while ($result && $row = $result->fetch_assoc()) { $papers[] = $row; }
$preClass = trim((string) ($_GET['class_name'] ?? ''));
$preTerm = trim((string) ($_GET['term'] ?? ''));
$year = $settings['academic_year'] ?? getAcademicYear();
?>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Exams - Results</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"><link rel="stylesheet" href="../css/style.css"><style>.bar{height:20px;background:#0d6efd;border-radius:4px}</style></head><body><div class="wrapper"><nav id="sidebar" class="sidebar"><?php if (!defined('EDUTRACK_MODULES')) require_once __DIR__ . '/../module_definitions.php'; echo buildSidebar('exams_generator/' . basename($_SERVER['PHP_SELF'])); ?></nav><div id="content" class="content"><header class="top-navbar d-flex justify-content-between align-items-center flex-wrap gap-2"><div><h4 class="mb-1">Exams - Results</h4><div class="text-muted small">Enter standalone exam scores and review class performance summaries.</div></div></header><main class="container-fluid py-4"><?= examsRenderTabs('results') ?><div class="alert alert-info">These are standalone exam results. For terminal report cards, go to <a href="../modules/reports/index.php">Terminal Reports</a>.</div><div class="card shadow-sm border-0 mb-4"><div class="card-body"><div class="row g-2 align-items-end"><div class="col-md-3"><label class="form-label">Paper</label><select id="paper_id" class="form-select"><option value="">Select paper</option><?php foreach($papers as $paper): ?><option value="<?= (int)$paper['id'] ?>" data-class="<?= htmlspecialchars((string)$paper['class_name']) ?>" data-term="<?= htmlspecialchars((string)$paper['term']) ?>" data-year="<?= htmlspecialchars((string)$paper['academic_year']) ?>" data-total="<?= (int)$paper['total_marks'] ?>" <?= $preClass !== '' && $preClass === $paper['class_name'] && ($preTerm === '' || $preTerm === $paper['term']) ? 'selected' : '' ?>><?= htmlspecialchars((string)$paper['title']) ?></option><?php endforeach; ?></select></div><div class="col-md-2"><label class="form-label">Class</label><select id="class_name" class="form-select"><option value="">Select class</option><?php foreach($classes as $class): ?><option value="<?= htmlspecialchars($class) ?>" <?= $preClass === $class ? 'selected' : '' ?>><?= htmlspecialchars($class) ?></option><?php endforeach; ?></select></div><div class="col-md-2"><label class="form-label">Term</label><select id="term" class="form-select"><?php foreach(examsTerms() as $term): ?><option value="<?= htmlspecialchars($term) ?>" <?= $preTerm === $term ? 'selected' : '' ?>><?= htmlspecialchars($term) ?></option><?php endforeach; ?></select></div><div class="col-md-2"><label class="form-label">Year</label><input type="text" id="academic_year" class="form-control" value="<?= htmlspecialchars($year) ?>"></div><div class="col-md-3 d-flex gap-2"><button class="btn btn-primary flex-fill" id="loadResultsBtn">Load Results</button><button class="btn btn-outline-secondary" id="printResultsBtn">Export Results PDF</button></div></div></div></div>
<div class="card shadow-sm border-0 mb-4"><div class="card-body"><div class="d-flex justify-content-between align-items-center mb-3"><h6 class="mb-0">Results Entry <span id="marksInfo" class="text-muted small"></span></h6><button class="btn btn-success btn-sm" id="saveAllBtn">Save All Results</button></div><div class="table-responsive"><table class="table table-bordered align-middle"><thead><tr><th>Student Name</th><th>Student ID</th><th>Score</th><th>Grade</th><th>Remarks</th></tr></thead><tbody id="resultsBody"><tr><td colspan="5" class="text-center text-muted py-4">Load a paper to view results.</td></tr></tbody></table></div></div></div>
<div class="row g-3 mb-4" id="summaryCards"></div><div class="card shadow-sm border-0 mb-4"><div class="card-body"><h6>Score Distribution</h6><div id="distributionChart"></div></div></div><div class="card shadow-sm border-0"><div class="card-body"><h6>Class Ranking</h6><div class="table-responsive"><table class="table table-striped"><thead><tr><th>Rank</th><th>Student</th><th>Student ID</th><th>Score</th><th>Grade</th></tr></thead><tbody id="rankingBody"></tbody></table></div></div></div></main></div></div>
<script>
const gradeBoundaries = <?= json_encode($boundaries, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
let loadedRows=[]; function esc(v){return String(v??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));}
function gradeForScore(score){for(const b of gradeBoundaries){const min=parseFloat(b.min_score),max=parseFloat(b.max_score);if(score>=min&&score<=max)return b.grade||'';}return '';}
function syncPaperMeta(){const opt=document.querySelector('#paper_id option:checked');if(opt&&opt.value){document.getElementById('class_name').value=opt.dataset.class||'';document.getElementById('term').value=opt.dataset.term||'First Term';document.getElementById('academic_year').value=opt.dataset.year||'';document.getElementById('marksInfo').textContent=`(Out of ${opt.dataset.total||0})`;}}
function renderResults(rows){loadedRows=rows;document.getElementById('resultsBody').innerHTML=rows.map(r=>`<tr><td>${esc((r.first_name||'')+' '+(r.last_name||''))}</td><td>${esc(r.student_code||'')}</td><td><input type="number" class="form-control form-control-sm score-input" data-student="${r.base_student_id}" value="${r.score??''}" step="0.01"></td><td><span class="grade-label">${esc(r.grade||'')}</span></td><td><input type="text" class="form-control form-control-sm remarks-input" data-student="${r.base_student_id}" value="${esc(r.remarks||'')}"></td></tr>`).join('');document.querySelectorAll('.score-input').forEach(i=>i.addEventListener('input',()=>{const tr=i.closest('tr');const score=parseFloat(i.value);tr.querySelector('.grade-label').textContent=Number.isNaN(score)?'':gradeForScore(score);updateSummary();}));updateSummary();}
function updateSummary(){const rows=[...document.querySelectorAll('#resultsBody tr')].map(tr=>({name:tr.children[0]?.textContent||'',code:tr.children[1]?.textContent||'',score:parseFloat(tr.querySelector('.score-input')?.value||'NaN'),grade:tr.querySelector('.grade-label')?.textContent||''})).filter(r=>!Number.isNaN(r.score));if(!rows.length){document.getElementById('summaryCards').innerHTML='';document.getElementById('distributionChart').innerHTML='';document.getElementById('rankingBody').innerHTML='';return;}const scores=rows.map(r=>r.score);const avg=(scores.reduce((a,b)=>a+b,0)/scores.length).toFixed(2), hi=Math.max(...scores).toFixed(2), lo=Math.min(...scores).toFixed(2), pass=((scores.filter(s=>s>=50).length/scores.length)*100).toFixed(1);document.getElementById('summaryCards').innerHTML=[['Average Score',avg],['Highest',hi],['Lowest',lo],['Pass Rate',pass+'%']].map(([l,v])=>`<div class="col-md-3"><div class="card border-0 shadow-sm"><div class="card-body text-center"><div class="small text-muted">${l}</div><div class="fw-bold">${v}</div></div></div></div>`).join('');const bands=[['0-39',0],['40-49',0],['50-69',0],['70-84',0],['85-100',0]];scores.forEach(s=>{if(s<40)bands[0][1]++;else if(s<50)bands[1][1]++;else if(s<70)bands[2][1]++;else if(s<85)bands[3][1]++;else bands[4][1]++;});const max=Math.max(...bands.map(b=>b[1]),1);document.getElementById('distributionChart').innerHTML=bands.map(([label,count])=>`<div class="mb-2"><div class="small">${label} (${count})</div><div class="bar" style="width:${(count/max)*100}%"></div></div>`).join('');rows.sort((a,b)=>b.score-a.score);document.getElementById('rankingBody').innerHTML=rows.map((r,i)=>`<tr><td>${i+1}</td><td>${esc(r.name)}</td><td>${esc(r.code)}</td><td>${r.score.toFixed(2)}</td><td>${esc(r.grade||gradeForScore(r.score))}</td></tr>`).join('');}
async function loadResults(){syncPaperMeta();const paperId=document.getElementById('paper_id').value,className=document.getElementById('class_name').value;if(!paperId||!className)return;const res=await fetch('ajax/get_results.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({paper_id:paperId,class_name:className})});const data=await res.json();renderResults(data);}
document.addEventListener('DOMContentLoaded',()=>{document.getElementById('paper_id').addEventListener('change',syncPaperMeta);document.getElementById('loadResultsBtn').addEventListener('click',loadResults);document.getElementById('printResultsBtn').addEventListener('click',()=>{const paperId=document.getElementById('paper_id').value;if(paperId)window.open(`print_results.php?paper_id=${paperId}`,'_blank');});document.getElementById('saveAllBtn').addEventListener('click',async()=>{const paperId=document.getElementById('paper_id').value;if(!paperId)return;const results=[...document.querySelectorAll('#resultsBody tr')].map(tr=>({student_id:tr.querySelector('.score-input')?.dataset.student,score:tr.querySelector('.score-input')?.value||'',remarks:tr.querySelector('.remarks-input')?.value||''}));const res=await fetch('ajax/bulk_save_results.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({paper_id:paperId,results:JSON.stringify(results)})});const data=await res.json();alert(`Saved ${data.saved_count||0} result(s)`);loadResults();});if(document.getElementById('paper_id').value)loadResults();});
</script></body></html>
