Lashtw commited on
Commit
5d76bc4
·
verified ·
1 Parent(s): 3b32573

Upload 10 files

Browse files
Files changed (1) hide show
  1. 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');