syuanteng315 commited on
Commit
f07c716
·
verified ·
1 Parent(s): 1ae9427

Update index.html

Browse files
Files changed (1) hide show
  1. 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
- <button onclick="window.print()" class="bg-slate-700 hover:bg-slate-600 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="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() ? nameEl.value.trim() : (selectEl && selectEl.value ? selectEl.value : "Unknown Student");
 
 
 
 
 
 
 
 
 
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
- window.savedSessions = [sessionRecord, ...window.savedSessions.filter(s => s.id !== sessionRecord.id)];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1304
  localStorage.setItem('smk_saved_sessions_v1', JSON.stringify(window.savedSessions));
1305
 
1306
  if (typeof window.saveSessionToCloud === 'function') {
1307
- window.saveSessionToCloud(sessionRecord);
1308
  }
1309
-
 
 
 
1310
  renderHistoryTable();
1311
  renderTimetableGrid();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1312
 
1313
- showToast("Session Saved", `Attendance for ${className} recorded successfully.`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>