254 lines
11 KiB
PHP
254 lines
11 KiB
PHP
<?= $this->extend('layout/management_layout') ?>
|
|
<?= $this->section('content') ?>
|
|
<?php
|
|
$schoolYear = $schoolYear ?? '';
|
|
$students = $students ?? [];
|
|
$batches = $batches ?? [];
|
|
$batchDetails = $batchDetails ?? [];
|
|
$placementTest = $placementTest ?? '';
|
|
$showStudents = $showStudents ?? false;
|
|
?>
|
|
|
|
<div class="container-fluid">
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between mb-3 gap-2">
|
|
<div>
|
|
<h2 class="mb-1">Placement Scores</h2>
|
|
<p class="text-muted mb-0">Select a placement test, enter scores, and save as a batch.</p>
|
|
</div>
|
|
<a href="<?= base_url('grading') ?>" class="btn btn-outline-secondary btn-sm">Back to grading</a>
|
|
</div>
|
|
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-white">
|
|
<h5 class="mb-0">Choose placement test</h5>
|
|
</div>
|
|
<div class="card-body pt-2">
|
|
<form method="get" action="<?= base_url('grading/placement') ?>" class="row g-3 align-items-end">
|
|
<input type="hidden" name="open" value="1">
|
|
<div class="col-12 col-md-6 col-lg-5">
|
|
<label class="form-label">Choose placement test</label>
|
|
<select name="placement_test" class="form-select" required>
|
|
<option value="">Select a placement test</option>
|
|
<option value="islamic" <?= $placementTest === 'islamic' ? 'selected' : '' ?>>Islamic Studies</option>
|
|
<option value="quran" <?= $placementTest === 'quran' ? 'selected' : '' ?>>Quran</option>
|
|
<option value="arabic" <?= $placementTest === 'arabic' ? 'selected' : '' ?>>Arabic</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-12 col-md-4 col-lg-3">
|
|
<label class="form-label">School Year</label>
|
|
<input type="text" name="school_year" class="form-control" value="<?= esc($schoolYear) ?>" placeholder="YYYY-YYYY" required>
|
|
</div>
|
|
<div class="col-12 col-md-2 col-lg-2 d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary w-100">Set Placement Scores</button>
|
|
<a href="<?= base_url('grading/placement') ?>" class="btn btn-outline-secondary">Cancel</a>
|
|
</div>
|
|
</form>
|
|
|
|
<?php if ($showStudents && !empty($students)): ?>
|
|
<form method="post" action="<?= base_url('grading/placement-all') ?>" class="mt-3">
|
|
<?= csrf_field() ?>
|
|
<input type="hidden" name="placement_test" value="<?= esc($placementTest) ?>">
|
|
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-striped align-middle mt-2" data-sortable>
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th data-sort-type="number">School ID</th>
|
|
<th data-sort-type="text">First Name</th>
|
|
<th data-sort-type="text">Last Name</th>
|
|
<th data-sort-type="text">Class Section</th>
|
|
<th data-sort-type="number" style="width: 160px;">Placement Score</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($students as $student): ?>
|
|
<?php
|
|
$sid = (int) ($student['student_id'] ?? 0);
|
|
$sectionName = (string) ($student['class_section_name'] ?? '');
|
|
$className = (string) ($student['class_name'] ?? '');
|
|
$sectionLabel = $className !== '' ? ($className . ' — ' . $sectionName) : $sectionName;
|
|
?>
|
|
<tr>
|
|
<td><?= esc($student['school_id'] ?? '') ?></td>
|
|
<td><?= esc($student['firstname'] ?? '') ?></td>
|
|
<td><?= esc($student['lastname'] ?? '') ?></td>
|
|
<td><?= esc($sectionLabel ?: '—') ?></td>
|
|
<td>
|
|
<input type="number"
|
|
name="placement_level[<?= esc((string) $sid) ?>]"
|
|
class="form-control form-control-sm"
|
|
min="0"
|
|
max="100"
|
|
step="1"
|
|
value=""
|
|
placeholder="0-100">
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="d-flex justify-content-end gap-2">
|
|
<a href="<?= base_url('grading/placement') ?>" class="btn btn-outline-secondary">Cancel</a>
|
|
<button type="submit" class="btn btn-primary">Save batch</button>
|
|
</div>
|
|
</form>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-white d-flex align-items-center justify-content-between">
|
|
<h5 class="mb-0">Placement batches</h5>
|
|
<span class="text-muted small"><?= count($batches) ?> batch<?= count($batches) === 1 ? '' : 'es' ?></span>
|
|
</div>
|
|
<div class="card-body pt-2">
|
|
<?php if (empty($batches)): ?>
|
|
<div class="alert alert-light border text-center mb-0">No placement batches yet.</div>
|
|
<?php else: ?>
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-striped align-middle mt-2" data-no-mgmt-sticky>
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Placement Test</th>
|
|
<th>School Year</th>
|
|
<th>Created</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($batches as $batch): ?>
|
|
<?php
|
|
$label = ucfirst((string) ($batch['placement_test'] ?? ''));
|
|
$createdAt = $batch['created_at'] ?? '';
|
|
$createdLabel = $createdAt ? date('M d, Y H:i', strtotime($createdAt)) : '';
|
|
$batchId = (int) ($batch['id'] ?? 0);
|
|
$detailRows = $batchDetails[$batchId] ?? [];
|
|
$collapseId = 'batch-' . $batchId;
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<button class="btn btn-link p-0 text-decoration-none" type="button" data-bs-toggle="collapse" data-bs-target="#<?= esc($collapseId) ?>" aria-expanded="false" aria-controls="<?= esc($collapseId) ?>">
|
|
<?= esc($label) ?><?= $createdLabel ? ' — ' . esc($createdLabel) : '' ?>
|
|
</button>
|
|
</td>
|
|
<td><?= esc($batch['school_year'] ?? '') ?></td>
|
|
<td><?= esc($createdLabel) ?></td>
|
|
<td>
|
|
<a href="<?= base_url('grading/placement/batch/' . (int) $batch['id']) ?>" class="btn btn-sm btn-outline-primary">Edit</a>
|
|
</td>
|
|
</tr>
|
|
<tr class="collapse" id="<?= esc($collapseId) ?>">
|
|
<td colspan="4">
|
|
<?php if (empty($detailRows)): ?>
|
|
<div class="text-muted">No students with scores in this batch.</div>
|
|
<?php else: ?>
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-bordered align-middle mb-0" data-no-mgmt-sticky>
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>School ID</th>
|
|
<th>First Name</th>
|
|
<th>Last Name</th>
|
|
<th>Class Section</th>
|
|
<th>Score</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($detailRows as $row): ?>
|
|
<?php
|
|
$sectionName = (string) ($row['class_section_name'] ?? '');
|
|
$className = (string) ($row['class_name'] ?? '');
|
|
$sectionLabel = $className !== '' ? ($className . ' — ' . $sectionName) : $sectionName;
|
|
?>
|
|
<tr>
|
|
<td><?= esc($row['school_id'] ?? '') ?></td>
|
|
<td><?= esc($row['firstname'] ?? '') ?></td>
|
|
<td><?= esc($row['lastname'] ?? '') ?></td>
|
|
<td><?= esc($sectionLabel ?: '—') ?></td>
|
|
<td><?= esc($row['score'] ?? '') ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?= $this->endSection() ?>
|
|
|
|
<?= $this->section('scripts') ?>
|
|
<script>
|
|
(() => {
|
|
const tables = document.querySelectorAll('table[data-sortable]');
|
|
tables.forEach((table) => {
|
|
const tbody = table.tBodies[0];
|
|
const headers = table.tHead ? Array.from(table.tHead.querySelectorAll('th')) : [];
|
|
if (!tbody || headers.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const getCellValue = (cell) => {
|
|
const input = cell.querySelector('input');
|
|
if (input) {
|
|
return input.value.trim();
|
|
}
|
|
return cell.textContent.trim();
|
|
};
|
|
|
|
const parseValue = (value, type) => {
|
|
if (type === 'number') {
|
|
const num = parseFloat(value.replace(/[^0-9.\-]/g, ''));
|
|
return Number.isNaN(num) ? null : num;
|
|
}
|
|
return value.toLowerCase();
|
|
};
|
|
|
|
headers.forEach((th, index) => {
|
|
th.style.cursor = 'pointer';
|
|
th.setAttribute('role', 'button');
|
|
th.setAttribute('aria-sort', 'none');
|
|
|
|
th.addEventListener('click', () => {
|
|
const sortType = th.dataset.sortType || 'text';
|
|
const rows = Array.from(tbody.querySelectorAll('tr')).map((row, idx) => ({
|
|
row,
|
|
idx,
|
|
value: parseValue(getCellValue(row.children[index] || row.cells[index]), sortType),
|
|
}));
|
|
|
|
const current = th.getAttribute('aria-sort') || 'none';
|
|
const nextDir = current === 'ascending' ? 'descending' : 'ascending';
|
|
|
|
headers.forEach((h) => h.setAttribute('aria-sort', 'none'));
|
|
th.setAttribute('aria-sort', nextDir);
|
|
|
|
rows.sort((a, b) => {
|
|
if (a.value === b.value) {
|
|
return a.idx - b.idx;
|
|
}
|
|
if (a.value === null) return 1;
|
|
if (b.value === null) return -1;
|
|
if (a.value < b.value) return nextDir === 'ascending' ? -1 : 1;
|
|
return nextDir === 'ascending' ? 1 : -1;
|
|
});
|
|
|
|
const fragment = document.createDocumentFragment();
|
|
rows.forEach(({ row }) => fragment.appendChild(row));
|
|
tbody.appendChild(fragment);
|
|
});
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
<?= $this->endSection() ?>
|