Lashtw commited on
Commit
6bd7f69
·
verified ·
1 Parent(s): 8f2336a

Upload 10 files

Browse files
Files changed (2) hide show
  1. src/main.js +0 -10
  2. src/views/InstructorView.js +78 -3
src/main.js CHANGED
@@ -3,8 +3,6 @@ import { renderInstructorView, setupInstructorEvents } from './views/InstructorV
3
  import { renderStudentView, setupStudentEvents } from './views/StudentView.js';
4
  import { renderAdminView, setupAdminEvents } from './views/AdminView.js';
5
 
6
- import { renderInstructorAdminView, setupInstructorAdminEvents } from './views/InstructorAdminView.js';
7
-
8
  const app = document.querySelector('#app');
9
 
10
  function navigateTo(view) {
@@ -22,10 +20,6 @@ function navigateTo(view) {
22
  setupInstructorEvents();
23
  });
24
  break;
25
- case 'instructors': // Manage Instructors
26
- app.innerHTML = renderInstructorAdminView();
27
- setupInstructorAdminEvents();
28
- break;
29
  case 'student':
30
  app.innerHTML = '載入中...';
31
  // Async render because Student view fetches challenges
@@ -62,10 +56,6 @@ function handleRoute() {
62
  navigateTo('instructor');
63
  return;
64
  }
65
- if (hash === 'instructors') {
66
- navigateTo('instructors');
67
- return;
68
- }
69
 
70
 
71
 
 
3
  import { renderStudentView, setupStudentEvents } from './views/StudentView.js';
4
  import { renderAdminView, setupAdminEvents } from './views/AdminView.js';
5
 
 
 
6
  const app = document.querySelector('#app');
7
 
8
  function navigateTo(view) {
 
20
  setupInstructorEvents();
21
  });
22
  break;
 
 
 
 
23
  case 'student':
24
  app.innerHTML = '載入中...';
25
  // Async render because Student view fetches challenges
 
56
  navigateTo('instructor');
57
  return;
58
  }
 
 
 
 
59
 
60
 
61
 
src/views/InstructorView.js CHANGED
@@ -791,9 +791,14 @@ export function setupInstructorEvents() {
791
  }
792
 
793
  if (navInstBtn) {
794
- navInstBtn.addEventListener('click', () => {
795
- if (confirm("即將離開儀表板前往講師管理頁面,確定嗎?")) {
796
- window.location.hash = 'instructors';
 
 
 
 
 
797
  }
798
  });
799
  }
@@ -1271,6 +1276,76 @@ export function setupInstructorEvents() {
1271
  };
1272
 
1273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1274
  // Snapshot Logic
1275
  snapshotBtn.addEventListener('click', async () => {
1276
  if (isSnapshotting || typeof htmlToImage === 'undefined') {
 
791
  }
792
 
793
  if (navInstBtn) {
794
+ navInstBtn.addEventListener('click', async () => {
795
+ const currentUser = auth.currentUser;
796
+ const instData = await checkInstructorPermission(currentUser);
797
+ if (instData?.permissions?.includes('manage_instructors')) {
798
+ document.getElementById('instructor-modal').classList.remove('hidden');
799
+ loadInstructorList();
800
+ } else {
801
+ alert("無此權限");
802
  }
803
  });
804
  }
 
1276
  };
1277
 
1278
 
1279
+
1280
+
1281
+ // --- Instructor Management Logic ---
1282
+ async function loadInstructorList() {
1283
+ const tbody = document.getElementById('instructor-list-body');
1284
+ tbody.innerHTML = '<tr><td colspan="4" class="p-4 text-center">載入中...</td></tr>';
1285
+ try {
1286
+ const list = await getInstructors();
1287
+ tbody.innerHTML = list.map(i => `
1288
+ <tr class="border-b border-gray-700 hover:bg-gray-700/50">
1289
+ <td class="p-3">${i.name || '-'}</td>
1290
+ <td class="p-3 font-mono text-sm text-gray-400">${i.email}</td>
1291
+ <td class="p-3">
1292
+ <div class="flex flex-wrap gap-1">
1293
+ ${(i.permissions || []).map(p => `<span class="text-xs bg-indigo-900 text-indigo-300 px-1 rounded">${p}</span>`).join('')}
1294
+ </div>
1295
+ </td>
1296
+ <td class="p-3">
1297
+ ${i.role !== 'admin' ?
1298
+ `<button onclick="window.removeInst('${i.email}')" class="text-red-400 hover:text-white px-2 py-1 bg-red-900/30 rounded">移除</button>` :
1299
+ '<span class="text-xs text-gray-600">Admin</span>'
1300
+ }
1301
+ </td>
1302
+ </tr>
1303
+ `).join('');
1304
+ } catch (e) {
1305
+ console.error(e);
1306
+ tbody.innerHTML = '<tr><td colspan="4" class="p-4 text-center text-red-500">載入失敗</td></tr>';
1307
+ }
1308
+ }
1309
+
1310
+ const btnAddInst = document.getElementById('btn-add-inst');
1311
+ if (btnAddInst) {
1312
+ btnAddInst.addEventListener('click', async () => {
1313
+ const email = document.getElementById('new-inst-email').value.trim();
1314
+ const name = document.getElementById('new-inst-name').value.trim();
1315
+ const perms = [];
1316
+ if (document.getElementById('perm-room').checked) perms.push('create_room');
1317
+ if (document.getElementById('perm-q').checked) perms.push('add_question');
1318
+ if (document.getElementById('perm-inst').checked) perms.push('manage_instructors');
1319
+
1320
+ if (!email || !name) {
1321
+ alert("請輸入 Email 和姓名");
1322
+ return;
1323
+ }
1324
+
1325
+ try {
1326
+ await addInstructor(email, name, perms);
1327
+ alert("新增成功");
1328
+ document.getElementById('new-inst-email').value = '';
1329
+ document.getElementById('new-inst-name').value = '';
1330
+ loadInstructorList();
1331
+ } catch (e) {
1332
+ console.error(e);
1333
+ alert("新增失敗: " + e.message);
1334
+ }
1335
+ });
1336
+ }
1337
+
1338
+ window.removeInst = async (email) => {
1339
+ if (confirm(`確定要移除 ${email} 嗎?`)) {
1340
+ try {
1341
+ await removeInstructor(email);
1342
+ loadInstructorList();
1343
+ } catch (e) {
1344
+ console.error(e);
1345
+ alert("移除失敗");
1346
+ }
1347
+ }
1348
+ };
1349
  // Snapshot Logic
1350
  snapshotBtn.addEventListener('click', async () => {
1351
  if (isSnapshotting || typeof htmlToImage === 'undefined') {