SMKBK1 commited on
Commit
73bcaec
·
verified ·
1 Parent(s): cc49b6f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +143 -2
index.html CHANGED
@@ -966,6 +966,20 @@ tbody tr:last-child td { border-bottom: none; }
966
  <div class="panel-b"><div class="dist" id="dist"></div></div>
967
  </div>
968
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
969
  <div class="panel">
970
  <div class="tools">
971
  <div class="search"><input type="search" id="search" placeholder="filter rows…"></div>
@@ -976,6 +990,14 @@ tbody tr:last-child td { border-bottom: none; }
976
  <option>Account</option>
977
  <option>Art / Other</option>
978
  </select>
 
 
 
 
 
 
 
 
979
  <a class="link hidden" id="sheetLink" href="#" target="_blank" rel="noopener">OPEN SHEET →</a>
980
  </div>
981
  <div class="table-wrap">
@@ -1104,11 +1126,120 @@ tbody tr:last-child td { border-bottom: none; }
1104
  if (x.includes("account")) return "a";
1105
  return "o";
1106
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1107
  function show(data) {
1108
  $("empty").classList.add("hidden");
1109
  $("results").classList.remove("hidden");
1110
  const list = data.rows || data.rows_preview || [];
1111
  rows = list;
 
 
 
 
1112
  $("kpiTotal").textContent = data.total ?? list.length;
1113
  $("kpiOk").textContent = data.ok ?? 0;
1114
  $("kpiFail").textContent = data.failed ?? 0;
@@ -1131,7 +1262,10 @@ tbody tr:last-child td { border-bottom: none; }
1131
  $("sheetLink").href = data.sheet_url;
1132
  $("sheetLink").classList.remove("hidden");
1133
  } else $("sheetLink").classList.add("hidden");
1134
- const prefer = ["Name","Nama","Class","Kelas","ID","Recommended Stream","Second Stream","Confidence","Prob Science %","Prob Reka Cipta %","Prob Account %","Prob Art / Other %","Key Subjects","Status"];
 
 
 
1135
  let all = data.columns || (list[0] ? Object.keys(list[0]) : []);
1136
  const ordered = [];
1137
  prefer.forEach((c) => {
@@ -1139,14 +1273,17 @@ tbody tr:last-child td { border-bottom: none; }
1139
  if (real && !ordered.includes(real)) ordered.push(real);
1140
  });
1141
  all.forEach((c) => { if (!ordered.includes(c)) ordered.push(c); });
1142
- cols = ordered.slice(0, 12);
1143
  filter();
1144
  }
 
1145
  function filter() {
1146
  const q = ($("search").value || "").toLowerCase().trim();
1147
  const stream = $("streamFilter").value;
 
1148
  let list = rows;
1149
  if (stream) list = list.filter((r) => (r["Recommended Stream"] || "") === stream);
 
1150
  if (q) list = list.filter((r) => cols.some((c) => String(r[c] ?? "").toLowerCase().includes(q)));
1151
  $("thead").innerHTML = "<tr>" + cols.map((c) => "<th>" + esc(c) + "</th>").join("") + "</tr>";
1152
  $("tbody").innerHTML = list.length ? list.map((r) => {
@@ -1159,6 +1296,9 @@ tbody tr:last-child td { border-bottom: none; }
1159
  if (c === "Recommended Stream") {
1160
  return '<td><span class="tag ' + streamTag(String(v)) + '">' + esc(String(v)) + "</span></td>";
1161
  }
 
 
 
1162
  if (c === "Confidence") {
1163
  return '<td><span class="tag ' + confTag(String(v)) + '">' + esc(String(v)) + "</span></td>";
1164
  }
@@ -1170,6 +1310,7 @@ tbody tr:last-child td { border-bottom: none; }
1170
  }
1171
  $("search").addEventListener("input", filter);
1172
  $("streamFilter").addEventListener("change", filter);
 
1173
  async function runCsv() {
1174
  if (!file) return;
1175
  load(true);
 
966
  <div class="panel-b"><div class="dist" id="dist"></div></div>
967
  </div>
968
 
969
+ <div class="panel">
970
+ <div class="panel-h">
971
+ <h3>CLASS ALLOCATION ROSTERS (4A · 4B · 4C · 4D · SASTERA)</h3>
972
+ </div>
973
+ <div class="panel-b">
974
+ <div class="dist" style="grid-template-columns: repeat(5, 1fr);" id="classDist">
975
+ <!-- Class counters rendered here -->
976
+ </div>
977
+ <div id="classTables" style="display: flex; flex-direction: column; gap: 20px; margin-top: 16px;">
978
+ <!-- Tables for 4A, 4B, 4C, 4D, SASTERA will be injected here -->
979
+ </div>
980
+ </div>
981
+ </div>
982
+
983
  <div class="panel">
984
  <div class="tools">
985
  <div class="search"><input type="search" id="search" placeholder="filter rows…"></div>
 
990
  <option>Account</option>
991
  <option>Art / Other</option>
992
  </select>
993
+ <select class="select" id="classFilter">
994
+ <option value="">ALL CLASSES</option>
995
+ <option value="4A">4A (Science Top)</option>
996
+ <option value="4B">4B (Science)</option>
997
+ <option value="4C">4C (Account)</option>
998
+ <option value="4D">4D (Reka Cipta)</option>
999
+ <option value="SASTERA">SASTERA</option>
1000
+ </select>
1001
  <a class="link hidden" id="sheetLink" href="#" target="_blank" rel="noopener">OPEN SHEET →</a>
1002
  </div>
1003
  <div class="table-wrap">
 
1126
  if (x.includes("account")) return "a";
1127
  return "o";
1128
  }
1129
+
1130
+ function assignClasses(list) {
1131
+ // Collect Science students and sort by Science Probability descending & Confidence
1132
+ const scienceStudents = [];
1133
+ const confRank = { "high": 3, "medium": 2, "borderline": 1 };
1134
+
1135
+ list.forEach((r) => {
1136
+ const stream = (r["Recommended Stream"] || "").trim();
1137
+ const prob = parseFloat(r["Prob Science %"]) || 0;
1138
+ const conf = (r["Confidence"] || "").toLowerCase().trim();
1139
+
1140
+ if (stream === "Science") {
1141
+ scienceStudents.push({ row: r, prob, confScore: confRank[conf] || 0 });
1142
+ } else if (stream === "Account") {
1143
+ r["Assigned Class"] = "4C";
1144
+ } else if (stream === "Reka Cipta") {
1145
+ r["Assigned Class"] = "4D";
1146
+ } else {
1147
+ r["Assigned Class"] = "SASTERA";
1148
+ }
1149
+ });
1150
+
1151
+ // Sort science candidates: Highest probability and highest confidence first
1152
+ scienceStudents.sort((a, b) => {
1153
+ if (b.prob !== a.prob) return b.prob - a.prob;
1154
+ return b.confScore - a.confScore;
1155
+ });
1156
+
1157
+ // Equally distribute Science candidates between 4A (Top) and 4B
1158
+ const half = Math.ceil(scienceStudents.length / 2);
1159
+ scienceStudents.forEach((item, idx) => {
1160
+ item.row["Assigned Class"] = idx < half ? "4A" : "4B";
1161
+ });
1162
+ }
1163
+
1164
+ function renderClassRosters() {
1165
+ const classNames = ["4A", "4B", "4C", "4D", "SASTERA"];
1166
+ const classMeta = {
1167
+ "4A": { title: "Class 4A — Science (High Confidence / Top)", badge: "tag-s" },
1168
+ "4B": { title: "Class 4B — Science", badge: "tag-s" },
1169
+ "4C": { title: "Class 4C — Account", badge: "tag-a" },
1170
+ "4D": { title: "Class 4D — Reka Cipta", badge: "tag-r" },
1171
+ "SASTERA": { title: "Class SASTERA — Art / Humanities", badge: "tag-o" }
1172
+ };
1173
+
1174
+ const counts = { "4A": 0, "4B": 0, "4C": 0, "4D": 0, "SASTERA": 0 };
1175
+ rows.forEach(r => {
1176
+ const c = r["Assigned Class"];
1177
+ if (counts[c] !== undefined) counts[c]++;
1178
+ });
1179
+
1180
+ // Render KPI bars for class distribution
1181
+ $("classDist").innerHTML = classNames.map(c => {
1182
+ const cnt = counts[c];
1183
+ const pct = rows.length ? Math.round((cnt / rows.length) * 100) : 0;
1184
+ return '<div class="dist-item"><div class="name">' + c + ' <b>' + cnt + '</b></div>' +
1185
+ '<div class="dist-bar"><i class="' + barClass(c === "4A" || c === "4B" ? "Science" : (c === "4C" ? "Account" : (c === "4D" ? "Reka" : "Art"))) + '" style="width:' + pct + '%"></i></div></div>';
1186
+ }).join("");
1187
+
1188
+ // Render 5 tables
1189
+ $("classTables").innerHTML = classNames.map(c => {
1190
+ const members = rows.filter(r => r["Assigned Class"] === c);
1191
+ const meta = classMeta[c];
1192
+
1193
+ let rowsHtml = members.map((m, idx) => `
1194
+ <tr>
1195
+ <td>${idx + 1}</td>
1196
+ <td><b>${esc(m["Name"] || m["Nama"] || "Student " + (idx + 1))}</b></td>
1197
+ <td><span class="tag ${streamTag(m["Recommended Stream"])}">${esc(m["Recommended Stream"] || "")}</span></td>
1198
+ <td><span class="tag ${confTag(m["Confidence"])}">${esc(m["Confidence"] || "N/A")}</span></td>
1199
+ <td>${esc(m["Prob Science %"] ? m["Prob Science %"] + "%" : "-")}</td>
1200
+ </tr>
1201
+ `).join("");
1202
+
1203
+ if (!members.length) {
1204
+ rowsHtml = `<tr><td colspan="5" style="text-align:center; color: var(--text-3); padding: 12px;">No students assigned to ${c}</td></tr>`;
1205
+ }
1206
+
1207
+ return `
1208
+ <div style="border: 1px solid var(--line); background: var(--panel-2);">
1209
+ <div style="padding: 10px 14px; border-bottom: 1px solid var(--line); display:flex; justify-content:space-between; align-items:center;">
1210
+ <strong style="font-family: var(--mono); font-size: 12px; color: var(--cyan); letter-spacing: 0.08em;">${meta.title}</strong>
1211
+ <span class="tag ${meta.badge}">${members.length} Students</span>
1212
+ </div>
1213
+ <div style="max-height: 220px; overflow-y: auto;">
1214
+ <table>
1215
+ <thead>
1216
+ <tr>
1217
+ <th style="width: 40px;">#</th>
1218
+ <th>Student Name</th>
1219
+ <th>Stream</th>
1220
+ <th>Confidence</th>
1221
+ <th>Science %</th>
1222
+ </tr>
1223
+ </thead>
1224
+ <tbody>
1225
+ ${rowsHtml}
1226
+ </tbody>
1227
+ </table>
1228
+ </div>
1229
+ </div>
1230
+ `;
1231
+ }).join("");
1232
+ }
1233
+
1234
  function show(data) {
1235
  $("empty").classList.add("hidden");
1236
  $("results").classList.remove("hidden");
1237
  const list = data.rows || data.rows_preview || [];
1238
  rows = list;
1239
+
1240
+ // Apply 5-class assignment logic
1241
+ assignClasses(rows);
1242
+
1243
  $("kpiTotal").textContent = data.total ?? list.length;
1244
  $("kpiOk").textContent = data.ok ?? 0;
1245
  $("kpiFail").textContent = data.failed ?? 0;
 
1262
  $("sheetLink").href = data.sheet_url;
1263
  $("sheetLink").classList.remove("hidden");
1264
  } else $("sheetLink").classList.add("hidden");
1265
+
1266
+ renderClassRosters();
1267
+
1268
+ const prefer = ["Name","Nama","Class","Kelas","Assigned Class","Recommended Stream","Second Stream","Confidence","Prob Science %","Prob Reka Cipta %","Prob Account %","Prob Art / Other %","Key Subjects","Status"];
1269
  let all = data.columns || (list[0] ? Object.keys(list[0]) : []);
1270
  const ordered = [];
1271
  prefer.forEach((c) => {
 
1273
  if (real && !ordered.includes(real)) ordered.push(real);
1274
  });
1275
  all.forEach((c) => { if (!ordered.includes(c)) ordered.push(c); });
1276
+ cols = ordered.slice(0, 13);
1277
  filter();
1278
  }
1279
+
1280
  function filter() {
1281
  const q = ($("search").value || "").toLowerCase().trim();
1282
  const stream = $("streamFilter").value;
1283
+ const assignedClass = $("classFilter") ? $("classFilter").value : "";
1284
  let list = rows;
1285
  if (stream) list = list.filter((r) => (r["Recommended Stream"] || "") === stream);
1286
+ if (assignedClass) list = list.filter((r) => (r["Assigned Class"] || "") === assignedClass);
1287
  if (q) list = list.filter((r) => cols.some((c) => String(r[c] ?? "").toLowerCase().includes(q)));
1288
  $("thead").innerHTML = "<tr>" + cols.map((c) => "<th>" + esc(c) + "</th>").join("") + "</tr>";
1289
  $("tbody").innerHTML = list.length ? list.map((r) => {
 
1296
  if (c === "Recommended Stream") {
1297
  return '<td><span class="tag ' + streamTag(String(v)) + '">' + esc(String(v)) + "</span></td>";
1298
  }
1299
+ if (c === "Assigned Class") {
1300
+ return '<td><span class="tag tag-ok" style="font-weight:700;">' + esc(String(v)) + "</span></td>";
1301
+ }
1302
  if (c === "Confidence") {
1303
  return '<td><span class="tag ' + confTag(String(v)) + '">' + esc(String(v)) + "</span></td>";
1304
  }
 
1310
  }
1311
  $("search").addEventListener("input", filter);
1312
  $("streamFilter").addEventListener("change", filter);
1313
+ if ($("classFilter")) $("classFilter").addEventListener("change", filter);
1314
  async function runCsv() {
1315
  if (!file) return;
1316
  load(true);