Spaces:
Running
Running
Upload 10 files
Browse files- src/main.js +0 -10
- 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 |
-
|
| 796 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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') {
|