import { submitPrompt, getChallenges, startChallenge, getUserProgress, getPeerPrompts, resetProgress, toggleLike, subscribeToNotifications, markNotificationRead } from "../services/classroom.js"; // Cache challenges locally let cachedChallenges = []; export async function renderStudentView() { const nickname = localStorage.getItem('vibecoding_nickname') || 'Guest'; const roomCode = localStorage.getItem('vibecoding_room_code') || 'Unknown'; const userId = localStorage.getItem('vibecoding_user_id'); // Fetch challenges if empty if (cachedChallenges.length === 0) { try { cachedChallenges = await getChallenges(); } catch (e) { console.error("Failed to fetch challenges", e); throw new Error("無法讀取題目列表 (Error: " + e.message + ")"); } } // Fetch User Progress let userProgress = {}; if (userId) { try { userProgress = await getUserProgress(userId); } catch (e) { console.error("Failed to fetch progress", e); } } const levelGroups = { beginner: cachedChallenges.filter(c => c.level === 'beginner'), intermediate: cachedChallenges.filter(c => c.level === 'intermediate'), advanced: cachedChallenges.filter(c => c.level === 'advanced') }; const levelNames = { beginner: "初級 (Beginner)", intermediate: "中級 (Intermediate)", advanced: "高級 (Advanced)" }; function renderTaskCard(c) { const p = userProgress[c.id] || {}; const isCompleted = p.status === 'completed'; const isStarted = p.status === 'started'; // 1. Completed State: Collapsed with Trophy if (isCompleted) { return `
🏆

${c.title}

已通關
`; } // 2. Started or Not Started // If Started: Show "Prompt Input" area. // If Not Started: Show "Start Task" button only. return `

${c.title}

${c.description}

${!isStarted ? `
` : `
`}
`; } // Accordion Layout return `
${nickname}
教室: ${roomCode}

VIBECODING

${['beginner', 'intermediate', 'advanced'].map(level => { const tasks = levelGroups[level] || []; const isOpen = level === 'beginner' ? 'open' : ''; // Count completed const completedCount = tasks.filter(t => userProgress[t.id]?.status === 'completed').length; return `

${levelNames[level]}

${completedCount === tasks.length && tasks.length > 0 ? 'ALL CLEAR' : ''}
${completedCount} / ${tasks.length}
${tasks.length > 0 ? tasks.map(c => renderTaskCard(c)).join('') : '
本區段尚無題目
'}
`; }).join('')}
`; } export function setupStudentEvents() { // Start Level Logic window.startLevel = async (challengeId, link) => { // Open link window.open(link, '_blank'); // Call service to update status const roomCode = localStorage.getItem('vibecoding_room_code'); const userId = localStorage.getItem('vibecoding_user_id'); if (roomCode && userId) { try { await startChallenge(userId, roomCode, challengeId); // Reload view to show Input State // Ideally we should use state management, but checking URL hash or re-rendering works const app = document.querySelector('#app'); app.innerHTML = await renderStudentView(); // Re-attach events (recursion safety check needed? No, navigateTo does this usually, but here we manually re-render) // Or better: trigger a custom event or call navigateTo functionality? // Simple re-render is fine for now. } catch (e) { console.error("Start challenge failed", e); } } }; window.submitLevel = async (challengeId) => { const input = document.getElementById(`input-${challengeId}`); const errorMsg = document.getElementById(`error-${challengeId}`); const prompt = input.value; const roomCode = localStorage.getItem('vibecoding_room_code'); const userId = localStorage.getItem('vibecoding_user_id'); if (!participantDataCheck(roomCode, userId)) return; if (prompt.trim().length < 5) { errorMsg.classList.remove('hidden'); input.classList.add('border-red-500'); return; } errorMsg.classList.add('hidden'); input.classList.remove('border-red-500'); // Show loading state on button const container = input.parentElement; const btn = container.querySelector('button'); const originalText = btn.textContent; btn.textContent = "提交中..."; btn.disabled = true; try { await submitPrompt(userId, roomCode, challengeId, prompt); btn.textContent = "✓ 已通關"; btn.classList.add("bg-green-600"); // Re-render to show Trophy state after short delay setTimeout(async () => { const app = document.querySelector('#app'); app.innerHTML = await renderStudentView(); }, 1000); } catch (error) { console.error(error); btn.textContent = originalText; btn.disabled = false; alert("提交失敗: " + error.message); } }; window.resetLevel = async (challengeId) => { if (!confirm("確定要重置這一題的進度嗎?(提示詞將會保留,但狀態會變回進行中)")) return; const roomCode = localStorage.getItem('vibecoding_room_code'); const userId = localStorage.getItem('vibecoding_user_id'); try { // Import and call resetProgress (Need to make sure it is imported or available globally? // Ideally import it. But setupStudentEvents is in module scope so imports are available. // Wait, import 'resetProgress' is not in the top import list yet. I need to add it.) // Let's assume I will update the import in the next step or use the global trick if needed. // But I should edit the import first. // For now, let's assume it is there. I will add it to the import list in a parallel or subsequent edit. // Checking imports above... I see 'getUserProgress' but not 'resetProgress'. I must update imports. // I'll do it in a separate edit step to be safe. // For now, just the logic: const { resetProgress } = await import("../services/classroom.js"); // Dynamic import to avoid changing top file lines again? // Or just rely on previous 'replace' having updated the file? // Actually, I should update the top import. await resetProgress(userId, roomCode, challengeId); const app = document.querySelector('#app'); app.innerHTML = await renderStudentView(); } catch (e) { console.error(e); alert("重置失敗"); } }; } function participantDataCheck(roomCode, userId) { if (!roomCode || !userId) { alert("連線資訊遺失,請重新登入"); window.location.reload(); return false; } return true; } // Peer Learning Modal Logic function renderPeerModal() { // We need to re-fetch challenges for the dropdown? // They are cached in 'cachedChallenges' module variable let optionsHtml = ''; if (cachedChallenges.length > 0) { optionsHtml += cachedChallenges.map(c => `` ).join(''); } return ` `; } window.openPeerModal = () => { const existing = document.getElementById('peer-modal'); if (existing) existing.remove(); const div = document.createElement('div'); div.innerHTML = renderPeerModal(); document.body.appendChild(div.firstElementChild); document.getElementById('peer-modal').classList.remove('hidden'); }; window.closePeerModal = () => { document.getElementById('peer-modal').classList.add('hidden'); }; window.loadPeerPrompts = async (challengeId) => { const container = document.getElementById('peer-prompts-container'); container.innerHTML = '
載入中...
'; const roomCode = localStorage.getItem('vibecoding_room_code'); const prompts = await getPeerPrompts(roomCode, challengeId); if (prompts.length === 0) { container.innerHTML = '
尚無同學提交此關卡或您無權限查看(需相同教室代碼)
'; return; } container.innerHTML = prompts.map(p => { const currentUserId = localStorage.getItem('vibecoding_user_id'); const isLiked = p.likedBy && p.likedBy.includes(currentUserId); return `
${p.nickname[0]}
${p.nickname} ${new Date(p.timestamp.seconds * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}

${p.prompt}

`}).join(''); // Attach challenge title for notification context window.currentPeerChallengeTitle = document.querySelector(`#peer-challenge-select option[value="${challengeId}"]`).text; }; // Like Handler window.handleLike = async (progressId, targetUserId) => { const userId = localStorage.getItem('vibecoding_user_id'); const nickname = localStorage.getItem('vibecoding_nickname'); const challengeTitle = window.currentPeerChallengeTitle || '挑戰'; // Optimistic UI update could go here, but for simplicity let's re-load or just fire and forget (the view won't update until reload currently) // To make it responsive, we should probably manually toggle the class on the button immediately. // For now, let's just call service and reload the list to see updated count. // Better UX: Find button and toggle 'processing' state? // Let's just reload the list for data consistency. const { toggleLike } = await import("../services/classroom.js"); await toggleLike(progressId, userId, nickname, targetUserId, challengeTitle); // Reload to refresh count const select = document.getElementById('peer-challenge-select'); if (select && select.value) { loadPeerPrompts(select.value); } };