This commit is contained in:
@@ -5,37 +5,26 @@
|
||||
<div class="content">
|
||||
<h2 class="text-center mt-4 mb-3">Auto-Distribute Students into Sections</h2>
|
||||
|
||||
<div class="row g-2 align-items-center justify-content-center mb-3">
|
||||
<div class="col-auto"><label for="adYearSelect" class="col-form-label">School year</label></div>
|
||||
<div class="col-auto">
|
||||
<form method="get" action="<?= site_url('administrator/sections/auto-distribute') ?>" class="d-flex align-items-center gap-2">
|
||||
<select id="adYearSelect" name="schoolYear" class="form-select form-select-sm" style="min-width: 180px;">
|
||||
<?php
|
||||
$years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : [];
|
||||
if (empty($years) && !empty($selectedYear)) $years = [$selectedYear];
|
||||
foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? $y['school_year'] : (string)$y; ?>
|
||||
<option value="<?= esc($val) ?>" <?= ((string)($selectedYear ?? '') === (string)$val) ? 'selected' : '' ?>>
|
||||
<?= esc($val) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-secondary btn-sm">Apply</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-end mb-2">
|
||||
<div class="col-md-3">
|
||||
<label for="globalPer" class="form-label">Students per section</label>
|
||||
<input type="number" min="1" id="globalPer" class="form-control" placeholder="e.g. 20" />
|
||||
<div class="col-md-2">
|
||||
<label for="sectionCount" class="form-label">Number of Sections</label>
|
||||
<input type="number" min="1" id="sectionCount" class="form-control" placeholder="e.g. 2" />
|
||||
</div>
|
||||
<div class="col-md-4 d-flex gap-2">
|
||||
<div class="col-md-2">
|
||||
<label for="minStudents" class="form-label">Minimum Students</label>
|
||||
<input type="number" min="1" id="minStudents" class="form-control" placeholder="e.g. 20" />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label for="maxStudents" class="form-label">Maximum Students</label>
|
||||
<input type="number" min="1" id="maxStudents" class="form-control" placeholder="Optional" />
|
||||
</div>
|
||||
<div class="col-md-3 d-flex gap-2">
|
||||
<button type="button" id="refreshTotalsBtn" class="btn btn-outline-secondary">Refresh Totals</button>
|
||||
<button type="button" id="generateAllBtn" class="btn btn-primary">Generate All</button>
|
||||
</div>
|
||||
<div class="col-md-7 text-end">
|
||||
<div class="col-md-3 text-end">
|
||||
<span id="pageMsg" class="small text-muted"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,7 +35,7 @@
|
||||
<tr id="tblHeader">
|
||||
<th>Class</th>
|
||||
<th>Total</th>
|
||||
<th>Sections Needed</th>
|
||||
<th>Sections</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -73,17 +62,18 @@
|
||||
|
||||
const tblBody = document.getElementById('tblBody');
|
||||
const tblHeader = document.getElementById('tblHeader');
|
||||
const perInput = document.getElementById('globalPer');
|
||||
const sectionCountInput = document.getElementById('sectionCount');
|
||||
const minInput = document.getElementById('minStudents');
|
||||
const maxInput = document.getElementById('maxStudents');
|
||||
const msgEl = document.getElementById('pageMsg');
|
||||
const refreshBtn= document.getElementById('refreshTotalsBtn');
|
||||
|
||||
let headerSections = []; // list of generated section names as columns
|
||||
let rowIndexByClassId = {}; // mapping to locate rows
|
||||
|
||||
function calcNeeded(total) {
|
||||
const per = parseInt(perInput.value || '0', 10);
|
||||
if (!per || per <= 0) return '';
|
||||
return Math.ceil(total / per);
|
||||
function selectedSectionCount() {
|
||||
const count = parseInt(sectionCountInput.value || '0', 10);
|
||||
return count > 0 ? count : '';
|
||||
}
|
||||
|
||||
function ensureSectionColumns(sectionNames) {
|
||||
@@ -129,7 +119,7 @@
|
||||
|
||||
const tdNeed = document.createElement('td');
|
||||
tdNeed.className = 'text-end need-cell';
|
||||
tdNeed.textContent = calcNeeded(r.total);
|
||||
tdNeed.textContent = selectedSectionCount();
|
||||
tr.appendChild(tdNeed);
|
||||
|
||||
const tdAct = document.createElement('td');
|
||||
@@ -143,24 +133,36 @@
|
||||
tblBody.appendChild(tr);
|
||||
rowIndexByClassId[r.class_id] = tr;
|
||||
});
|
||||
|
||||
rows.forEach(function(r){
|
||||
if (Array.isArray(r.sections) && r.sections.length) {
|
||||
renderSectionsForRow(r.class_section_name || '', r.sections);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateNeeds() {
|
||||
document.querySelectorAll('#tblBody tr').forEach(function(tr){
|
||||
const total = parseInt(tr.children[1].textContent || '0', 10);
|
||||
const needCell = tr.querySelector('.need-cell');
|
||||
if (needCell) needCell.textContent = calcNeeded(total);
|
||||
if (needCell) needCell.textContent = selectedSectionCount();
|
||||
});
|
||||
}
|
||||
|
||||
function runDistribution(baseSectionId, baseName) {
|
||||
const per = parseInt(perInput.value || '0', 10);
|
||||
if (!per || per <= 0) { msgEl.textContent = 'Enter students per section first.'; return; }
|
||||
const sectionCount = parseInt(sectionCountInput.value || '0', 10);
|
||||
const minStudents = parseInt(minInput.value || '0', 10);
|
||||
const maxStudents = parseInt(maxInput.value || '0', 10);
|
||||
if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) {
|
||||
msgEl.textContent = 'Enter number of sections and minimum students first.';
|
||||
return;
|
||||
}
|
||||
|
||||
msgEl.textContent = 'Distributing ' + (baseName || '') + '...';
|
||||
const fd = new FormData();
|
||||
fd.append('class_section_id', String(baseSectionId));
|
||||
fd.append('students_per_section', String(per));
|
||||
fd.append('section_count', String(sectionCount));
|
||||
fd.append('min_students_per_section', String(minStudents));
|
||||
if (maxStudents > 0) fd.append('max_students_per_section', String(maxStudents));
|
||||
fd.append('school_year', selectedYear);
|
||||
// CSRF
|
||||
const csrfNameEl = document.getElementById('csrfName');
|
||||
@@ -184,24 +186,39 @@
|
||||
|
||||
msgEl.textContent = res && res.message ? res.message : 'Completed.';
|
||||
|
||||
// Collect section names, then ensure header columns
|
||||
const names = res.sections.map(s => s.class_section_name).filter(Boolean);
|
||||
ensureSectionColumns(names);
|
||||
|
||||
// Fill row cells for this class
|
||||
const tr = Array.from(document.querySelectorAll('#tblBody tr')).find(function(_tr){
|
||||
return (_tr.children[0].textContent || '') === (baseName || '');
|
||||
});
|
||||
if (!tr) return;
|
||||
|
||||
res.sections.forEach(function(s){
|
||||
const td = tr.querySelector('td[data-section="'+ s.class_section_name +'"]');
|
||||
if (td) td.textContent = (s.total ?? 0) + ' (M'+ (s.male ?? 0) + '/F'+ (s.female ?? 0) +')';
|
||||
});
|
||||
renderSectionsForRow(baseName || '', res.sections);
|
||||
})
|
||||
.catch(() => { msgEl.textContent = 'Failed to distribute. Please try again.'; });
|
||||
}
|
||||
|
||||
function renderSectionsForRow(baseName, sections) {
|
||||
const names = sections.map(s => s.class_section_name).filter(Boolean);
|
||||
ensureSectionColumns(names);
|
||||
|
||||
const tr = Array.from(document.querySelectorAll('#tblBody tr')).find(function(_tr){
|
||||
return (_tr.children[0].textContent || '') === (baseName || '');
|
||||
});
|
||||
if (!tr) return;
|
||||
|
||||
sections.forEach(function(s){
|
||||
const td = Array.from(tr.querySelectorAll('td[data-section]')).find(cell => cell.dataset.section === s.class_section_name);
|
||||
if (!td) return;
|
||||
|
||||
const studentNames = Array.isArray(s.student_names) ? s.student_names : [];
|
||||
td.innerHTML = '';
|
||||
const count = document.createElement('div');
|
||||
count.className = 'fw-semibold small mb-1';
|
||||
count.textContent = (s.total ?? studentNames.length) + ' students';
|
||||
td.appendChild(count);
|
||||
|
||||
const list = document.createElement('div');
|
||||
list.className = 'small';
|
||||
list.style.whiteSpace = 'normal';
|
||||
list.textContent = studentNames.length ? studentNames.join(', ') : 'No students assigned';
|
||||
td.appendChild(list);
|
||||
});
|
||||
}
|
||||
|
||||
function loadTotals() {
|
||||
msgEl.textContent = 'Loading totals...';
|
||||
fetch(totalsUrl, { headers: { 'X-Requested-With': 'XMLHttpRequest' }})
|
||||
@@ -215,10 +232,14 @@
|
||||
.catch(() => { msgEl.textContent = 'Failed to load totals.'; });
|
||||
}
|
||||
|
||||
refreshBtn.addEventListener('click', function(){ updateNeeds(); });
|
||||
refreshBtn.addEventListener('click', function(){ loadTotals(); });
|
||||
document.getElementById('generateAllBtn').addEventListener('click', async function(){
|
||||
const per = parseInt(perInput.value || '0', 10);
|
||||
if (!per || per <= 0) { msgEl.textContent = 'Enter students per section first.'; return; }
|
||||
const sectionCount = parseInt(sectionCountInput.value || '0', 10);
|
||||
const minStudents = parseInt(minInput.value || '0', 10);
|
||||
if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) {
|
||||
msgEl.textContent = 'Enter number of sections and minimum students first.';
|
||||
return;
|
||||
}
|
||||
// Collect base sections from rows
|
||||
const rows = Array.from(document.querySelectorAll('#tblBody tr'))
|
||||
.map(tr => ({ id: tr.dataset.classSectionId, name: tr.dataset.className }))
|
||||
@@ -231,7 +252,7 @@
|
||||
}
|
||||
msgEl.textContent = 'All distributions completed.';
|
||||
});
|
||||
perInput.addEventListener('input', function(){ updateNeeds(); });
|
||||
sectionCountInput.addEventListener('input', function(){ updateNeeds(); });
|
||||
|
||||
loadTotals();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user