Spaces:
Running
Running
Upload 10 files
Browse files- src/views/InstructorView.js +125 -0
src/views/InstructorView.js
CHANGED
|
@@ -580,6 +580,131 @@ export function setupInstructorEvents() {
|
|
| 580 |
if (aiSettingsModal) aiSettingsModal.classList.add('hidden');
|
| 581 |
};
|
| 582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
// Login Logic
|
| 584 |
const authErrorMsg = document.getElementById('auth-error');
|
| 585 |
const authModal = document.getElementById('auth-modal');
|
|
|
|
| 580 |
if (aiSettingsModal) aiSettingsModal.classList.add('hidden');
|
| 581 |
};
|
| 582 |
|
| 583 |
+
// --- Additional Heatmap Interaction Functions ---
|
| 584 |
+
window.confirmKick = async (userId, nickname) => {
|
| 585 |
+
if (confirm(`確定要踢出 ${nickname} 嗎?`)) {
|
| 586 |
+
try {
|
| 587 |
+
const roomCode = localStorage.getItem('vibecoding_room_code');
|
| 588 |
+
if (!roomCode) return alert("未連接到教室");
|
| 589 |
+
await removeUser(userId, roomCode);
|
| 590 |
+
alert(`已踢出 ${nickname}`);
|
| 591 |
+
} catch (e) {
|
| 592 |
+
console.error(e);
|
| 593 |
+
alert("踢出失敗: " + e.message);
|
| 594 |
+
}
|
| 595 |
+
}
|
| 596 |
+
};
|
| 597 |
+
|
| 598 |
+
window.analyzeChallenge = (challengeId, challengeTitle) => {
|
| 599 |
+
if (!localStorage.getItem('vibecoding_gemini_key')) {
|
| 600 |
+
alert("請先設定 Gemini API Key");
|
| 601 |
+
return;
|
| 602 |
+
}
|
| 603 |
+
window.openPromptList('challenge', challengeId, challengeTitle);
|
| 604 |
+
setTimeout(() => {
|
| 605 |
+
const btn = document.getElementById('btn-ai-analyze');
|
| 606 |
+
if (btn && !btn.disabled) {
|
| 607 |
+
btn.click();
|
| 608 |
+
}
|
| 609 |
+
}, 300);
|
| 610 |
+
};
|
| 611 |
+
|
| 612 |
+
window.openPromptList = (type, id, title) => {
|
| 613 |
+
const modal = document.getElementById('prompt-list-modal');
|
| 614 |
+
const container = document.getElementById('prompt-list-container');
|
| 615 |
+
const titleEl = document.getElementById('prompt-list-title');
|
| 616 |
+
|
| 617 |
+
if (type === 'challenge') {
|
| 618 |
+
window.currentViewingChallengeId = id;
|
| 619 |
+
} else {
|
| 620 |
+
window.currentViewingChallengeId = null;
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
titleEl.textContent = type === 'student' ? `${title} 的所有提示詞` : `題目:${title} 的所有作品`;
|
| 624 |
+
|
| 625 |
+
container.innerHTML = '';
|
| 626 |
+
modal.classList.remove('hidden');
|
| 627 |
+
|
| 628 |
+
let prompts = [];
|
| 629 |
+
if (type === 'student') {
|
| 630 |
+
const student = currentStudents.find(s => s.id === id);
|
| 631 |
+
if (student && student.progress) {
|
| 632 |
+
prompts = Object.entries(student.progress)
|
| 633 |
+
.filter(([_, p]) => p.status === 'completed' && p.prompt)
|
| 634 |
+
.map(([challengeId, p]) => {
|
| 635 |
+
const challenge = cachedChallenges.find(c => c.id === challengeId);
|
| 636 |
+
return {
|
| 637 |
+
id: `${student.id}_${challengeId}`,
|
| 638 |
+
title: challenge ? challenge.title : '未知題目',
|
| 639 |
+
prompt: p.prompt,
|
| 640 |
+
author: student.nickname,
|
| 641 |
+
studentId: student.id,
|
| 642 |
+
challengeId: challengeId,
|
| 643 |
+
time: p.completedAt ? new Date(p.completedAt.seconds * 1000).toLocaleString() : ''
|
| 644 |
+
};
|
| 645 |
+
});
|
| 646 |
+
}
|
| 647 |
+
} else if (type === 'challenge') {
|
| 648 |
+
currentStudents.forEach(student => {
|
| 649 |
+
if (student.progress && student.progress[id]) {
|
| 650 |
+
const p = student.progress[id];
|
| 651 |
+
if (p.status === 'completed' && p.prompt) {
|
| 652 |
+
prompts.push({
|
| 653 |
+
id: `${student.id}_${id}`,
|
| 654 |
+
title: student.nickname,
|
| 655 |
+
prompt: p.prompt,
|
| 656 |
+
author: student.nickname,
|
| 657 |
+
studentId: student.id,
|
| 658 |
+
challengeId: id,
|
| 659 |
+
time: p.completedAt ? new Date(p.completedAt.seconds * 1000).toLocaleString() : ''
|
| 660 |
+
});
|
| 661 |
+
}
|
| 662 |
+
}
|
| 663 |
+
});
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
if (prompts.length === 0) {
|
| 667 |
+
container.innerHTML = '<div class="col-span-full text-center text-gray-500 py-10">無資料</div>';
|
| 668 |
+
return;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
prompts.forEach(p => {
|
| 672 |
+
const card = document.createElement('div');
|
| 673 |
+
card.className = 'bg-gray-800 rounded-xl p-3 border border-gray-700 hover:border-cyan-500 transition-colors flex flex-col h-48 group';
|
| 674 |
+
card.innerHTML = `
|
| 675 |
+
<div class="flex justify-between items-start mb-1.5">
|
| 676 |
+
<h3 class="font-bold text-white text-base truncate w-3/4" title="${p.title}">${p.title}</h3>
|
| 677 |
+
</div>
|
| 678 |
+
<div class="bg-black/30 rounded p-2 flex-1 overflow-y-auto font-mono text-green-300 whitespace-pre-wrap custom-scrollbar text-base leading-snug group-hover:text-green-200 transaction-colors mb-2" style="text-align: left;">${cleanText(p.prompt)}</div>
|
| 679 |
+
<div class="flex justify-between items-center text-[10px] text-gray-500 mt-auto">
|
| 680 |
+
<span>${p.time}</span>
|
| 681 |
+
<div class="flex space-x-2">
|
| 682 |
+
<button onclick="window.confirmReset('${p.studentId}', '${p.challengeId}', '${p.title}')" class="px-2 py-1 bg-red-900/30 hover:bg-red-800 text-red-400 rounded transition-colors text-xs border border-red-800/50" title="退回重做">
|
| 683 |
+
退回
|
| 684 |
+
</button>
|
| 685 |
+
</div>
|
| 686 |
+
</div>
|
| 687 |
+
`;
|
| 688 |
+
container.appendChild(card);
|
| 689 |
+
});
|
| 690 |
+
};
|
| 691 |
+
|
| 692 |
+
window.confirmReset = async (userId, challengeId, title) => {
|
| 693 |
+
if (confirm(`確定要退回 ${title} 嗎?此動作將清除學員目前的進度。`)) {
|
| 694 |
+
const roomCode = localStorage.getItem('vibecoding_room_code');
|
| 695 |
+
if (userId && challengeId) {
|
| 696 |
+
try {
|
| 697 |
+
await resetProgress(userId, roomCode, challengeId);
|
| 698 |
+
alert("已退回");
|
| 699 |
+
document.getElementById('prompt-list-modal').classList.add('hidden');
|
| 700 |
+
} catch (e) {
|
| 701 |
+
console.error(e);
|
| 702 |
+
alert("退回失敗");
|
| 703 |
+
}
|
| 704 |
+
}
|
| 705 |
+
}
|
| 706 |
+
};
|
| 707 |
+
|
| 708 |
// Login Logic
|
| 709 |
const authErrorMsg = document.getElementById('auth-error');
|
| 710 |
const authModal = document.getElementById('auth-modal');
|