recreate project
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
<?= $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() ?>
|
||||
Reference in New Issue
Block a user