This commit is contained in:
@@ -61,22 +61,6 @@ $decisionBadge = [
|
||||
<i class="bi bi-award me-2"></i>Generate Certificates
|
||||
</h2>
|
||||
|
||||
<!-- School year filter -->
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<form method="get" action="<?= site_url('administrator/certificates') ?>" class="d-flex gap-2 align-items-center">
|
||||
<label class="form-label mb-0 me-1 text-muted small">School Year</label>
|
||||
<input type="text"
|
||||
name="school_year"
|
||||
class="form-control form-control-sm"
|
||||
style="width:130px;"
|
||||
value="<?= esc($schoolYear) ?>"
|
||||
placeholder="e.g. 2024-2025">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-arrow-repeat me-1"></i>Reload
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?= esc(session()->getFlashdata('error')) ?>
|
||||
|
||||
@@ -30,8 +30,15 @@
|
||||
$lowProgressSectionIds = $lowProgressSectionIds ?? [];
|
||||
$lowProgressQuery = implode(',', $lowProgressSectionIds);
|
||||
$lowProgressUrl = base_url('administrator/teacher-submissions');
|
||||
$lowProgressParams = [];
|
||||
if (!empty($filters['school_year'])) {
|
||||
$lowProgressParams['school_year'] = $filters['school_year'];
|
||||
}
|
||||
if ($lowProgressQuery !== '') {
|
||||
$lowProgressUrl .= '?low_progress_sections=' . rawurlencode($lowProgressQuery);
|
||||
$lowProgressParams['low_progress_sections'] = $lowProgressQuery;
|
||||
}
|
||||
if (!empty($lowProgressParams)) {
|
||||
$lowProgressUrl .= '?' . http_build_query($lowProgressParams);
|
||||
}
|
||||
?>
|
||||
<a
|
||||
@@ -48,6 +55,16 @@
|
||||
<form class="card shadow-sm mb-3" method="get" action="<?= base_url('admin/progress') ?>">
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">School year</label>
|
||||
<select name="school_year" class="form-select">
|
||||
<?php foreach (($schoolYears ?? []) as $year): ?>
|
||||
<option value="<?= esc($year) ?>" <?= (string)($filters['school_year'] ?? $schoolYear ?? '') === (string)$year ? 'selected' : '' ?>>
|
||||
<?= esc($year) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">From</label>
|
||||
<input type="date" name="from" class="form-control" value="<?= esc($filters['from'] ?? '') ?>">
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3 class="mb-0">Competition Winners</h3>
|
||||
<div>
|
||||
<h3 class="mb-0">Competition Winners</h3>
|
||||
<?php if (!empty($schoolYear)): ?>
|
||||
<div class="text-muted small">School Year: <?= esc($schoolYear) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<a class="btn btn-primary" href="/admin/competition-winners/create">+ New Competition</a>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +24,7 @@
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Title</th>
|
||||
<th>School Year</th>
|
||||
<th>Class Section</th>
|
||||
<th>Winners Rule</th>
|
||||
<th>Published</th>
|
||||
@@ -36,6 +42,7 @@
|
||||
<tr>
|
||||
<td><?= esc($c['id']) ?></td>
|
||||
<td><?= esc($c['title']) ?></td>
|
||||
<td><?= esc($c['school_year'] ?? '') ?></td>
|
||||
<td><?= esc($sectionName) ?></td>
|
||||
<td>Tiered per class</td>
|
||||
<td><?= $c['is_published'] ? 'Yes' : 'No' ?></td>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
$activeStudents = $active_students ?? [];
|
||||
$removedStudents = $removed_students ?? [];
|
||||
$schoolYear = $school_year ?? '';
|
||||
$activeSchoolYear = $active_school_year ?? '';
|
||||
?>
|
||||
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
@@ -12,8 +13,8 @@ $schoolYear = $school_year ?? '';
|
||||
<div class="content">
|
||||
<h2 class="text-center mt-4 mb-2">Student Removal</h2>
|
||||
<p class="text-center text-muted mb-4">
|
||||
Removed students keep their data but do not appear in classes or attendance.
|
||||
<?= $schoolYear !== '' ? 'Current year: ' . esc($schoolYear) : '' ?>
|
||||
Active school year: <?= esc($schoolYear !== '' ? $schoolYear : $activeSchoolYear) ?>.
|
||||
Active students have a class assignment in the active school year. Removed students are inactive or have no active-year assignment.
|
||||
</p>
|
||||
<?= $this->include('partials/flash_messages') ?>
|
||||
|
||||
|
||||
+137
-153
@@ -1,171 +1,156 @@
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
<div class="container-fluid">
|
||||
<div class="wrapper">
|
||||
<div class="content"></div>
|
||||
<h2 class="text-center mt-4 mb-3">Class Preparation</h2>
|
||||
|
||||
<h2 class="text-center mt-4 mb-3">Class Preparation</h2>
|
||||
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
|
||||
|
||||
<!-- School Year Selector + Calculate Button -->
|
||||
<form method="get" action="<?= site_url('class-prep') ?>" class="mb-4 d-flex align-items-center gap-2 flex-wrap">
|
||||
<label for="school_year" class="form-label mb-0">School Year:</label>
|
||||
<select name="school_year" id="school_year" class="form-select form-select-sm w-auto">
|
||||
<?php for ($y = date('Y'); $y >= 2020; $y--): ?>
|
||||
<?php $sy = $y . '-' . ($y + 1); ?>
|
||||
<option value="<?= $sy ?>" <?= $schoolYear === $sy ? 'selected' : '' ?>><?= $sy ?></option>
|
||||
<?php endfor; ?>
|
||||
</select>
|
||||
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
|
||||
<label for="semester" class="form-label mb-0">Semester:</label>
|
||||
<select name="semester" id="semester" class="form-select form-select-sm w-auto">
|
||||
<option value="">—</option>
|
||||
<option value="Fall" <?= (strcasecmp($semVal,'Fall')===0?'selected':'') ?>>Fall</option>
|
||||
<option value="Spring" <?= (strcasecmp($semVal,'Spring')===0?'selected':'') ?>>Spring</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-primary">Calculate Items</button>
|
||||
</form>
|
||||
<!-- Recalculate using the current school year and semester -->
|
||||
<form method="get" action="<?= site_url('class-prep') ?>" class="mb-4">
|
||||
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
|
||||
<?php if ($semVal !== ''): ?>
|
||||
<input type="hidden" name="semester" value="<?= esc($semVal) ?>">
|
||||
<?php endif; ?>
|
||||
<button type="submit" class="btn btn-sm btn-primary">Calculate Items</button>
|
||||
</form>
|
||||
|
||||
<!-- Class Cards -->
|
||||
<div class="row g-3">
|
||||
<?php foreach ($prepResults as $prep): ?>
|
||||
<?php $adjMap = $prep['adjustments'] ?? []; ?>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-light border-0">
|
||||
<div>
|
||||
<div class="fw-semibold mb-1">
|
||||
<?= esc($prep['class_section']) ?>
|
||||
</div>
|
||||
<?php if (!empty($prep['needs_print'])): ?>
|
||||
<span class="badge bg-danger">Updated since last print</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-success">Up-to-date</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($prep['last_printed_at'])): ?>
|
||||
<small class="text-muted ms-2">Last printed: <?= esc($prep['last_printed_at']) ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="small text-muted text-end">
|
||||
Students:<br>
|
||||
<span class="badge bg-secondary"><?= (int)$prep['student_count'] ?></span>
|
||||
</div>
|
||||
<!-- Class Cards -->
|
||||
<div class="row g-3">
|
||||
<?php foreach ($prepResults as $prep): ?>
|
||||
<?php $adjMap = $prep['adjustments'] ?? []; ?>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-light border-0">
|
||||
<div>
|
||||
<div class="fw-semibold mb-1">
|
||||
<?= esc($prep['class_section']) ?>
|
||||
</div>
|
||||
<?php if (!empty($prep['needs_print'])): ?>
|
||||
<span class="badge bg-danger">Updated since last print</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-success">Up-to-date</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($prep['last_printed_at'])): ?>
|
||||
<small class="text-muted ms-2">Last printed: <?= esc($prep['last_printed_at']) ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="small text-muted text-end">
|
||||
Students:<br>
|
||||
<span class="badge bg-secondary"><?= (int)$prep['student_count'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<!-- Prep Items -->
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="fw-semibold mb-2">Prep Items</div>
|
||||
<ul class="list-group list-group-flush small border rounded overflow-hidden">
|
||||
<?php foreach (($prep['prep_items'] ?? []) as $item => $qty): ?>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center <?= (int)$qty === 0 ? 'text-muted-50' : '' ?>">
|
||||
<span><?= esc($item) ?></span>
|
||||
<span class="badge <?= (int)$qty > 0 ? 'bg-primary' : 'bg-secondary' ?> rounded-pill">
|
||||
<?= (int)$qty ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<!-- Prep Items -->
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="fw-semibold mb-2">Prep Items</div>
|
||||
<ul class="list-group list-group-flush small border rounded overflow-hidden">
|
||||
<?php foreach (($prep['prep_items'] ?? []) as $item => $qty): ?>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center <?= (int)$qty === 0 ? 'text-muted-50' : '' ?>">
|
||||
<span><?= esc($item) ?></span>
|
||||
<span class="badge <?= (int)$qty > 0 ? 'bg-primary' : 'bg-secondary' ?> rounded-pill">
|
||||
<?= (int)$qty ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Adjustments -->
|
||||
<div class="col-12 col-md-5">
|
||||
<div class="fw-semibold mb-2">Adjustments (Tables)</div>
|
||||
<form method="post" action="<?= site_url('class-prep/save-adjustment') ?>" class="d-grid gap-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="class_section_id" value="<?= esc($prep['class_section_id']) ?>">
|
||||
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
|
||||
|
||||
<!-- Adjustments -->
|
||||
<div class="col-12 col-md-5">
|
||||
<div class="fw-semibold mb-2">Adjustments (Tables)</div>
|
||||
<form method="post" action="<?= site_url('class-prep/save-adjustment') ?>" class="d-grid gap-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="class_section_id" value="<?= esc($prep['class_section_id']) ?>">
|
||||
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
|
||||
|
||||
<?php foreach ($adjMap as $item => $value): ?>
|
||||
<div>
|
||||
<label class="form-label mb-1 small text-muted d-block"><?= esc($item) ?></label>
|
||||
<div class="input-group input-group-sm">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="adjustQty(this, -1)" aria-label="Decrease <?= esc($item) ?>">−</button>
|
||||
<input type="number"
|
||||
class="form-control text-center"
|
||||
name="adjustments[<?= esc($item) ?>]"
|
||||
value="<?= (int)$value ?>"
|
||||
readonly>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="adjustQty(this, 1)" aria-label="Increase <?= esc($item) ?>">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-sm btn-primary">Save</button>
|
||||
<?php foreach ($adjMap as $item => $value): ?>
|
||||
<div>
|
||||
<label class="form-label mb-1 small text-muted d-block"><?= esc($item) ?></label>
|
||||
<div class="input-group input-group-sm">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="adjustQty(this, -1)" aria-label="Decrease <?= esc($item) ?>">−</button>
|
||||
<input type="number"
|
||||
class="form-control text-center"
|
||||
name="adjustments[<?= esc($item) ?>]"
|
||||
value="<?= (int)$value ?>"
|
||||
readonly>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="adjustQty(this, 1)" aria-label="Increase <?= esc($item) ?>">+</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-sm btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-transparent border-0 d-flex justify-content-end">
|
||||
<?php
|
||||
$printUrl = site_url('class-prep/print/' . urlencode($prep['class_section_id']) . '/' . $schoolYear);
|
||||
if ($semVal !== '') {
|
||||
$printUrl .= '?semester=' . urlencode($semVal);
|
||||
}
|
||||
?>
|
||||
<a href="<?= $printUrl ?>"
|
||||
class="btn btn-lg btn-print-class <?= !empty($prep['needs_print']) ? 'btn-danger' : 'btn-success' ?>" target="_blank" rel="noopener noreferrer"
|
||||
onclick="window.open(this.href, '_blank'); return false;">
|
||||
Print
|
||||
</a>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Totals Section -->
|
||||
<div class="mt-5">
|
||||
<h5 class="mb-3">📦 Totals for All Used Items</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered align-middle no-mgmt-sticky">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th class="text-end">Total Needed</th>
|
||||
<th class="text-end">In Inventory</th>
|
||||
<th class="text-end">Short By</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($totalNeeded as $item => $needed): ?>
|
||||
<?php
|
||||
$have = (int)($available[$item] ?? 0);
|
||||
$short = max(0, (int)$needed - $have);
|
||||
$rowCls = $short > 0 ? 'table-warning' : '';
|
||||
?>
|
||||
<tr class="<?= $rowCls ?>">
|
||||
<td><?= esc($item) ?></td>
|
||||
<td class="text-end"><?= (int)$needed ?></td>
|
||||
<td class="text-end"><?= $have ?></td>
|
||||
<td class="text-end <?= $short > 0 ? 'text-danger fw-bold' : 'text-success' ?>">
|
||||
<?= $short ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Shortage Warning -->
|
||||
<?php if (!empty($shortages)): ?>
|
||||
<div class="alert alert-danger mt-3">
|
||||
<strong>Shortages Detected</strong>
|
||||
<ul class="mb-0">
|
||||
<?php foreach ($shortages as $item => $short): ?>
|
||||
<li><?= esc($item) ?>: short by <?= (int)$short ?> pcs</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<div class="card-footer bg-transparent border-0 d-flex justify-content-end">
|
||||
<?php
|
||||
$printUrl = site_url('class-prep/print/' . urlencode($prep['class_section_id']) . '/' . $schoolYear);
|
||||
if ($semVal !== '') {
|
||||
$printUrl .= '?semester=' . urlencode($semVal);
|
||||
}
|
||||
?>
|
||||
<a href="<?= $printUrl ?>"
|
||||
class="btn btn-lg btn-print-class <?= !empty($prep['needs_print']) ? 'btn-danger' : 'btn-success' ?>" target="_blank" rel="noopener noreferrer"
|
||||
onclick="window.open(this.href, '_blank'); return false;">
|
||||
Print
|
||||
</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-success mt-3">✅ All items are available in stock.</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Totals Section -->
|
||||
<div class="mt-5">
|
||||
<h5 class="mb-3">📦 Totals for All Used Items</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered align-middle no-mgmt-sticky">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th class="text-end">Total Needed</th>
|
||||
<th class="text-end">In Inventory</th>
|
||||
<th class="text-end">Short By</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($totalNeeded as $item => $needed): ?>
|
||||
<?php
|
||||
$have = (int)($available[$item] ?? 0);
|
||||
$short = max(0, (int)$needed - $have);
|
||||
$rowCls = $short > 0 ? 'table-warning' : '';
|
||||
?>
|
||||
<tr class="<?= $rowCls ?>">
|
||||
<td><?= esc($item) ?></td>
|
||||
<td class="text-end"><?= (int)$needed ?></td>
|
||||
<td class="text-end"><?= $have ?></td>
|
||||
<td class="text-end <?= $short > 0 ? 'text-danger fw-bold' : 'text-success' ?>">
|
||||
<?= $short ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Shortage Warning -->
|
||||
<?php if (!empty($shortages)): ?>
|
||||
<div class="alert alert-danger mt-3">
|
||||
<strong>Shortages Detected</strong>
|
||||
<ul class="mb-0">
|
||||
<?php foreach ($shortages as $item => $short): ?>
|
||||
<li><?= esc($item) ?>: short by <?= (int)$short ?> pcs</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-success mt-3">✅ All items are available in stock.</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -204,10 +189,9 @@
|
||||
function adjustQty(btn, delta) {
|
||||
const input = btn.parentElement.querySelector('input[type="number"]');
|
||||
const curr = parseInt(input.value, 10) || 0;
|
||||
input.value = curr + delta; // clamp if you want: input.value = Math.max(0, curr + delta);
|
||||
input.value = curr + delta;
|
||||
}
|
||||
|
||||
// Force print links to open in a new tab even if other handlers intercept
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.btn-print-class').forEach(function(el) {
|
||||
el.addEventListener('click', function(ev) {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?= $this->extend('layout/main') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h1 class="h5 mb-2">School-year context unavailable</h1>
|
||||
<p class="mb-0"><?= esc($message ?? 'The selected school year is not available.') ?></p>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
@@ -6,16 +6,6 @@
|
||||
<div class="content"></div>
|
||||
<h2 class="text-center mt-4 mb-3">Invoice Management</h2>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="row g-2 align-items-center justify-content-center mb-3">
|
||||
<div class="col-auto">
|
||||
<label for="schoolYearSelect" class="col-form-label">School year</label>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select id="schoolYearSelect" class="form-select form-select-sm" style="min-width: 180px;"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table for displaying invoices (client-hydrated) -->
|
||||
<table id="invoicesTable" class="table table-striped table-hover table-bordered w-100">
|
||||
<thead class="thead-dark">
|
||||
@@ -155,16 +145,20 @@
|
||||
const $tbl = $('#invoicesTable');
|
||||
let dt = null;
|
||||
const $year = document.getElementById('schoolYearSelect');
|
||||
const selectedSchoolYear = () => $year ? $year.value : '';
|
||||
const syncSchoolYearSelect = (resp) => {
|
||||
if (!$year) return;
|
||||
const years = Array.isArray(resp.schoolYears) ? resp.schoolYears : [];
|
||||
const selected = resp.schoolYear || (years[0] || '');
|
||||
$year.innerHTML = years.map(y => `<option value="${esc(y)}" ${y===selected?'selected':''}>${esc(y)}</option>`).join('');
|
||||
};
|
||||
// Global fallback handler for inline onclick
|
||||
window.__genInvoice = async function(btn){
|
||||
try {
|
||||
btn.disabled = true;
|
||||
await generateInvoice(btn.getAttribute('data-parent-id'));
|
||||
const resp = await loadInvoices();
|
||||
// Populate year select
|
||||
const years = Array.isArray(resp.schoolYears) ? resp.schoolYears : [];
|
||||
const selected = resp.schoolYear || (years[0] || '');
|
||||
$year.innerHTML = years.map(y => `<option value="${esc(y)}" ${y===selected?'selected':''}>${esc(y)}</option>`).join('');
|
||||
const resp = await loadInvoices(selectedSchoolYear());
|
||||
syncSchoolYearSelect(resp);
|
||||
|
||||
const data = (resp.invoices || []).map(r => {
|
||||
const ts = Date.parse(r.invoice_date || new Date().toISOString());
|
||||
@@ -194,10 +188,7 @@
|
||||
}
|
||||
try {
|
||||
const resp = await loadInvoices();
|
||||
// Populate year select on first load
|
||||
const years = Array.isArray(resp.schoolYears) ? resp.schoolYears : [];
|
||||
const selected = resp.schoolYear || (years[0] || '');
|
||||
$year.innerHTML = years.map(y => `<option value="${esc(y)}" ${y===selected?'selected':''}>${esc(y)}</option>`).join('');
|
||||
syncSchoolYearSelect(resp);
|
||||
|
||||
const data = (resp.invoices || []).map(r => {
|
||||
const ts = Date.parse(r.invoice_date || new Date().toISOString());
|
||||
@@ -240,7 +231,7 @@
|
||||
try {
|
||||
await generateInvoice(btn.getAttribute('data-parent-id'));
|
||||
// Refresh table minimally: reload data
|
||||
const resp = await loadInvoices($year.value);
|
||||
const resp = await loadInvoices(selectedSchoolYear());
|
||||
const data = (resp.invoices || []).map(r => {
|
||||
const ts = Date.parse(r.invoice_date || new Date().toISOString());
|
||||
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
|
||||
@@ -273,7 +264,7 @@
|
||||
try {
|
||||
this.disabled = true;
|
||||
await generateInvoice(this.getAttribute('data-parent-id'));
|
||||
const resp = await loadInvoices($year.value);
|
||||
const resp = await loadInvoices(selectedSchoolYear());
|
||||
const data = (resp.invoices || []).map(r => {
|
||||
const ts = Date.parse(r.invoice_date || new Date().toISOString());
|
||||
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
|
||||
@@ -312,34 +303,36 @@
|
||||
});
|
||||
|
||||
// Year change
|
||||
$year.addEventListener('change', async () => {
|
||||
try {
|
||||
const resp = await loadInvoices($year.value);
|
||||
const data = (resp.invoices || []).map(r => {
|
||||
const ts = Date.parse(r.invoice_date || new Date().toISOString());
|
||||
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
|
||||
const pdf = r.invoice_id ? `<a href=\"<?= base_url('invoice/pdf') ?>/${esc(r.invoice_id)}\" target=\"_blank\" class=\"btn btn-info btn-sm external-link\">View/Print PDF</a>` : '<span class=\"text-muted\">No invoice yet</span>';
|
||||
return [
|
||||
esc(r.parent_name || ''),
|
||||
renderStudents(r.enrolledKids || []),
|
||||
genBtn,
|
||||
fmtMoney(r.invoice_amount),
|
||||
fmtMoney(r.refund_amount),
|
||||
`<span data-order=\"${ts}\">${formatDateTime(ts)}</span>`,
|
||||
pdf,
|
||||
];
|
||||
});
|
||||
if ($.fn.DataTable.isDataTable($tbl)) {
|
||||
const dti = $tbl.DataTable();
|
||||
dti.clear();
|
||||
dti.rows.add(data).draw(false);
|
||||
if ($year) {
|
||||
$year.addEventListener('change', async () => {
|
||||
try {
|
||||
const resp = await loadInvoices($year.value);
|
||||
const data = (resp.invoices || []).map(r => {
|
||||
const ts = Date.parse(r.invoice_date || new Date().toISOString());
|
||||
const genBtn = `<button type=\"button\" class=\"btn btn-primary btn-sm gen-invoice\" data-parent-id=\"${esc(r.parent_id)}\" onclick=\"return window.__genInvoice && window.__genInvoice(this)\">Generate Invoice</button>`;
|
||||
const pdf = r.invoice_id ? `<a href=\"<?= base_url('invoice/pdf') ?>/${esc(r.invoice_id)}\" target=\"_blank\" class=\"btn btn-info btn-sm external-link\">View/Print PDF</a>` : '<span class=\"text-muted\">No invoice yet</span>';
|
||||
return [
|
||||
esc(r.parent_name || ''),
|
||||
renderStudents(r.enrolledKids || []),
|
||||
genBtn,
|
||||
fmtMoney(r.invoice_amount),
|
||||
fmtMoney(r.refund_amount),
|
||||
`<span data-order=\"${ts}\">${formatDateTime(ts)}</span>`,
|
||||
pdf,
|
||||
];
|
||||
});
|
||||
if ($.fn.DataTable.isDataTable($tbl)) {
|
||||
const dti = $tbl.DataTable();
|
||||
dti.clear();
|
||||
dti.rows.add(data).draw(false);
|
||||
}
|
||||
showToast('Loaded ' + $year.value);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast('Failed to load ' + $year.value, false);
|
||||
}
|
||||
showToast('Loaded ' + $year.value);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast('Failed to load ' + $year.value, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<body>
|
||||
<!-- Navbar -->
|
||||
<?php include(__DIR__ . '/../partials/navbar.php'); ?>
|
||||
<?= view('partials/school_year_selector') ?>
|
||||
|
||||
<div class="container my-4">
|
||||
<?= $this->renderSection('content') ?>
|
||||
|
||||
@@ -165,6 +165,7 @@ html, body { overflow-x: hidden; }
|
||||
|
||||
<body data-app-menu-mode="<?= esc($appMenuMode) ?>">
|
||||
<?php include(__DIR__ . '/../partials/navbar.php'); ?>
|
||||
<?= view('partials/school_year_selector') ?>
|
||||
|
||||
<?php
|
||||
$uri = service('uri');
|
||||
@@ -188,7 +189,11 @@ html, body { overflow-x: hidden; }
|
||||
if ($isTeacher) {
|
||||
$cfg = new \App\Models\ConfigurationModel();
|
||||
$tcModel = new \App\Models\TeacherClassModel();
|
||||
$sy = (string)($cfg->getConfig('school_year') ?? '');
|
||||
try {
|
||||
$sy = service('schoolYearContext')->resolve(service('request'))->yearName();
|
||||
} catch (\Throwable $e) {
|
||||
$sy = (string)($cfg->getConfig('school_year') ?? '');
|
||||
}
|
||||
$sem = (string)($cfg->getConfig('semester') ?? '');
|
||||
$uid = (int)(session()->get('user_id') ?? 0);
|
||||
if ($uid) {
|
||||
|
||||
@@ -287,6 +287,20 @@
|
||||
<!-- Header & Navbar -->
|
||||
<?php include(__DIR__ . '/../partials/header_back.php'); ?>
|
||||
<?php include(__DIR__ . '/../partials/navbar_dynamic.php'); ?>
|
||||
<?php
|
||||
$managementSchoolYearSelectorData = [];
|
||||
if (!isset($schoolYearContext, $schoolYearOptions) && session()->get('user_id')) {
|
||||
try {
|
||||
$managementSchoolYearSelectorData = service('schoolYearViewData')->forCurrentRequest(service('request'));
|
||||
$managementSchoolYearSelectorData['schoolYearSelectorEnabled'] = true;
|
||||
} catch (\Throwable $e) {
|
||||
log_message('warning', 'Unable to load management school-year selector: {message}', [
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?= view('partials/school_year_selector', $managementSchoolYearSelectorData) ?>
|
||||
|
||||
<!-- Main Wrapper -->
|
||||
<div class="wrapper">
|
||||
|
||||
@@ -20,6 +20,8 @@ if ($currentYear === '' || $currentSem === '') {
|
||||
} catch (\Throwable $e) { /* ignore */ }
|
||||
}
|
||||
|
||||
$showSchoolYearControl = $showSchoolYearControl ?? true;
|
||||
|
||||
$selectedYear = (string)($schoolYear ?? ($_GET['school_year'] ?? ''));
|
||||
if ($selectedYear === '') { $selectedYear = $currentYear; }
|
||||
|
||||
@@ -32,23 +34,25 @@ $classSectionVal = (string)($classSectionId ?? ($_GET['class_section_id'] ?? '')
|
||||
<?php if ($classSectionVal !== ''): ?>
|
||||
<input type="hidden" name="class_section_id" value="<?= esc($classSectionVal) ?>">
|
||||
<?php endif; ?>
|
||||
<div class="col-auto"><label class="form-label mb-0">School year</label></div>
|
||||
<div class="col-auto">
|
||||
<?php $years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : []; ?>
|
||||
<select name="school_year" class="form-select form-select-sm" style="min-width: 180px;">
|
||||
<?php if (!empty($years)): ?>
|
||||
<?php foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? (string)$y['school_year'] : (string)$y; ?>
|
||||
<option value="<?= esc($val) ?>" <?= ($selectedYear === $val ? 'selected' : '') ?>><?= esc($val) ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<?php if ($selectedYear !== ''): ?>
|
||||
<option value="<?= esc($selectedYear) ?>" selected><?= esc($selectedYear) ?></option>
|
||||
<?php if ($showSchoolYearControl): ?>
|
||||
<div class="col-auto"><label class="form-label mb-0">School year</label></div>
|
||||
<div class="col-auto">
|
||||
<?php $years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : []; ?>
|
||||
<select name="school_year" class="form-select form-select-sm" style="min-width: 180px;">
|
||||
<?php if (!empty($years)): ?>
|
||||
<?php foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? (string)$y['school_year'] : (string)$y; ?>
|
||||
<option value="<?= esc($val) ?>" <?= ($selectedYear === $val ? 'selected' : '') ?>><?= esc($val) ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<option value="">—</option>
|
||||
<?php if ($selectedYear !== ''): ?>
|
||||
<option value="<?= esc($selectedYear) ?>" selected><?= esc($selectedYear) ?></option>
|
||||
<?php else: ?>
|
||||
<option value="">-</option>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="col-auto"><label class="form-label mb-0">Semester</label></div>
|
||||
<div class="col-auto">
|
||||
|
||||
@@ -60,7 +60,11 @@ switch ($role) {
|
||||
}
|
||||
} else {
|
||||
$configModel = new \App\Models\ConfigurationModel();
|
||||
$schoolYear = session()->get('school_year') ?? $configModel->getConfig('school_year');
|
||||
try {
|
||||
$schoolYear = service('schoolYearContext')->resolve(service('request'))->yearName();
|
||||
} catch (\Throwable $e) {
|
||||
$schoolYear = session()->get('school_year') ?? $configModel->getConfig('school_year');
|
||||
}
|
||||
$classSectionId = (int)($class_section_id ?? session()->get('class_section_id') ?? 0);
|
||||
if ($classSectionId > 0 && !empty($schoolYear)) {
|
||||
$scoreCardStudents = $studentModel->getByClassAndYear($classSectionId, $schoolYear);
|
||||
@@ -267,7 +271,11 @@ switch ($role) {
|
||||
if (!isset($activeEventCount) && ($role ?? '') === 'parent') {
|
||||
$configModel = new \App\Models\ConfigurationModel();
|
||||
$eventModel = new \App\Models\EventModel();
|
||||
$year = $sess->get('school_year') ?? $configModel->getConfig('school_year');
|
||||
try {
|
||||
$year = service('schoolYearContext')->resolve(service('request'))->yearName();
|
||||
} catch (\Throwable $e) {
|
||||
$year = $sess->get('school_year') ?? $configModel->getConfig('school_year');
|
||||
}
|
||||
$semester = $sess->get('semester') ?? $configModel->getConfig('semester');
|
||||
$activeEventCount = count($eventModel->getActiveEvents($year, $semester) ?? []);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php if (!empty($schoolYearSelectorEnabled) && isset($schoolYearContext, $schoolYearOptions)): ?>
|
||||
<?php
|
||||
$query = service('request')->getUri()->getQuery();
|
||||
$returnTo = current_url() . ($query !== '' ? '?' . $query : '');
|
||||
?>
|
||||
<div class="school-year-context-bar border-bottom bg-light py-2">
|
||||
<div class="container-fluid px-3">
|
||||
<form method="post" action="<?= site_url('school-year/select') ?>" class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
||||
|
||||
<label for="global-school-year" class="form-label mb-0 fw-semibold">
|
||||
School year
|
||||
</label>
|
||||
|
||||
<select
|
||||
id="global-school-year"
|
||||
name="school_year_id"
|
||||
class="form-select form-select-sm w-auto"
|
||||
onchange="this.form.submit()"
|
||||
aria-label="Selected school year"
|
||||
>
|
||||
<?php foreach ($schoolYearOptions as $year): ?>
|
||||
<option
|
||||
value="<?= (int) $year['id'] ?>"
|
||||
<?= (int) $year['id'] === $schoolYearContext->id() ? 'selected' : '' ?>
|
||||
>
|
||||
<?= esc($year['name']) ?> - <?= esc(ucfirst((string) $year['status'])) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<?php if ($schoolYearContext->isReadonly()): ?>
|
||||
<span class="btn btn-warning btn-sm disabled" aria-disabled="true">
|
||||
<i class="bi bi-lock-fill"></i> READ-ONLY
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="badge text-bg-success">ACTIVE</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<span class="text-muted small"><?= esc($schoolYearContext->yearName()) ?></span>
|
||||
</form>
|
||||
<?php if ($schoolYearContext->isExplicitSelection()): ?>
|
||||
<form method="post" action="<?= site_url('school-year/reset') ?>" class="d-inline-block mt-2 mt-sm-0 ms-sm-2">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
||||
Active year
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -1,3 +1,35 @@
|
||||
|
||||
Yesterday 11:12 PM
|
||||
write sql querry to update school_year = 2025-2026
|
||||
|
||||
UPDATE your_table_name
|
||||
SET school_year = '2025-2026'
|
||||
WHERE your_condition;
|
||||
To update every row:
|
||||
|
||||
UPDATE your_table_name
|
||||
SET school_year = '2025-2026';
|
||||
Use the second version only if you genuinely intend to rewrite the entire table, because databases are remarkably obedient when humans forget WHERE.
|
||||
|
||||
|
||||
Today 12:49 AM
|
||||
Screenshot 2026-07-14 at 12.49.07 AM.png
|
||||
|
||||
Pasted code(6).php
|
||||
File
|
||||
remove the image content from the view
|
||||
|
||||
Removed the School year / Semester filter from the view by deleting:
|
||||
|
||||
<?= $this->include('partials/academic_filter') ?>
|
||||
Pasted_code_6_without_academic_filter.php
|
||||
|
||||
|
||||
Library
|
||||
/
|
||||
Pasted_code_6_without_academic_filter.php
|
||||
|
||||
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?php
|
||||
@@ -28,8 +60,6 @@ $formatRole = function (?string $role): string {
|
||||
<h2 class="text-dark mb-3" style="font-family: Arial, sans-serif;">Generate Staff Badges</h2>
|
||||
</div>
|
||||
|
||||
<?= $this->include('partials/academic_filter') ?>
|
||||
|
||||
<div class="d-flex flex-column flex-md-row justify-content-md-end align-items-md-center gap-2">
|
||||
<!-- Submit button associates to form below -->
|
||||
<button type="submit" class="btn btn-success mt-3" form="badgeForm">Generate Badges</button>
|
||||
@@ -38,10 +68,7 @@ $formatRole = function (?string $role): string {
|
||||
<!-- Staff selection -->
|
||||
<form method="post" action="<?= base_url('badge') ?>" target="_blank" id="badgeForm">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="school_year" value="<?= esc($selectedYear ?? '') ?>">
|
||||
<!-- hidden container to preserve selections and their data (guaranteed inside form) -->
|
||||
<div id="selectedHiddenInputs" style="display:none"></div>
|
||||
|
||||
|
||||
<table id="staffTable" class="table table-bordered table-striped align-middle w-100">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
@@ -278,11 +305,7 @@ $formatRole = function (?string $role): string {
|
||||
async function refreshCsrf() {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (selectedYear) params.append('school_year', selectedYear);
|
||||
const resp = await fetch('<?= base_url('api/printables/badges/status') ?>' + (params.toString() ? ('?' + params.toString()) : ''), {
|
||||
method: 'GET',
|
||||
headers: { 'Accept': 'application/json' }
|
||||
});
|
||||
|
||||
if (!resp.ok) return;
|
||||
const json = await resp.json();
|
||||
if (json && json.csrf_token && json.csrf_hash && csrfInput && json.csrf_token === csrfName) {
|
||||
@@ -351,3 +374,352 @@ $formatRole = function (?string $role): string {
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
Library
|
||||
/
|
||||
Pasted_code_6_without_academic_filter.php
|
||||
|
||||
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?php
|
||||
// Role formatter for display + posting
|
||||
$formatRole = function (?string $role): string {
|
||||
$role = (string)$role;
|
||||
$role = str_replace(['-', '_'], ' ', $role);
|
||||
$role = preg_replace('/\s+/', ' ', trim($role));
|
||||
if ($role === '') return '';
|
||||
$out = [];
|
||||
foreach (explode(' ', $role) as $w) {
|
||||
if ($w === '') continue;
|
||||
if (preg_match('/^[A-Za-z]{1,3}$/', $w)) {
|
||||
$out[] = strtoupper($w); // TA, PTA, HR, KG...
|
||||
} else {
|
||||
$out[] = ucfirst(strtolower($w)); // Teacher, Assistant, Admin...
|
||||
}
|
||||
}
|
||||
return implode(' ', $out);
|
||||
};
|
||||
?>
|
||||
<div class="container-fluid">
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
|
||||
<div class="text-center mx-auto mb-5 wow" data-wow-delay="0.1s" style="max-width: 600px;">
|
||||
<br>
|
||||
<h2 class="text-dark mb-3" style="font-family: Arial, sans-serif;">Generate Staff Badges</h2>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column flex-md-row justify-content-md-end align-items-md-center gap-2">
|
||||
<!-- Submit button associates to form below -->
|
||||
<button type="submit" class="btn btn-success mt-3" form="badgeForm">Generate Badges</button>
|
||||
<br>
|
||||
</div>
|
||||
<!-- Staff selection -->
|
||||
<form method="post" action="<?= base_url('badge') ?>" target="_blank" id="badgeForm">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<table id="staffTable" class="table table-bordered table-striped align-middle w-100">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th style="width: 70px;">#</th>
|
||||
<th scope="col">Firstname</th>
|
||||
<th scope="col">Lastname</th>
|
||||
<th scope="col">Role</th>
|
||||
<th scope="col">Teacher Class Section</th>
|
||||
<th scope="col" style="width: 140px;">Badge Prints</th>
|
||||
<th scope="col" style="width: 150px;">
|
||||
<div class="form-check m-0">
|
||||
<input class="form-check-input" type="checkbox" id="select-all">
|
||||
<label class="form-check-label" for="select-all">Select All (page)</label>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($users)): ?>
|
||||
<?php $order = 1; ?>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<?php
|
||||
// Pick a representative role (first of CSV or role_name/active_role)
|
||||
$roleRaw = $user['active_role'] ?? $user['role_name'] ?? ($user['roles'] ?? '');
|
||||
if (strpos((string)$roleRaw, ',') !== false) {
|
||||
$parts = array_filter(array_map('trim', explode(',', (string)$roleRaw)));
|
||||
$roleRaw = $parts[0] ?? '';
|
||||
}
|
||||
//$roleLabel = $roleRaw !== '' ? $formatRole($roleRaw) : '-';
|
||||
$roleLabel = $user['role_name'] ?? ($user['roles'] ?? '-'); // both are pre-formatted by $formatRole
|
||||
|
||||
// Normalize user id key for checkbox
|
||||
$uid = $user['user_id'] ?? $user['id'] ?? ($user['users.id'] ?? null);
|
||||
|
||||
// Class section name (optional)
|
||||
$className = !empty($user['class_section_name']) ? (string)$user['class_section_name'] : '';
|
||||
?>
|
||||
<tr
|
||||
data-user-id="<?= esc($uid ?? '') ?>"
|
||||
data-role-label="<?= esc($roleLabel) ?>"
|
||||
data-class-name="<?= esc($className) ?>">
|
||||
<td style="text-align: center;"><?= esc($order++) ?></td>
|
||||
<td><?= esc($user['firstname'] ?? '') ?></td>
|
||||
<td><?= esc($user['lastname'] ?? '') ?></td>
|
||||
<td><?= esc($roleLabel) ?></td>
|
||||
<td><?= $className !== '' ? esc($className) : '-' ?></td>
|
||||
<td><span class="badge bg-secondary prints-badge" data-user-id="<?= esc($uid ?? '') ?>">—</span></td>
|
||||
<td>
|
||||
<?php if ($uid !== null): ?>
|
||||
<div class="form-check m-0">
|
||||
<input type="checkbox" name="user_ids[]" value="<?= esc($uid) ?>" class="form-check-input user-checkbox" id="chk-<?= esc($uid) ?>">
|
||||
<label class="form-check-label" for="chk-<?= esc($uid) ?>">Select</label>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">N/A</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted">No staff found for the selected year.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</form>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Track selected user IDs across paging/filtering/sorting
|
||||
const selected = new Set();
|
||||
const formEl = document.getElementById('badgeForm');
|
||||
let clearTimerId = null; // pending auto-clear timer, if any
|
||||
let hiddenContainer = document.getElementById('selectedHiddenInputs');
|
||||
// Ensure hidden container exists inside the form (some earlier layouts had it outside)
|
||||
if (!hiddenContainer || !formEl.contains(hiddenContainer)) {
|
||||
hiddenContainer = document.createElement('div');
|
||||
hiddenContainer.id = 'selectedHiddenInputs';
|
||||
hiddenContainer.style.display = 'none';
|
||||
formEl.appendChild(hiddenContainer);
|
||||
}
|
||||
const selectAll = document.getElementById('select-all');
|
||||
const csrfName = <?= json_encode(csrf_token()) ?>;
|
||||
const csrfInput = document.querySelector(`input[name='${csrfName}']`);
|
||||
|
||||
// Build a meta map (userId -> { role, className }) from the DOM BEFORE DataTables paginates
|
||||
const rowMeta = {};
|
||||
document.querySelectorAll('#staffTable tbody tr').forEach(tr => {
|
||||
const id = tr.getAttribute('data-user-id');
|
||||
if (!id) return;
|
||||
rowMeta[id] = {
|
||||
role: tr.getAttribute('data-role-label') || '',
|
||||
className: tr.getAttribute('data-class-name') || ''
|
||||
};
|
||||
});
|
||||
|
||||
const table = $('#staffTable').DataTable({
|
||||
stateSave: true,
|
||||
pageLength: 100,
|
||||
lengthMenu: [10, 25, 50, 100],
|
||||
order: [
|
||||
[1, 'asc'],
|
||||
[2, 'asc']
|
||||
], // Firstname, Lastname
|
||||
columnDefs: [{
|
||||
targets: 0,
|
||||
searchable: false
|
||||
}, // row #
|
||||
{
|
||||
targets: 6,
|
||||
orderable: false,
|
||||
searchable: false
|
||||
} // checkbox column
|
||||
]
|
||||
});
|
||||
|
||||
// Helper: keep hidden inputs in sync so selections submit even if not on current page
|
||||
function syncHiddenInputs() {
|
||||
hiddenContainer.innerHTML = '';
|
||||
selected.forEach(function(id) {
|
||||
const meta = rowMeta[id] || {
|
||||
role: '',
|
||||
className: ''
|
||||
};
|
||||
// user_ids[]
|
||||
const i1 = document.createElement('input');
|
||||
i1.type = 'hidden';
|
||||
i1.name = 'user_ids[]';
|
||||
i1.value = id;
|
||||
hiddenContainer.appendChild(i1);
|
||||
// roles[ID]
|
||||
const i2 = document.createElement('input');
|
||||
i2.type = 'hidden';
|
||||
i2.name = `roles[${id}]`;
|
||||
i2.value = meta.role;
|
||||
hiddenContainer.appendChild(i2);
|
||||
// classes[ID]
|
||||
const i3 = document.createElement('input');
|
||||
i3.type = 'hidden';
|
||||
i3.name = `classes[${id}]`;
|
||||
i3.value = meta.className;
|
||||
hiddenContainer.appendChild(i3);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper: refresh checkboxes on current page based on Set
|
||||
function syncPageCheckboxes() {
|
||||
const pageNodes = table.rows({
|
||||
page: 'current'
|
||||
}).nodes().to$();
|
||||
let allChecked = true;
|
||||
let anyChecked = false;
|
||||
pageNodes.each(function() {
|
||||
const row = this;
|
||||
const userId = row.getAttribute('data-user-id');
|
||||
const cb = row.querySelector('.user-checkbox');
|
||||
if (!cb || !userId) return;
|
||||
cb.checked = selected.has(userId);
|
||||
if (!cb.checked) allChecked = false;
|
||||
if (cb.checked) anyChecked = true;
|
||||
});
|
||||
// Update header select-all for current page
|
||||
selectAll.checked = allChecked && pageNodes.length > 0;
|
||||
selectAll.indeterminate = !allChecked && anyChecked;
|
||||
}
|
||||
|
||||
// Helper: clear all selections and reset the current page UI
|
||||
function clearSelectionsNow() {
|
||||
selected.clear();
|
||||
// Uncheck visible checkboxes on current page
|
||||
table.rows({ page: 'current' }).every(function () {
|
||||
const row = this.node();
|
||||
const cb = row.querySelector('.user-checkbox');
|
||||
if (cb) cb.checked = false;
|
||||
});
|
||||
// Reset select-all and hidden inputs
|
||||
selectAll.checked = false;
|
||||
selectAll.indeterminate = false;
|
||||
syncHiddenInputs();
|
||||
syncPageCheckboxes();
|
||||
}
|
||||
|
||||
// On draw (paging/sort/search), just refresh checkbox UI states
|
||||
table.on('draw', function() {
|
||||
syncPageCheckboxes();
|
||||
});
|
||||
|
||||
// Delegate checkbox change handling once at the table level (works across redraws)
|
||||
document.getElementById('staffTable').addEventListener('change', function (e) {
|
||||
const target = e.target;
|
||||
if (!target || !target.classList.contains('user-checkbox')) return;
|
||||
const tr = target.closest('tr[data-user-id]');
|
||||
const userId = tr ? tr.getAttribute('data-user-id') : null;
|
||||
if (!userId) return;
|
||||
if (target.checked) selected.add(userId); else selected.delete(userId);
|
||||
syncHiddenInputs();
|
||||
syncPageCheckboxes();
|
||||
// If user interacts, cancel any pending auto-clear to avoid wiping new selections
|
||||
if (clearTimerId !== null) { clearTimeout(clearTimerId); clearTimerId = null; }
|
||||
});
|
||||
|
||||
// Initial sync on first load
|
||||
table.draw(false);
|
||||
|
||||
// Header "Select All (page)" toggler
|
||||
selectAll.addEventListener('change', function() {
|
||||
const check = this.checked;
|
||||
table.rows({
|
||||
page: 'current'
|
||||
}).every(function() {
|
||||
const row = this.node();
|
||||
const userId = row.getAttribute('data-user-id');
|
||||
const cb = row.querySelector('.user-checkbox');
|
||||
if (!cb || !userId) return;
|
||||
cb.checked = check;
|
||||
if (check) selected.add(userId);
|
||||
else selected.delete(userId);
|
||||
});
|
||||
syncHiddenInputs();
|
||||
syncPageCheckboxes();
|
||||
if (clearTimerId !== null) { clearTimeout(clearTimerId); clearTimerId = null; }
|
||||
});
|
||||
|
||||
// Before submit, ensure CSRF is fresh and hidden inputs include all selections
|
||||
const form = document.getElementById('badgeForm');
|
||||
async function refreshCsrf() {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (!resp.ok) return;
|
||||
const json = await resp.json();
|
||||
if (json && json.csrf_token && json.csrf_hash && csrfInput && json.csrf_token === csrfName) {
|
||||
csrfInput.value = json.csrf_hash;
|
||||
}
|
||||
} catch (e) {
|
||||
// No-op: if refresh fails we'll still attempt submit; server may accept existing token
|
||||
}
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
if (selected.size === 0) {
|
||||
alert('Please select at least one staff member to generate badges.');
|
||||
return;
|
||||
}
|
||||
syncHiddenInputs();
|
||||
// CSRF is excluded for this endpoint now, but keeping refresh is safe
|
||||
try { await refreshCsrf(); } catch (_) {}
|
||||
// Native submit so browser handles PDF in a new tab
|
||||
form.submit();
|
||||
// Auto-clear selections 5 seconds after generating badges
|
||||
clearTimerId = setTimeout(function() { clearSelectionsNow(); clearTimerId = null; }, 5000);
|
||||
});
|
||||
|
||||
// --- Fetch and render print status ---
|
||||
const selectedYear = <?= json_encode($selectedYear ?? '') ?>;
|
||||
|
||||
function fetchPrintStatus() {
|
||||
const ids = Object.keys(rowMeta);
|
||||
if (ids.length === 0) return;
|
||||
const params = new URLSearchParams();
|
||||
ids.forEach(id => params.append('user_ids[]', id));
|
||||
if (selectedYear) params.append('school_year', selectedYear);
|
||||
|
||||
fetch('<?= base_url('api/printables/badges/status') ?>?' + params.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(r => r.ok ? r.json() : Promise.reject())
|
||||
.then(json => {
|
||||
if (!json || !json.ok || !json.data) return;
|
||||
const data = json.data;
|
||||
Object.keys(rowMeta).forEach(id => {
|
||||
const info = data[id];
|
||||
const badge = document.querySelector(`.prints-badge[data-user-id="${id}"]`);
|
||||
const tr = document.querySelector(`#staffTable tbody tr[data-user-id="${id}"]`);
|
||||
if (!badge || !tr) return;
|
||||
const count = info ? (info.count || 0) : 0;
|
||||
badge.textContent = count > 0 ? `Printed ${count}` : '—';
|
||||
badge.className = 'badge prints-badge ' + (count > 0 ? 'bg-success' : 'bg-secondary');
|
||||
tr.classList.toggle('table-success', count > 0);
|
||||
});
|
||||
// Refresh CSRF for subsequent submissions (so no page reload is needed)
|
||||
if (json.csrf_token && json.csrf_hash && csrfInput && json.csrf_token === csrfName) {
|
||||
csrfInput.value = json.csrf_hash;
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
fetchPrintStatus();
|
||||
window.addEventListener('focus', fetchPrintStatus);
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
@@ -6,12 +6,48 @@
|
||||
$target = $preview['target'] ?? null;
|
||||
$overview = $preview['overview'] ?? [];
|
||||
$finance = $preview['finance'] ?? [];
|
||||
$promotion = $preview['promotion'] ?? ['summary' => [], 'rows' => []];
|
||||
$promotionSummary = $promotion['summary'] ?? [];
|
||||
$promotionTable = $promotionTable ?? [];
|
||||
$promotionRows = $promotionTable['rows'] ?? ($promotion['rows'] ?? []);
|
||||
$promotionSort = (string) ($promotionTable['sort'] ?? 'class');
|
||||
$promotionOrder = (string) ($promotionTable['order'] ?? 'asc');
|
||||
$promotionPage = (int) ($promotionTable['page'] ?? 1);
|
||||
$promotionPerPage = (int) ($promotionTable['perPage'] ?? 25);
|
||||
$promotionTotal = (int) ($promotionTable['total'] ?? count($promotionRows));
|
||||
$promotionPageCount = (int) ($promotionTable['pageCount'] ?? 1);
|
||||
$promotionFrom = (int) ($promotionTable['from'] ?? ($promotionTotal === 0 ? 0 : 1));
|
||||
$promotionTo = (int) ($promotionTable['to'] ?? count($promotionRows));
|
||||
$promotionPerPageOptions = $promotionTable['allowedPerPage'] ?? [10, 25, 50, 100];
|
||||
$blockers = $preview['blockers'] ?? [];
|
||||
$warnings = $preview['warnings'] ?? [];
|
||||
$carryForward = $preview['carry_forward'] ?? [];
|
||||
$status = (string) ($source['status'] ?? '');
|
||||
$batchStatus = (string) ($latestBatch['status'] ?? '');
|
||||
$money = static fn ($value): string => '$' . number_format((float) $value, 2);
|
||||
$score = static fn ($value): string => is_numeric($value) ? number_format((float) $value, 2) : '-';
|
||||
$promotionUrl = static function (array $overrides = []) use ($source, $target, $promotionSort, $promotionOrder, $promotionPage, $promotionPerPage): string {
|
||||
$query = array_merge([
|
||||
'target_school_year_id' => $target['id'] ?? null,
|
||||
'sort' => $promotionSort,
|
||||
'order' => $promotionOrder,
|
||||
'page' => $promotionPage,
|
||||
'per_page' => $promotionPerPage,
|
||||
], $overrides);
|
||||
$query = array_filter($query, static fn ($value): bool => $value !== null && $value !== '');
|
||||
|
||||
return site_url('administrator/school-years/' . (int) ($source['id'] ?? 0) . '/closing/preview?' . http_build_query($query));
|
||||
};
|
||||
$sortLink = static function (string $key, string $label, string $class = '') use ($promotionSort, $promotionOrder, $promotionUrl): string {
|
||||
$nextOrder = $promotionSort === $key && $promotionOrder === 'asc' ? 'desc' : 'asc';
|
||||
$indicator = $promotionSort === $key ? ($promotionOrder === 'asc' ? ' (asc)' : ' (desc)') : '';
|
||||
|
||||
return '<a class="link-dark text-decoration-none ' . esc($class, 'attr') . '" href="' . esc($promotionUrl([
|
||||
'sort' => $key,
|
||||
'order' => $nextOrder,
|
||||
'page' => 1,
|
||||
]), 'attr') . '">' . esc($label . $indicator) . '</a>';
|
||||
};
|
||||
?>
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
@@ -23,21 +59,30 @@
|
||||
<div class="container-fluid py-3">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="mb-1">Closing Preview: <?= esc($source['name'] ?? '') ?></h2>
|
||||
<h2 class="mb-1">End-Year Checklist: <?= esc($source['name'] ?? '') ?></h2>
|
||||
<div class="text-muted">
|
||||
Status: <span class="fw-semibold"><?= esc(ucfirst($status)) ?></span>
|
||||
<?php if ($target): ?>
|
||||
<span class="mx-2">Target: <span class="fw-semibold"><?= esc($target['name'] ?? '') ?></span></span>
|
||||
<span class="mx-2">Next year: <span class="fw-semibold"><?= esc($target['name'] ?? '') ?></span></span>
|
||||
<?php endif; ?>
|
||||
<span>Generated: <?= esc($preview['generated_at'] ?? '') ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-outline-secondary" href="<?= site_url('administrator/school-years') ?>">Back to Management</a>
|
||||
<a class="btn btn-outline-secondary" href="<?= site_url('administrator/school-years') ?>">Back to School Years</a>
|
||||
</div>
|
||||
|
||||
<div class="border rounded bg-white p-3 mb-4">
|
||||
<h5 class="mb-2">How to End This Year</h5>
|
||||
<ol class="mb-0">
|
||||
<li>Clear all blocking issues below.</li>
|
||||
<li>Start the end-year process to lock the checklist.</li>
|
||||
<li>Confirm carry-forward balances, then mark this school year closed.</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<form class="row g-2 align-items-end mb-4" method="get" action="<?= site_url('administrator/school-years/' . (int) ($source['id'] ?? 0) . '/closing/preview') ?>">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="target_school_year_id">Target school year</label>
|
||||
<label class="form-label" for="target_school_year_id">Next school year</label>
|
||||
<select class="form-select" id="target_school_year_id" name="target_school_year_id">
|
||||
<option value="">Auto-select next draft year</option>
|
||||
<?php foreach (($schoolYears ?? []) as $year): ?>
|
||||
@@ -50,7 +95,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button class="btn btn-primary" type="submit">Refresh Preview</button>
|
||||
<button class="btn btn-primary" type="submit">Refresh Checklist</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -130,11 +175,11 @@
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="border rounded bg-white p-3 h-100">
|
||||
<h5>Closing Progress</h5>
|
||||
<h5>End-Year Progress</h5>
|
||||
<ol class="mb-0">
|
||||
<li>Preview generated</li>
|
||||
<li>Checklist generated</li>
|
||||
<li class="<?= $blockers === [] ? '' : 'text-muted' ?>">Validation <?= $blockers === [] ? 'passed' : 'has blockers' ?></li>
|
||||
<li class="<?= $batchStatus !== '' ? '' : 'text-muted' ?>">Closing batch <?= $batchStatus !== '' ? esc($batchStatus) : 'not created' ?></li>
|
||||
<li class="<?= $batchStatus !== '' ? '' : 'text-muted' ?>">End-year process <?= $batchStatus !== '' ? esc($batchStatus) : 'not started' ?></li>
|
||||
<li class="<?= in_array($batchStatus, ['executed', 'completed'], true) ? '' : 'text-muted' ?>">Carry-forward completed</li>
|
||||
<li class="<?= $status === 'closed' ? '' : 'text-muted' ?>">Year closed</li>
|
||||
</ol>
|
||||
@@ -142,13 +187,186 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded bg-white p-3 mb-4">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<div>
|
||||
<h5 class="mb-1">Student Promotion Preview</h5>
|
||||
<div class="text-muted small">Promotion decisions must be generated and resolved before this school year can end.</div>
|
||||
</div>
|
||||
<a class="btn btn-outline-primary btn-sm" href="<?= site_url('grading/decisions?' . http_build_query(['school_year' => $source['name'] ?? ''])) ?>">
|
||||
Manage Promotion Decisions
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Students</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['total_students'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Decided</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['with_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Pass</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['pass'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Other</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['other_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Queued</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['queued'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Assigned</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['assigned'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Applied</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['applied'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Pending</div>
|
||||
<div class="fw-semibold text-danger"><?= esc((string) ($promotionSummary['pending_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Missing</div>
|
||||
<div class="fw-semibold text-danger"><?= esc((string) ($promotionSummary['missing_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Missing Queue</div>
|
||||
<div class="fw-semibold text-danger"><?= esc((string) ($promotionSummary['missing_queue'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||||
<div class="text-muted small">
|
||||
Showing <?= esc((string) $promotionFrom) ?>-<?= esc((string) $promotionTo) ?> of <?= esc((string) $promotionTotal) ?> students
|
||||
</div>
|
||||
<form class="d-flex align-items-center gap-2" method="get" action="<?= site_url('administrator/school-years/' . (int) ($source['id'] ?? 0) . '/closing/preview') ?>">
|
||||
<?php if ($target): ?>
|
||||
<input type="hidden" name="target_school_year_id" value="<?= (int) $target['id'] ?>">
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="sort" value="<?= esc($promotionSort, 'attr') ?>">
|
||||
<input type="hidden" name="order" value="<?= esc($promotionOrder, 'attr') ?>">
|
||||
<input type="hidden" name="page" value="1">
|
||||
<label class="form-label small text-muted mb-0" for="promotion_per_page">Rows</label>
|
||||
<select class="form-select form-select-sm w-auto" id="promotion_per_page" name="per_page" onchange="this.form.submit()">
|
||||
<?php foreach ($promotionPerPageOptions as $option): ?>
|
||||
<option value="<?= (int) $option ?>" <?= (int) $option === $promotionPerPage ? 'selected' : '' ?>>
|
||||
<?= esc((string) $option) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle no-mgmt-sticky" data-no-mgmt-sticky>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $sortLink('student', 'Student') ?></th>
|
||||
<th><?= $sortLink('school_id', 'School ID') ?></th>
|
||||
<th><?= $sortLink('class', 'Class') ?></th>
|
||||
<th class="text-end"><?= $sortLink('year_score', 'Year Score') ?></th>
|
||||
<th><?= $sortLink('decision', 'Decision') ?></th>
|
||||
<th><?= $sortLink('source', 'Source') ?></th>
|
||||
<th><?= $sortLink('queue', 'Queue') ?></th>
|
||||
<th><?= $sortLink('target', 'Next Year Placement') ?></th>
|
||||
<th><?= $sortLink('status', 'Status') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($promotionTotal === 0): ?>
|
||||
<tr><td colspan="9" class="text-muted">No active class assignments found for this school year.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($promotionRows as $row): ?>
|
||||
<?php
|
||||
$rowStatus = (string) ($row['status'] ?? 'missing');
|
||||
$statusClass = $rowStatus === 'decided' ? 'success' : 'danger';
|
||||
$queueStatus = (string) ($row['queue_status'] ?? '');
|
||||
$sourceLabel = (string) ($row['source'] ?? '');
|
||||
if ($sourceLabel === 'automatic_kg_age') {
|
||||
$sourceLabel = 'Auto KG age';
|
||||
}
|
||||
$targetLabel = trim((string) ($row['target_section'] ?? ''));
|
||||
if ($targetLabel === '') {
|
||||
$targetLabel = trim((string) ($row['target_class'] ?? ''));
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td><?= esc($row['student_name'] ?? ('Student #' . (int) ($row['student_id'] ?? 0))) ?></td>
|
||||
<td><?= esc($row['school_id'] ?? '') ?></td>
|
||||
<td><?= esc($row['class_section_name'] ?? '') ?></td>
|
||||
<td class="text-end"><?= esc($score($row['year_score'] ?? null)) ?></td>
|
||||
<td><?= esc(($row['decision'] ?? '') !== '' ? $row['decision'] : '-') ?></td>
|
||||
<td><?= esc($sourceLabel) ?></td>
|
||||
<td><?= esc($queueStatus !== '' ? ucfirst($queueStatus) : '-') ?></td>
|
||||
<td><?= esc($targetLabel !== '' ? $targetLabel : '-') ?></td>
|
||||
<td><span class="badge bg-<?= esc($statusClass) ?>"><?= esc(ucfirst($rowStatus)) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php if ($promotionPageCount > 1): ?>
|
||||
<?php
|
||||
$pageStart = max(1, $promotionPage - 2);
|
||||
$pageEnd = min($promotionPageCount, $promotionPage + 2);
|
||||
if ($pageEnd - $pageStart < 4) {
|
||||
$pageStart = max(1, $pageEnd - 4);
|
||||
$pageEnd = min($promotionPageCount, $pageStart + 4);
|
||||
}
|
||||
?>
|
||||
<nav aria-label="Student promotion pages">
|
||||
<ul class="pagination pagination-sm justify-content-end mb-0">
|
||||
<li class="page-item <?= $promotionPage <= 1 ? 'disabled' : '' ?>">
|
||||
<a class="page-link" href="<?= esc($promotionUrl(['page' => max(1, $promotionPage - 1)]), 'attr') ?>">Previous</a>
|
||||
</li>
|
||||
<?php for ($pageNumber = $pageStart; $pageNumber <= $pageEnd; $pageNumber++): ?>
|
||||
<li class="page-item <?= $pageNumber === $promotionPage ? 'active' : '' ?>">
|
||||
<a class="page-link" href="<?= esc($promotionUrl(['page' => $pageNumber]), 'attr') ?>"><?= esc((string) $pageNumber) ?></a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
<li class="page-item <?= $promotionPage >= $promotionPageCount ? 'disabled' : '' ?>">
|
||||
<a class="page-link" href="<?= esc($promotionUrl(['page' => min($promotionPageCount, $promotionPage + 1)]), 'attr') ?>">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="border rounded bg-white p-3 mb-4">
|
||||
<h5>Carry-Forward Families</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<table class="table table-sm align-middle no-mgmt-sticky" data-no-mgmt-sticky>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Family</th>
|
||||
<th>Parent</th>
|
||||
<th class="text-end">Source Balance</th>
|
||||
<th class="text-end">Credits</th>
|
||||
<th class="text-end">Adjustments</th>
|
||||
@@ -157,11 +375,17 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ($carryForward === []): ?>
|
||||
<tr><td colspan="5" class="text-muted">No carry-forward balances found.</td></tr>
|
||||
<tr><td colspan="6" class="text-muted">No carry-forward balances found.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($carryForward as $row): ?>
|
||||
<tr>
|
||||
<td><?= esc($row['family']) ?></td>
|
||||
<td>
|
||||
<div><?= esc($row['parent'] ?? ('Parent #' . (int) ($row['family_id'] ?? 0))) ?></div>
|
||||
<?php if (! empty($row['parent_email'])): ?>
|
||||
<div class="text-muted small"><?= esc($row['parent_email']) ?></div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end"><?= esc($money($row['source_balance'])) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['credit_amount'])) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['adjustment_amount'])) ?></td>
|
||||
@@ -178,28 +402,37 @@
|
||||
<form action="<?= site_url('administrator/school-years/' . (int) $source['id'] . '/closing/start') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="target_school_year_id" value="<?= (int) $target['id'] ?>">
|
||||
<button class="btn btn-warning" type="submit">Begin Closing</button>
|
||||
<button class="btn btn-warning" type="submit">Close Year</button>
|
||||
</form>
|
||||
<?php elseif ($status === 'active'): ?>
|
||||
<button class="btn btn-warning" type="button" disabled>Close Year</button>
|
||||
<span class="align-self-center text-muted small">
|
||||
<?php if (! $target): ?>
|
||||
Create or select the next school year first.
|
||||
<?php elseif ($blockers !== []): ?>
|
||||
Resolve the blocking issues above first.
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($status === 'closing' && $batchStatus === 'started'): ?>
|
||||
<form action="<?= site_url('administrator/school-years/' . (int) $source['id'] . '/closing/execute') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<button class="btn btn-primary" type="submit">Execute Carry-Forward</button>
|
||||
<button class="btn btn-primary" type="submit">Confirm Carry-Forward</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($status === 'closing' && $batchStatus === 'executed'): ?>
|
||||
<form action="<?= site_url('administrator/school-years/' . (int) $source['id'] . '/closing/complete') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<button class="btn btn-success" type="submit">Complete Closing</button>
|
||||
<button class="btn btn-success" type="submit">Close Year</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($status === 'closing' && ! in_array($batchStatus, ['executed', 'completed'], true)): ?>
|
||||
<form action="<?= site_url('administrator/school-years/' . (int) $source['id'] . '/closing/cancel') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<button class="btn btn-outline-secondary" type="submit">Cancel Closing</button>
|
||||
<button class="btn btn-outline-secondary" type="submit">Cancel End-Year Process</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
'archived' => 'bg-dark',
|
||||
];
|
||||
$formatDate = static fn ($value): string => $value ? esc((string) $value) : 'Not set';
|
||||
$money = static fn ($value): string => '$' . number_format((float) $value, 2);
|
||||
$activeId = (int) ($activeYear['id'] ?? 0);
|
||||
$nextDraftId = (int) ($nextDraftYear['id'] ?? 0);
|
||||
$closingPreviewUrl = $activeId > 0
|
||||
? site_url('administrator/school-years/' . $activeId . '/closing/preview' . ($nextDraftId > 0 ? '?' . http_build_query(['target_school_year_id' => $nextDraftId]) : ''))
|
||||
: '';
|
||||
?>
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
@@ -19,46 +25,53 @@
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="container-fluid py-3">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
<h2 class="mb-1">School Year Management</h2>
|
||||
<div class="text-muted">
|
||||
Active year:
|
||||
<?php if (! empty($activeYear)): ?>
|
||||
<span class="badge bg-success"><?= esc($activeYear['name']) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-danger">None</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<h2 class="mb-1">School Years</h2>
|
||||
<div class="text-muted">End the current school year after its checklist is clear, then start the next year.</div>
|
||||
</div>
|
||||
<button class="btn btn-success" type="button" data-bs-toggle="collapse" data-bs-target="#schoolYearCreateForm">
|
||||
Add School Year
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded bg-white p-3 h-100">
|
||||
<div class="text-muted small">Active Year</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc($activeYear['name'] ?? 'None') ?></div>
|
||||
<div class="border rounded bg-white p-3 mb-4">
|
||||
<div class="d-flex flex-wrap justify-content-between gap-3">
|
||||
<div>
|
||||
<h5 class="mb-2">End Current School Year</h5>
|
||||
<div class="d-flex flex-wrap gap-2 mb-2">
|
||||
<span class="badge <?= $activeYear ? 'bg-success' : 'bg-secondary' ?>">Current: <?= esc($activeYear['name'] ?? 'None') ?></span>
|
||||
<span class="badge <?= $nextDraftYear ? 'bg-info text-dark' : 'bg-secondary' ?>">Next: <?= esc($nextDraftYear['name'] ?? 'Not created') ?></span>
|
||||
<?php if ($closingYear): ?>
|
||||
<span class="badge bg-warning text-dark">Ending: <?= esc($closingYear['name']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($closingYear): ?>
|
||||
<div class="text-muted">Finish the end-year checklist for <?= esc($closingYear['name']) ?> before starting another year.</div>
|
||||
<?php elseif ($activeYear && $nextDraftYear): ?>
|
||||
<div class="text-muted">Review promotions and balances for <?= esc($activeYear['name']) ?>. When all blockers are resolved, end it and start <?= esc($nextDraftYear['name']) ?>.</div>
|
||||
<?php elseif ($activeYear): ?>
|
||||
<div class="text-muted">Create the next school year first. It will stay as a draft until the current year is closed.</div>
|
||||
<?php elseif ($nextDraftYear): ?>
|
||||
<div class="text-muted">No year is active. Start <?= esc($nextDraftYear['name']) ?> when you are ready.</div>
|
||||
<?php else: ?>
|
||||
<div class="text-muted">Create a school year draft to begin.</div>
|
||||
<?php endif; ?>
|
||||
<ol class="small text-muted ps-3 mt-2 mb-0">
|
||||
<li>Create the next year as a draft.</li>
|
||||
<li>Review the end-year checklist for the current year.</li>
|
||||
<li>End the current year, then start the next year.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded bg-white p-3 h-100">
|
||||
<div class="text-muted small">Next Draft</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc($nextDraftYear['name'] ?? 'None') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded bg-white p-3 h-100">
|
||||
<div class="text-muted small">Currently Closing</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc($closingYear['name'] ?? 'None') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded bg-white p-3 h-100">
|
||||
<div class="text-muted small">Archived Years</div>
|
||||
<div class="fs-5 fw-semibold"><?= esc((string) ($archivedCount ?? 0)) ?></div>
|
||||
<div class="d-flex align-items-start">
|
||||
<?php if ($closingYear): ?>
|
||||
<a class="btn btn-primary" href="<?= site_url('administrator/school-years/' . (int) $closingYear['id'] . '/closing/preview') ?>">Finish End-Year Checklist</a>
|
||||
<?php elseif ($activeYear && $nextDraftYear): ?>
|
||||
<a class="btn btn-primary" href="<?= esc($closingPreviewUrl, 'attr') ?>">Close Year</a>
|
||||
<?php elseif ($activeYear): ?>
|
||||
<button class="btn btn-success" type="button" data-bs-toggle="collapse" data-bs-target="#schoolYearCreateForm">Create Next Year</button>
|
||||
<?php elseif ($nextDraftYear): ?>
|
||||
<button class="btn btn-success" type="button" data-bs-toggle="modal" data-bs-target="#activateSchoolYearModal<?= $nextDraftId ?>">Start <?= esc($nextDraftYear['name']) ?></button>
|
||||
<?php else: ?>
|
||||
<button class="btn btn-success" type="button" data-bs-toggle="collapse" data-bs-target="#schoolYearCreateForm">Create School Year</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,6 +84,15 @@
|
||||
<label class="form-label" for="new_school_year_name">School Year</label>
|
||||
<input class="form-control" id="new_school_year_name" name="name" placeholder="2026-2027" required pattern="\d{4}-\d{4}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="new_previous_school_year_id">Previous Year</label>
|
||||
<select class="form-select" id="new_previous_school_year_id" name="previous_school_year_id">
|
||||
<option value="">None</option>
|
||||
<?php foreach (($schoolYears ?? []) as $existingYear): ?>
|
||||
<option value="<?= (int) ($existingYear['id'] ?? 0) ?>"><?= esc($existingYear['name'] ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="new_school_year_starts_on">Starts On</label>
|
||||
<input class="form-control" id="new_school_year_starts_on" name="starts_on" type="date">
|
||||
@@ -106,10 +128,8 @@
|
||||
<th>School Year</th>
|
||||
<th>Dates</th>
|
||||
<th>Status</th>
|
||||
<th>Students</th>
|
||||
<th>Families</th>
|
||||
<th>Financial Balance</th>
|
||||
<th>Last Transition</th>
|
||||
<th>Verify</th>
|
||||
<th>Last Activity</th>
|
||||
<th>Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@@ -121,6 +141,8 @@
|
||||
$status = (string) ($year['status'] ?? 'draft');
|
||||
$readonly = in_array($status, ['closed', 'archived'], true);
|
||||
$transition = $latestTransitions[$id] ?? null;
|
||||
$verify = $yearVerification[$id] ?? [];
|
||||
$previousYearName = $schoolYearNamesById[(int) ($year['previous_school_year_id'] ?? 0)] ?? null;
|
||||
?>
|
||||
<tr>
|
||||
<td class="fw-semibold"><?= esc($year['name'] ?? '') ?></td>
|
||||
@@ -133,9 +155,13 @@
|
||||
<?= esc(ucfirst($status)) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-muted">-</td>
|
||||
<td class="text-muted">-</td>
|
||||
<td class="text-muted">Preview</td>
|
||||
<td class="small">
|
||||
<div>Previous: <span class="fw-semibold"><?= esc($previousYearName ?? 'Not linked') ?></span></div>
|
||||
<div>Students: <span class="fw-semibold"><?= esc((string) ($verify['students'] ?? 0)) ?></span></div>
|
||||
<div>Promoted: <span class="fw-semibold"><?= esc((string) ($verify['promoted'] ?? 0)) ?></span> / Decisions: <?= esc((string) ($verify['decisions'] ?? 0)) ?></div>
|
||||
<div>Queued: <span class="fw-semibold"><?= esc((string) ($verify['queued'] ?? 0)) ?></span></div>
|
||||
<div>Carry-forward: <span class="fw-semibold"><?= esc($money($verify['carry_forward_balance'] ?? 0)) ?></span></div>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($transition): ?>
|
||||
<div><?= esc($transition['action'] ?? '') ?></div>
|
||||
@@ -159,11 +185,13 @@
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if ($status === 'draft'): ?>
|
||||
<li>
|
||||
<button class="dropdown-item" type="button" data-bs-toggle="modal" data-bs-target="#activateSchoolYearModal<?= $id ?>">
|
||||
Activate
|
||||
</button>
|
||||
</li>
|
||||
<?php if (empty($activeYear) && empty($closingYear)): ?>
|
||||
<li>
|
||||
<button class="dropdown-item" type="button" data-bs-toggle="modal" data-bs-target="#activateSchoolYearModal<?= $id ?>">
|
||||
Start this year
|
||||
</button>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="<?= site_url('administrator/school-years/' . $id . '/delete-draft') ?>" method="post" onsubmit="return confirm('Delete this unused draft school year?');">
|
||||
@@ -173,21 +201,21 @@
|
||||
</li>
|
||||
<?php elseif ($status === 'active'): ?>
|
||||
<li>
|
||||
<a class="dropdown-item" href="<?= site_url('administrator/school-years/' . $id . '/closing/preview') ?>">Preview closing</a>
|
||||
<a class="dropdown-item" href="<?= site_url('administrator/school-years/' . $id . '/closing/preview' . ($nextDraftId > 0 ? '?' . http_build_query(['target_school_year_id' => $nextDraftId]) : '')) ?>">Close year</a>
|
||||
</li>
|
||||
<?php elseif ($status === 'closing'): ?>
|
||||
<li>
|
||||
<a class="dropdown-item" href="<?= site_url('administrator/school-years/' . $id . '/closing/preview') ?>">View closing</a>
|
||||
<a class="dropdown-item" href="<?= site_url('administrator/school-years/' . $id . '/closing/preview') ?>">Finish end-year checklist</a>
|
||||
</li>
|
||||
<li>
|
||||
<form action="<?= site_url('administrator/school-years/' . $id . '/closing/cancel') ?>" method="post" onsubmit="return confirm('Cancel closing for this school year?');">
|
||||
<form action="<?= site_url('administrator/school-years/' . $id . '/closing/cancel') ?>" method="post" onsubmit="return confirm('Cancel the end-year process for this school year?');">
|
||||
<?= csrf_field() ?>
|
||||
<button class="dropdown-item" type="submit">Cancel closing</button>
|
||||
<button class="dropdown-item" type="submit">Cancel end-year process</button>
|
||||
</form>
|
||||
</li>
|
||||
<?php elseif ($status === 'closed'): ?>
|
||||
<li>
|
||||
<a class="dropdown-item" href="<?= site_url('administrator/school-years/' . $id . '/closing/preview') ?>">View closing report</a>
|
||||
<a class="dropdown-item" href="<?= site_url('administrator/school-years/' . $id . '/closing/preview') ?>">View end-year report</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="dropdown-item" type="button" data-bs-toggle="modal" data-bs-target="#reopenSchoolYearModal<?= $id ?>">
|
||||
@@ -220,6 +248,8 @@
|
||||
$id = (int) ($year['id'] ?? 0);
|
||||
$status = (string) ($year['status'] ?? 'draft');
|
||||
$readonly = in_array($status, ['closed', 'archived', 'closing'], true);
|
||||
$verify = $yearVerification[$id] ?? [];
|
||||
$previousYearName = $schoolYearNamesById[(int) ($year['previous_school_year_id'] ?? 0)] ?? null;
|
||||
?>
|
||||
<?php if (! $readonly): ?>
|
||||
<div class="modal fade" id="editSchoolYearModal<?= $id ?>" tabindex="-1" aria-hidden="true">
|
||||
@@ -231,6 +261,47 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="border rounded bg-light p-3 mb-3">
|
||||
<h6 class="mb-2">Current Verification Data</h6>
|
||||
<div class="row g-2 small">
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">Previous year</div>
|
||||
<div class="fw-semibold"><?= esc($previousYearName ?? 'Not linked') ?></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">Students in year</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($verify['students'] ?? 0)) ?></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">Promotion decisions</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($verify['decisions'] ?? 0)) ?></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">Promoted students</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($verify['promoted'] ?? 0)) ?></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">Queued for next year</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($verify['queued'] ?? 0)) ?></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">Carry-forward balance</div>
|
||||
<div class="fw-semibold"><?= esc($money($verify['carry_forward_balance'] ?? 0)) ?></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">Positive balances</div>
|
||||
<div class="fw-semibold"><?= esc($money($verify['positive_balance'] ?? 0)) ?></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">Credits</div>
|
||||
<div class="fw-semibold"><?= esc($money($verify['credit_balance'] ?? 0)) ?></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="text-muted">End-year status</div>
|
||||
<div class="fw-semibold"><?= esc(($verify['closing_status'] ?? '') !== '' ? ucfirst((string) $verify['closing_status']) : 'Not started') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label" for="school_year_name_<?= $id ?>">School Year</label>
|
||||
@@ -245,6 +316,19 @@
|
||||
<input class="form-control" id="school_year_ends_on_<?= $id ?>" name="ends_on" type="date" value="<?= esc($year['ends_on'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="previous_school_year_id_<?= $id ?>">Previous Year</label>
|
||||
<select class="form-select" id="previous_school_year_id_<?= $id ?>" name="previous_school_year_id">
|
||||
<option value="">None</option>
|
||||
<?php foreach (($schoolYears ?? []) as $existingYear): ?>
|
||||
<?php if ((int) ($existingYear['id'] ?? 0) !== $id): ?>
|
||||
<option value="<?= (int) ($existingYear['id'] ?? 0) ?>" <?= (int) ($year['previous_school_year_id'] ?? 0) === (int) ($existingYear['id'] ?? 0) ? 'selected' : '' ?>>
|
||||
<?= esc($existingYear['name'] ?? '') ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label" for="registration_starts_on_<?= $id ?>">Registration Starts</label>
|
||||
@@ -273,24 +357,24 @@
|
||||
<form class="modal-content" action="<?= site_url('administrator/school-years/' . $id . '/activate') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Activate <?= esc($year['name'] ?? '') ?></h5>
|
||||
<h5 class="modal-title">Start <?= esc($year['name'] ?? '') ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="mb-2">The selected school year will become active.</p>
|
||||
<p class="mb-2">This school year will become the active year.</p>
|
||||
<?php if (! empty($activeYear)): ?>
|
||||
<p class="mb-3">Current active year <strong><?= esc($activeYear['name']) ?></strong> will move to <strong>closing</strong>.</p>
|
||||
<p class="mb-3">Current active year <strong><?= esc($activeYear['name']) ?></strong> will move to <strong>end-year review</strong>.</p>
|
||||
<?php endif; ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="1" name="confirm_activation" id="confirm_activation_<?= $id ?>" required>
|
||||
<label class="form-check-label" for="confirm_activation_<?= $id ?>">
|
||||
I understand activation does not complete closing for the previous year.
|
||||
I understand this starts the selected year.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-success">Activate</button>
|
||||
<button type="submit" class="btn btn-success">Start Year</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -101,9 +101,9 @@ function orderNum($v)
|
||||
?>
|
||||
|
||||
<div class="mb-4 text-center">
|
||||
<p class="text-muted small mb-1">Active Students</p>
|
||||
<p class="text-muted small mb-1">Enrolled Students</p>
|
||||
<div class="display-4 fw-semibold"><?= (int)$totalStudents ?></div>
|
||||
<p class="text-muted small mb-0">Reflects only currently active students, so removals update the count instantly.</p>
|
||||
<p class="text-muted small mb-0">Reflects accepted enrollments for the selected school year.</p>
|
||||
</div>
|
||||
|
||||
<form method="get" class="row g-3 mb-4 align-items-end">
|
||||
@@ -132,8 +132,8 @@ function orderNum($v)
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header py-2 d-flex justify-content-between align-items-center">
|
||||
<strong>Active Student Snapshot</strong>
|
||||
<span class="badge bg-primary"><?= (int)$totalStudents ?> active</span>
|
||||
<strong>Enrolled Student Snapshot</strong>
|
||||
<span class="badge bg-primary"><?= (int)$totalStudents ?> enrolled</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
|
||||
@@ -66,7 +66,11 @@
|
||||
if ($userId) {
|
||||
$configModel = new \App\Models\ConfigurationModel();
|
||||
$teacherClassModel = new \App\Models\TeacherClassModel();
|
||||
$sy = $configModel->getConfig('school_year');
|
||||
try {
|
||||
$sy = service('schoolYearContext')->resolve(service('request'))->yearName();
|
||||
} catch (\Throwable $e) {
|
||||
$sy = $configModel->getConfig('school_year');
|
||||
}
|
||||
$sem = $configModel->getConfig('semester');
|
||||
$classOptions = $teacherClassModel->getClassAssignmentsByUserId((int)$userId, (string)$sy, (string)$sem);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
<div class="container mt-4">
|
||||
<h3>Competition Winners</h3>
|
||||
<p class="text-muted">Published competition winner announcements.</p>
|
||||
<p class="text-muted">
|
||||
Published competition winner announcements
|
||||
<?php if (!empty($schoolYear)): ?>
|
||||
for <?= esc($schoolYear) ?>
|
||||
<?php endif; ?>.
|
||||
</p>
|
||||
|
||||
<div class="list-group">
|
||||
<?php foreach ($competitions as $c): ?>
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<h3><?= esc($competition['title']) ?></h3>
|
||||
<div class="text-muted mb-3">
|
||||
Published: <?= esc($competition['published_at'] ?? '') ?>
|
||||
<?php if (!empty($schoolYear)): ?>
|
||||
<span class="ms-2">School Year: <?= esc($schoolYear) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (empty($winnersByClass)): ?>
|
||||
|
||||
Reference in New Issue
Block a user