Spaces:
Running
Running
Update index.html
Browse files- index.html +222 -19
index.html
CHANGED
|
@@ -210,8 +210,11 @@
|
|
| 210 |
<button onclick="copySummaryToClipboard()" class="bg-slate-700 hover:bg-slate-600 text-white px-3 py-1.5 rounded-lg text-xs font-semibold flex items-center gap-1 transition shadow">
|
| 211 |
<i data-lucide="copy" class="w-3.5 h-3.5"></i> Copy Summary
|
| 212 |
</button>
|
| 213 |
-
<button onclick="saveAttendanceSession()" class="bg-emerald-600 hover:bg-emerald-500 text-white px-4 py-1.5 rounded-lg text-xs font-bold flex items-center gap-1 transition shadow border border-emerald-500">
|
| 214 |
-
<i data-lucide="save" class="w-4 h-4"></i> Save / Sync
|
|
|
|
|
|
|
|
|
|
| 215 |
</button>
|
| 216 |
</div>
|
| 217 |
</div>
|
|
@@ -342,7 +345,7 @@
|
|
| 342 |
<h2 class="text-lg font-bold text-slate-800 flex items-center gap-2">
|
| 343 |
<i data-lucide="sheet" class="w-5 h-5 text-emerald-600"></i> Saved Attendance Sessions & Logs
|
| 344 |
</h2>
|
| 345 |
-
<p class="text-[13px] text-slate-500 mt-0.5">Review, filter, restore, or delete attendance records</p>
|
| 346 |
</div>
|
| 347 |
<div class="flex flex-wrap items-center gap-2">
|
| 348 |
<button onclick="document.getElementById('log-csv-file-input').click()" class="bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-semibold px-3.5 py-2 rounded-xl flex items-center gap-1.5 transition shadow-sm" title="Restore old logs from exported CSV">
|
|
@@ -351,9 +354,7 @@
|
|
| 351 |
<button onclick="exportAllCSV()" class="bg-emerald-600 hover:bg-emerald-700 text-white text-xs font-semibold px-3.5 py-2 rounded-xl flex items-center gap-1.5 transition shadow-sm">
|
| 352 |
<i data-lucide="download" class="w-4 h-4"></i> Export CSV
|
| 353 |
</button>
|
| 354 |
-
|
| 355 |
-
<i data-lucide="printer" class="w-4 h-4"></i> Print
|
| 356 |
-
</button>
|
| 357 |
<button id="btn-delete-selected" onclick="deleteSelectedSessions()" class="bg-rose-600 hover:bg-rose-700 text-white text-xs font-bold px-3.5 py-2 rounded-xl flex items-center gap-1.5 transition shadow-sm hidden">
|
| 358 |
<i data-lucide="trash-2" class="w-4 h-4"></i> Delete Selected (<span id="selected-count">0</span>)
|
| 359 |
</button>
|
|
@@ -868,6 +869,8 @@
|
|
| 868 |
window.activeModalClass = "";
|
| 869 |
window.expandedLogSessions = new Set();
|
| 870 |
window.selectedLogIds = new Set();
|
|
|
|
|
|
|
| 871 |
|
| 872 |
const SUBJECT_ABBR = {
|
| 873 |
"Bahasa Melayu": "BM",
|
|
@@ -1256,36 +1259,75 @@
|
|
| 1256 |
const teacher = document.getElementById('select-teacher').value;
|
| 1257 |
const startTime = document.getElementById('select-start-time').value;
|
| 1258 |
const endTime = document.getElementById('select-end-time').value;
|
| 1259 |
-
|
| 1260 |
const total = parseInt(document.getElementById('input-total-enrolled').value) || 0;
|
| 1261 |
const present = parseInt(document.getElementById('input-present-count').value) || 0;
|
| 1262 |
const absent = total - present;
|
| 1263 |
const rate = total > 0 ? ((present / total) * 100).toFixed(1) + '%' : '0%';
|
| 1264 |
|
| 1265 |
-
if(!date || !className) {
|
| 1266 |
showToast("Missing Fields", "Please ensure Date and Class are selected.");
|
| 1267 |
return;
|
| 1268 |
}
|
| 1269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1270 |
let absenteesList = [];
|
| 1271 |
for (let i = 0; i < absent; i++) {
|
| 1272 |
const selectEl = document.getElementById(`absentee-select-${i}`);
|
| 1273 |
const nameEl = document.getElementById(`absentee-name-${i}`);
|
| 1274 |
const reasonEl = document.getElementById(`absentee-reason-${i}`);
|
| 1275 |
const remarkEl = document.getElementById(`absentee-remark-${i}`);
|
| 1276 |
-
|
| 1277 |
-
let sName = nameEl && nameEl.value.trim()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1278 |
let sReason = reasonEl ? reasonEl.value : "No Reason";
|
| 1279 |
-
|
| 1280 |
if (remarkEl && remarkEl.value.trim()) {
|
| 1281 |
sReason += ` (${remarkEl.value.trim()})`;
|
| 1282 |
}
|
| 1283 |
-
|
| 1284 |
absenteesList.push({ name: sName, reason: sReason });
|
| 1285 |
}
|
| 1286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1287 |
const sessionRecord = {
|
| 1288 |
-
id: Date.now(),
|
| 1289 |
date,
|
| 1290 |
dayKey,
|
| 1291 |
class: className,
|
|
@@ -1297,20 +1339,178 @@
|
|
| 1297 |
present,
|
| 1298 |
absent,
|
| 1299 |
rate,
|
| 1300 |
-
absentees: absenteesList
|
|
|
|
| 1301 |
};
|
| 1302 |
|
| 1303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1304 |
localStorage.setItem('smk_saved_sessions_v1', JSON.stringify(window.savedSessions));
|
| 1305 |
|
| 1306 |
if (typeof window.saveSessionToCloud === 'function') {
|
| 1307 |
-
|
| 1308 |
}
|
| 1309 |
-
|
|
|
|
|
|
|
|
|
|
| 1310 |
renderHistoryTable();
|
| 1311 |
renderTimetableGrid();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1312 |
|
| 1313 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1314 |
}
|
| 1315 |
|
| 1316 |
function toggleLogAbsentee(id) {
|
|
@@ -1424,7 +1624,10 @@
|
|
| 1424 |
<td class="py-3.5 px-4 text-center text-rose-600 font-bold">${s.absent}</td>
|
| 1425 |
<td class="py-3.5 px-4 min-w-[220px] max-w-[320px] leading-tight pt-3.5">${absDisplay}</td>
|
| 1426 |
<td class="py-3.5 px-4 text-center font-bold text-sky-600">${s.rate}</td>
|
| 1427 |
-
<td class="py-3.5 px-4 text-right">
|
|
|
|
|
|
|
|
|
|
| 1428 |
<button onclick="deleteSession(${s.id})" class="text-slate-400 hover:text-rose-600 hover:bg-rose-50 p-1.5 rounded-lg transition" title="Delete Log">
|
| 1429 |
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
| 1430 |
</button>
|
|
|
|
| 210 |
<button onclick="copySummaryToClipboard()" class="bg-slate-700 hover:bg-slate-600 text-white px-3 py-1.5 rounded-lg text-xs font-semibold flex items-center gap-1 transition shadow">
|
| 211 |
<i data-lucide="copy" class="w-3.5 h-3.5"></i> Copy Summary
|
| 212 |
</button>
|
| 213 |
+
<button id="btn-save-session" onclick="saveAttendanceSession()" class="bg-emerald-600 hover:bg-emerald-500 text-white px-4 py-1.5 rounded-lg text-xs font-bold flex items-center gap-1 transition shadow border border-emerald-500">
|
| 214 |
+
<i data-lucide="save" class="w-4 h-4"></i> <span id="save-session-label">Save / Sync</span>
|
| 215 |
+
</button>
|
| 216 |
+
<button id="btn-cancel-edit" onclick="cancelEditSession()" class="hidden bg-slate-600 hover:bg-slate-500 text-white px-3 py-1.5 rounded-lg text-xs font-bold items-center gap-1 transition shadow">
|
| 217 |
+
<i data-lucide="x" class="w-4 h-4"></i> Cancel Edit
|
| 218 |
</button>
|
| 219 |
</div>
|
| 220 |
</div>
|
|
|
|
| 345 |
<h2 class="text-lg font-bold text-slate-800 flex items-center gap-2">
|
| 346 |
<i data-lucide="sheet" class="w-5 h-5 text-emerald-600"></i> Saved Attendance Sessions & Logs
|
| 347 |
</h2>
|
| 348 |
+
<p class="text-[13px] text-slate-500 mt-0.5">Review, correct, filter, restore, or delete attendance records</p>
|
| 349 |
</div>
|
| 350 |
<div class="flex flex-wrap items-center gap-2">
|
| 351 |
<button onclick="document.getElementById('log-csv-file-input').click()" class="bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-semibold px-3.5 py-2 rounded-xl flex items-center gap-1.5 transition shadow-sm" title="Restore old logs from exported CSV">
|
|
|
|
| 354 |
<button onclick="exportAllCSV()" class="bg-emerald-600 hover:bg-emerald-700 text-white text-xs font-semibold px-3.5 py-2 rounded-xl flex items-center gap-1.5 transition shadow-sm">
|
| 355 |
<i data-lucide="download" class="w-4 h-4"></i> Export CSV
|
| 356 |
</button>
|
| 357 |
+
|
|
|
|
|
|
|
| 358 |
<button id="btn-delete-selected" onclick="deleteSelectedSessions()" class="bg-rose-600 hover:bg-rose-700 text-white text-xs font-bold px-3.5 py-2 rounded-xl flex items-center gap-1.5 transition shadow-sm hidden">
|
| 359 |
<i data-lucide="trash-2" class="w-4 h-4"></i> Delete Selected (<span id="selected-count">0</span>)
|
| 360 |
</button>
|
|
|
|
| 869 |
window.activeModalClass = "";
|
| 870 |
window.expandedLogSessions = new Set();
|
| 871 |
window.selectedLogIds = new Set();
|
| 872 |
+
// ID of the saved attendance session currently being edited.
|
| 873 |
+
window.editingSessionId = null;
|
| 874 |
|
| 875 |
const SUBJECT_ABBR = {
|
| 876 |
"Bahasa Melayu": "BM",
|
|
|
|
| 1259 |
const teacher = document.getElementById('select-teacher').value;
|
| 1260 |
const startTime = document.getElementById('select-start-time').value;
|
| 1261 |
const endTime = document.getElementById('select-end-time').value;
|
| 1262 |
+
|
| 1263 |
const total = parseInt(document.getElementById('input-total-enrolled').value) || 0;
|
| 1264 |
const present = parseInt(document.getElementById('input-present-count').value) || 0;
|
| 1265 |
const absent = total - present;
|
| 1266 |
const rate = total > 0 ? ((present / total) * 100).toFixed(1) + '%' : '0%';
|
| 1267 |
|
| 1268 |
+
if (!date || !className) {
|
| 1269 |
showToast("Missing Fields", "Please ensure Date and Class are selected.");
|
| 1270 |
return;
|
| 1271 |
}
|
| 1272 |
|
| 1273 |
+
if (present < 0 || present > total || total < 0) {
|
| 1274 |
+
showToast("Invalid Attendance", "Present students must be between 0 and Total Enrolled.");
|
| 1275 |
+
return;
|
| 1276 |
+
}
|
| 1277 |
+
|
| 1278 |
let absenteesList = [];
|
| 1279 |
for (let i = 0; i < absent; i++) {
|
| 1280 |
const selectEl = document.getElementById(`absentee-select-${i}`);
|
| 1281 |
const nameEl = document.getElementById(`absentee-name-${i}`);
|
| 1282 |
const reasonEl = document.getElementById(`absentee-reason-${i}`);
|
| 1283 |
const remarkEl = document.getElementById(`absentee-remark-${i}`);
|
| 1284 |
+
|
| 1285 |
+
let sName = nameEl && nameEl.value.trim()
|
| 1286 |
+
? nameEl.value.trim()
|
| 1287 |
+
: (selectEl && selectEl.value ? selectEl.value : "");
|
| 1288 |
+
|
| 1289 |
+
// Don't silently save an unnamed absentee.
|
| 1290 |
+
if (!sName) {
|
| 1291 |
+
showToast("Missing Absentee Name", `Please select or enter a name for absentee #${i + 1}.`);
|
| 1292 |
+
return;
|
| 1293 |
+
}
|
| 1294 |
+
|
| 1295 |
let sReason = reasonEl ? reasonEl.value : "No Reason";
|
| 1296 |
+
|
| 1297 |
if (remarkEl && remarkEl.value.trim()) {
|
| 1298 |
sReason += ` (${remarkEl.value.trim()})`;
|
| 1299 |
}
|
| 1300 |
+
|
| 1301 |
absenteesList.push({ name: sName, reason: sReason });
|
| 1302 |
}
|
| 1303 |
|
| 1304 |
+
// Prevent accidental duplicate sessions for the same class/date/slot.
|
| 1305 |
+
// During editing, ignore the record being edited.
|
| 1306 |
+
const duplicate = window.savedSessions.find(s =>
|
| 1307 |
+
s.id !== window.editingSessionId &&
|
| 1308 |
+
s.date === date &&
|
| 1309 |
+
s.class === className &&
|
| 1310 |
+
s.startTime === startTime
|
| 1311 |
+
);
|
| 1312 |
+
|
| 1313 |
+
if (duplicate) {
|
| 1314 |
+
const ok = confirm(
|
| 1315 |
+
`Attendance already exists for ${className} on ${date}, ${startTime}-${duplicate.endTime}.\n\n` +
|
| 1316 |
+
`Do you want to replace that existing record instead?`
|
| 1317 |
+
);
|
| 1318 |
+
if (!ok) return;
|
| 1319 |
+
|
| 1320 |
+
// Delete the duplicate from cloud before replacing it.
|
| 1321 |
+
if (typeof window.deleteSessionFromCloud === 'function') {
|
| 1322 |
+
window.deleteSessionFromCloud(duplicate);
|
| 1323 |
+
}
|
| 1324 |
+
window.savedSessions = window.savedSessions.filter(s => s.id !== duplicate.id);
|
| 1325 |
+
window.selectedLogIds.delete(duplicate.id);
|
| 1326 |
+
}
|
| 1327 |
+
|
| 1328 |
+
const existingId = window.editingSessionId;
|
| 1329 |
const sessionRecord = {
|
| 1330 |
+
id: existingId || Date.now(),
|
| 1331 |
date,
|
| 1332 |
dayKey,
|
| 1333 |
class: className,
|
|
|
|
| 1339 |
present,
|
| 1340 |
absent,
|
| 1341 |
rate,
|
| 1342 |
+
absentees: absenteesList,
|
| 1343 |
+
updatedAt: Date.now()
|
| 1344 |
};
|
| 1345 |
|
| 1346 |
+
// If this is an edit and the class/slot changed, remove the old cloud document.
|
| 1347 |
+
const oldSession = existingId
|
| 1348 |
+
? window.savedSessions.find(s => s.id === existingId)
|
| 1349 |
+
: null;
|
| 1350 |
+
|
| 1351 |
+
if (oldSession && typeof window.deleteSessionFromCloud === 'function') {
|
| 1352 |
+
const oldDocKeyChanged =
|
| 1353 |
+
oldSession.class !== sessionRecord.class ||
|
| 1354 |
+
oldSession.startTime !== sessionRecord.startTime;
|
| 1355 |
+
|
| 1356 |
+
if (oldDocKeyChanged) {
|
| 1357 |
+
window.deleteSessionFromCloud(oldSession);
|
| 1358 |
+
}
|
| 1359 |
+
}
|
| 1360 |
+
|
| 1361 |
+
window.savedSessions = [
|
| 1362 |
+
sessionRecord,
|
| 1363 |
+
...window.savedSessions.filter(s => s.id !== sessionRecord.id)
|
| 1364 |
+
];
|
| 1365 |
+
|
| 1366 |
localStorage.setItem('smk_saved_sessions_v1', JSON.stringify(window.savedSessions));
|
| 1367 |
|
| 1368 |
if (typeof window.saveSessionToCloud === 'function') {
|
| 1369 |
+
awaitCloudSaveSession(sessionRecord);
|
| 1370 |
}
|
| 1371 |
+
|
| 1372 |
+
window.editingSessionId = null;
|
| 1373 |
+
updateEditModeUI(false);
|
| 1374 |
+
|
| 1375 |
renderHistoryTable();
|
| 1376 |
renderTimetableGrid();
|
| 1377 |
+
if (typeof renderAnalyticsDashboard === 'function') renderAnalyticsDashboard();
|
| 1378 |
+
|
| 1379 |
+
showToast(
|
| 1380 |
+
oldSession ? "Record Updated" : "Session Saved",
|
| 1381 |
+
oldSession
|
| 1382 |
+
? `The attendance record for ${className} has been corrected and synced.`
|
| 1383 |
+
: `Attendance for ${className} recorded successfully.`
|
| 1384 |
+
);
|
| 1385 |
+
}
|
| 1386 |
+
|
| 1387 |
+
// Cloud save wrapper so the UI does not wait for Firestore before continuing.
|
| 1388 |
+
function awaitCloudSaveSession(sessionRecord) {
|
| 1389 |
+
try {
|
| 1390 |
+
const result = window.saveSessionToCloud(sessionRecord);
|
| 1391 |
+
if (result && typeof result.catch === 'function') {
|
| 1392 |
+
result.catch(err => console.error("Cloud save failed:", err));
|
| 1393 |
+
}
|
| 1394 |
+
} catch (err) {
|
| 1395 |
+
console.error("Cloud save failed:", err);
|
| 1396 |
+
}
|
| 1397 |
+
}
|
| 1398 |
+
|
| 1399 |
+
function updateEditModeUI(isEditing) {
|
| 1400 |
+
const label = document.getElementById('save-session-label');
|
| 1401 |
+
const saveBtn = document.getElementById('btn-save-session');
|
| 1402 |
+
const cancelBtn = document.getElementById('btn-cancel-edit');
|
| 1403 |
+
|
| 1404 |
+
if (label) label.innerText = isEditing ? "Save Changes" : "Save / Sync";
|
| 1405 |
+
|
| 1406 |
+
if (saveBtn) {
|
| 1407 |
+
saveBtn.classList.toggle('bg-amber-600', isEditing);
|
| 1408 |
+
saveBtn.classList.toggle('hover:bg-amber-500', isEditing);
|
| 1409 |
+
saveBtn.classList.toggle('border-amber-500', isEditing);
|
| 1410 |
+
saveBtn.classList.toggle('bg-emerald-600', !isEditing);
|
| 1411 |
+
saveBtn.classList.toggle('hover:bg-emerald-500', !isEditing);
|
| 1412 |
+
saveBtn.classList.toggle('border-emerald-500', !isEditing);
|
| 1413 |
+
}
|
| 1414 |
+
|
| 1415 |
+
if (cancelBtn) {
|
| 1416 |
+
cancelBtn.classList.toggle('hidden', !isEditing);
|
| 1417 |
+
cancelBtn.classList.toggle('flex', isEditing);
|
| 1418 |
+
}
|
| 1419 |
+
}
|
| 1420 |
|
| 1421 |
+
function editSession(id) {
|
| 1422 |
+
const session = window.savedSessions.find(s => s.id === id);
|
| 1423 |
+
if (!session) {
|
| 1424 |
+
showToast("Record Not Found", "This attendance record is no longer available.");
|
| 1425 |
+
return;
|
| 1426 |
+
}
|
| 1427 |
+
|
| 1428 |
+
window.editingSessionId = session.id;
|
| 1429 |
+
|
| 1430 |
+
// Switch to the attendance tracker first.
|
| 1431 |
+
switchTab('tracker');
|
| 1432 |
+
|
| 1433 |
+
// Set the saved record back into the input controls.
|
| 1434 |
+
const classSelect = document.getElementById('select-class');
|
| 1435 |
+
const subjectSelect = document.getElementById('select-subject');
|
| 1436 |
+
const teacherSelect = document.getElementById('select-teacher');
|
| 1437 |
+
const startSelect = document.getElementById('select-start-time');
|
| 1438 |
+
const endSelect = document.getElementById('select-end-time');
|
| 1439 |
+
const dateInput = document.getElementById('input-date');
|
| 1440 |
+
|
| 1441 |
+
if (classSelect) classSelect.value = session.class;
|
| 1442 |
+
if (subjectSelect) subjectSelect.value = session.subject;
|
| 1443 |
+
if (teacherSelect) teacherSelect.value = session.teacher;
|
| 1444 |
+
if (startSelect) startSelect.value = session.startTime;
|
| 1445 |
+
if (endSelect) endSelect.value = session.endTime;
|
| 1446 |
+
if (dateInput) dateInput.value = session.date;
|
| 1447 |
+
|
| 1448 |
+
// Refresh roster/banner without wiping the values we are about to restore.
|
| 1449 |
+
window.currentRosterStudents =
|
| 1450 |
+
window.rosters[session.class] ||
|
| 1451 |
+
Array.from({length: Math.max(session.total, 30)}, (_, i) => `Student ${i + 1}`);
|
| 1452 |
+
|
| 1453 |
+
document.getElementById('input-total-enrolled').value = session.total;
|
| 1454 |
+
document.getElementById('input-present-count').value = session.present;
|
| 1455 |
+
|
| 1456 |
+
onDateChange();
|
| 1457 |
+
updateBannerInfo();
|
| 1458 |
+
updateAttendanceCalc();
|
| 1459 |
+
|
| 1460 |
+
// Restore every absentee's name, reason and remarks.
|
| 1461 |
+
(session.absentees || []).forEach((ab, i) => {
|
| 1462 |
+
const selectEl = document.getElementById(`absentee-select-${i}`);
|
| 1463 |
+
const nameEl = document.getElementById(`absentee-name-${i}`);
|
| 1464 |
+
const reasonEl = document.getElementById(`absentee-reason-${i}`);
|
| 1465 |
+
const remarkEl = document.getElementById(`absentee-remark-${i}`);
|
| 1466 |
+
|
| 1467 |
+
if (!selectEl || !nameEl || !reasonEl) return;
|
| 1468 |
+
|
| 1469 |
+
const rawReason = ab.reason || "No Reason";
|
| 1470 |
+
const match = rawReason.match(/^(.+?)\s*\((.*)\)$/);
|
| 1471 |
+
const baseReason = match ? match[1].trim() : rawReason;
|
| 1472 |
+
const remark = match ? match[2].trim() : "";
|
| 1473 |
+
|
| 1474 |
+
if ([...selectEl.options].some(o => o.value === ab.name)) {
|
| 1475 |
+
selectEl.value = ab.name;
|
| 1476 |
+
nameEl.value = "";
|
| 1477 |
+
} else {
|
| 1478 |
+
selectEl.value = "";
|
| 1479 |
+
nameEl.value = ab.name;
|
| 1480 |
+
}
|
| 1481 |
+
|
| 1482 |
+
if ([...reasonEl.options].some(o => o.value === baseReason)) {
|
| 1483 |
+
reasonEl.value = baseReason;
|
| 1484 |
+
} else {
|
| 1485 |
+
reasonEl.value = "Lain-lain";
|
| 1486 |
+
}
|
| 1487 |
+
|
| 1488 |
+
if (remarkEl) remarkEl.value = remark;
|
| 1489 |
+
});
|
| 1490 |
+
|
| 1491 |
+
updateEditModeUI(true);
|
| 1492 |
+
|
| 1493 |
+
// Make the edit state obvious without using a disruptive modal.
|
| 1494 |
+
const banner = document.getElementById('subject-info-card');
|
| 1495 |
+
if (banner) {
|
| 1496 |
+
banner.classList.add('ring-2', 'ring-amber-400', 'ring-offset-2');
|
| 1497 |
+
}
|
| 1498 |
+
|
| 1499 |
+
showToast("Edit Mode", "Correct the attendance below, then click Save Changes.");
|
| 1500 |
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 1501 |
+
}
|
| 1502 |
+
|
| 1503 |
+
function cancelEditSession() {
|
| 1504 |
+
if (window.editingSessionId !== null) {
|
| 1505 |
+
window.editingSessionId = null;
|
| 1506 |
+
updateEditModeUI(false);
|
| 1507 |
+
|
| 1508 |
+
const banner = document.getElementById('subject-info-card');
|
| 1509 |
+
if (banner) banner.classList.remove('ring-2', 'ring-amber-400', 'ring-offset-2');
|
| 1510 |
+
|
| 1511 |
+
onClassChange();
|
| 1512 |
+
showToast("Edit Cancelled", "The saved record was not changed.");
|
| 1513 |
+
}
|
| 1514 |
}
|
| 1515 |
|
| 1516 |
function toggleLogAbsentee(id) {
|
|
|
|
| 1624 |
<td class="py-3.5 px-4 text-center text-rose-600 font-bold">${s.absent}</td>
|
| 1625 |
<td class="py-3.5 px-4 min-w-[220px] max-w-[320px] leading-tight pt-3.5">${absDisplay}</td>
|
| 1626 |
<td class="py-3.5 px-4 text-center font-bold text-sky-600">${s.rate}</td>
|
| 1627 |
+
<td class="py-3.5 px-4 text-right whitespace-nowrap">
|
| 1628 |
+
<button onclick="editSession(${s.id})" class="text-emerald-600 hover:text-emerald-800 hover:bg-emerald-50 p-1.5 rounded-lg transition mr-1" title="Edit / Correct This Record">
|
| 1629 |
+
<i data-lucide="pencil" class="w-4 h-4"></i>
|
| 1630 |
+
</button>
|
| 1631 |
<button onclick="deleteSession(${s.id})" class="text-slate-400 hover:text-rose-600 hover:bg-rose-50 p-1.5 rounded-lg transition" title="Delete Log">
|
| 1632 |
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
| 1633 |
</button>
|