fix enrollment logic, add financial aid, fix class distribution
Tests / PHPUnit (push) Failing after 1m6s

This commit is contained in:
root
2026-08-15 15:07:16 -04:00
parent c12bb59372
commit 4603d9ced2
95 changed files with 6892 additions and 1295 deletions
@@ -152,17 +152,17 @@
<div class="card-body">
<div class="row g-3 align-items-end mb-2">
<div class="col-md-2">
<label for="sectionCount" class="form-label">Number of Sections</label>
<label for="sectionCount" class="form-label">Max Number of Sections</label>
<input type="number" min="1" id="sectionCount" class="form-control" placeholder="e.g. 2" />
</div>
<div class="col-md-2">
<label for="minStudents" class="form-label">Minimum Students</label>
<label for="minStudents" class="form-label">Min Students Per Section</label>
<input type="number" min="1" id="minStudents" class="form-control" placeholder="e.g. 20" />
</div>
<div class="col-md-2">
<!--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-->
<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>
@@ -217,6 +217,13 @@
</tr>
</thead>
<tbody id="tblBody"></tbody>
<tfoot class="table-light">
<tr>
<th>Total</th>
<th class="text-end" id="allStudentsTotal">0</th>
<th></th>
</tr>
</tfoot>
</table>
</div>
</div>
@@ -247,6 +254,7 @@
}, $classes ?? [])), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
const tblBody = document.getElementById('tblBody');
const allStudentsTotal = document.getElementById('allStudentsTotal');
const sectionCountInput = document.getElementById('sectionCount');
const minInput = document.getElementById('minStudents');
const maxInput = document.getElementById('maxStudents');
@@ -352,6 +360,20 @@
});
});
if (!resolvedClassId && selectedClassId > 0) {
const firstSection = (sectionsByClassId[String(selectedClassId)] || [])[0] || null;
if (firstSection) {
select.value = String(firstSection.id);
resolvedClassId = selectedClassId;
} else {
const baseClass = baseClasses.find(c => c.class_id === selectedClassId) || null;
if (baseClass) {
select.value = String(baseClass.class_section_id);
resolvedClassId = selectedClassId;
}
}
}
const selectedOption = select.options[select.selectedIndex] || null;
if (!resolvedClassId && selectedOption) {
resolvedClassId = parseInt(selectedOption.dataset.classId || '0', 10);
@@ -380,8 +402,10 @@
function buildInitialTable(rows) {
tblBody.innerHTML = '';
rowIndexByClassId = {};
let allTotal = 0;
rows.forEach(function(r){
allTotal += parseInt(r.total || '0', 10) || 0;
const tr = document.createElement('tr');
tr.dataset.classId = r.class_id;
tr.dataset.classSectionId = r.class_section_id;
@@ -417,13 +441,16 @@
renderSectionsForRow(r.class_section_id, r.sections, r.students || []);
}
});
if (allStudentsTotal) {
allStudentsTotal.textContent = String(allTotal);
}
applyStudentSearch();
}
function runDistribution(baseSectionId, baseName) {
const sectionCount = parseInt(sectionCountInput.value || '0', 10);
const minStudents = parseInt(minInput.value || '0', 10);
const maxStudents = parseInt(maxInput.value || '0', 10);
const maxStudents = parseInt((maxInput && maxInput.value) || '0', 10);
if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) {
msgEl.textContent = 'Enter number of sections and minimum students first.';
return;
@@ -611,6 +638,7 @@
student_name: assignment.student_name || 'Student',
age_at_reference: assignment.age_at_reference ?? null,
gender: assignment.gender || '',
previous_final_score: assignment.previous_final_score ?? null,
last_year_class_section: assignment.last_year_class_section || '',
class_id: parseInt(assignment.class_id || section.class_id || '0', 10),
class_section_id: parseInt(assignment.class_section_id || section.class_section_id || '0', 10),
@@ -623,6 +651,27 @@
return out;
}
function assignmentStats(assignments) {
const stats = { male: 0, female: 0, scoreTotal: 0, scoreCount: 0 };
(Array.isArray(assignments) ? assignments : []).forEach(function(assignment){
const gender = String(assignment.gender || '').trim().toLowerCase();
if (gender === 'female' || gender === 'f') {
stats.female++;
} else if (gender === 'male' || gender === 'm') {
stats.male++;
}
const score = Number(assignment.previous_final_score);
if (Number.isFinite(score)) {
stats.scoreTotal += score;
stats.scoreCount++;
}
});
stats.averageScore = stats.scoreCount ? stats.scoreTotal / stats.scoreCount : null;
return stats;
}
function renderClassRoster(tr, assignments) {
const wrap = document.createElement('div');
wrap.className = 'distribution-class-roster';
@@ -641,6 +690,23 @@
title.appendChild(count);
wrap.appendChild(title);
const stats = assignmentStats(assignments);
const meta = document.createElement('div');
meta.className = 'distribution-meta';
if (stats.averageScore !== null) {
const avg = document.createElement('span');
avg.className = 'badge text-bg-light';
avg.textContent = 'Avg ' + stats.averageScore.toFixed(2);
meta.appendChild(avg);
}
if (stats.male || stats.female) {
const gender = document.createElement('span');
gender.className = 'badge text-bg-light';
gender.textContent = 'M ' + stats.male + ' / F ' + stats.female;
meta.appendChild(gender);
}
if (meta.children.length) wrap.appendChild(meta);
if (!assignments.length) {
const empty = document.createElement('div');
empty.className = 'distribution-empty';