Spaces:
Running
Running
Upload 8 files
Browse files- src/views/StudentView.js +53 -15
src/views/StudentView.js
CHANGED
|
@@ -91,7 +91,47 @@ function renderTaskCard(c, userProgress) {
|
|
| 91 |
`;
|
| 92 |
}
|
| 93 |
|
| 94 |
-
export async function renderStudentView() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
<div class="flex flex-col">
|
| 96 |
<div class="flex items-center space-x-2">
|
| 97 |
<div class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
|
|
@@ -127,25 +167,23 @@ export async function renderStudentView() { <header class="flex justify-b
|
|
| 127 |
</svg>
|
| 128 |
</div>
|
| 129 |
</summary>
|
| 130 |
-
<div class="p-4 pt-0 grid grid-cols-1 gap-4 mt-4">
|
| 131 |
<div class="p-4 pt-0 grid grid-cols-1 gap-4 mt-4">
|
| 132 |
${tasks.length > 0 ? tasks.map(c => renderTaskCard(c, userProgress)).join('') : '<div class="text-gray-500 text-sm italic">本區段尚無題目</div>'}
|
| 133 |
</div>
|
| 134 |
-
</div>
|
| 135 |
</details>
|
| 136 |
`;
|
| 137 |
}).join('')}
|
| 138 |
</div>
|
| 139 |
|
| 140 |
-
<!--Peer Learning FAB--
|
| 141 |
<button onclick="window.openPeerModal()" class="fixed bottom-6 right-6 bg-purple-600 hover:bg-purple-500 text-white rounded-full p-4 shadow-xl shadow-purple-600/40 transition-transform hover:scale-110 active:scale-90 z-40"
|
| 142 |
title="查看同學作業">
|
| 143 |
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 144 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0z" />
|
| 145 |
</svg>
|
| 146 |
</button>
|
| 147 |
-
</div
|
| 148 |
-
|
| 149 |
}
|
| 150 |
|
| 151 |
export function setupStudentEvents() {
|
|
@@ -175,8 +213,8 @@ export function setupStudentEvents() {
|
|
| 175 |
};
|
| 176 |
|
| 177 |
window.submitLevel = async (challengeId) => {
|
| 178 |
-
const input = document.getElementById(`input
|
| 179 |
-
const errorMsg = document.getElementById(`error
|
| 180 |
const prompt = input.value;
|
| 181 |
const roomCode = localStorage.getItem('vibecoding_room_code');
|
| 182 |
const userId = localStorage.getItem('vibecoding_user_id');
|
|
@@ -222,7 +260,7 @@ export function setupStudentEvents() {
|
|
| 222 |
const newCardHTML = renderTaskCard(challenge, newProgress);
|
| 223 |
|
| 224 |
// 3. Replace in DOM
|
| 225 |
-
const oldCard = document.getElementById(`card
|
| 226 |
if (oldCard) {
|
| 227 |
oldCard.outerHTML = newCardHTML;
|
| 228 |
} else {
|
|
@@ -289,12 +327,12 @@ function renderPeerModal() {
|
|
| 289 |
let optionsHtml = '<option value="" disabled selected>選擇題目...</option>';
|
| 290 |
if (cachedChallenges.length > 0) {
|
| 291 |
optionsHtml += cachedChallenges.map(c =>
|
| 292 |
-
`<
|
| 293 |
).join('');
|
| 294 |
}
|
| 295 |
|
| 296 |
return `
|
| 297 |
-
<
|
| 298 |
<div class="bg-gray-800 rounded-2xl w-full max-w-md h-[80vh] flex flex-col border border-gray-700 shadow-2xl">
|
| 299 |
<div class="p-6 border-b border-gray-700 flex justify-between items-center">
|
| 300 |
<h3 class="text-xl font-bold text-white">同學的成功提示詞</h3>
|
|
@@ -311,7 +349,7 @@ function renderPeerModal() {
|
|
| 311 |
<div class="text-center text-gray-500 mt-10">請選擇一個題目來查看</div>
|
| 312 |
</div>
|
| 313 |
</div>
|
| 314 |
-
|
| 315 |
`;
|
| 316 |
}
|
| 317 |
|
|
@@ -347,7 +385,7 @@ window.loadPeerPrompts = async (challengeId) => {
|
|
| 347 |
const isLiked = p.likedBy && p.likedBy.includes(currentUserId);
|
| 348 |
|
| 349 |
return `
|
| 350 |
-
<
|
| 351 |
<div class="flex items-center justify-between mb-2">
|
| 352 |
<div class="flex items-center space-x-2">
|
| 353 |
<div class="w-6 h-6 rounded-full bg-cyan-600 flex items-center justify-center text-xs font-bold text-white">
|
|
@@ -366,11 +404,11 @@ window.loadPeerPrompts = async (challengeId) => {
|
|
| 366 |
</button>
|
| 367 |
</div>
|
| 368 |
<p class="text-gray-300 font-mono text-sm bg-black/20 p-3 rounded-lg border border-gray-700/50 whitespace-pre-wrap">${p.prompt}</p>
|
| 369 |
-
</div
|
| 370 |
`}).join('');
|
| 371 |
|
| 372 |
// Attach challenge title for notification context
|
| 373 |
-
window.currentPeerChallengeTitle = document.querySelector(`#peer
|
| 374 |
};
|
| 375 |
|
| 376 |
// Like Handler
|
|
|
|
| 91 |
`;
|
| 92 |
}
|
| 93 |
|
| 94 |
+
export async function renderStudentView() {
|
| 95 |
+
const nickname = localStorage.getItem('vibecoding_nickname') || 'Guest';
|
| 96 |
+
const roomCode = localStorage.getItem('vibecoding_room_code') || 'Unknown';
|
| 97 |
+
const userId = localStorage.getItem('vibecoding_user_id');
|
| 98 |
+
|
| 99 |
+
// Fetch challenges if empty
|
| 100 |
+
if (cachedChallenges.length === 0) {
|
| 101 |
+
try {
|
| 102 |
+
cachedChallenges = await getChallenges();
|
| 103 |
+
} catch (e) {
|
| 104 |
+
console.error("Failed to fetch challenges", e);
|
| 105 |
+
throw new Error("無法讀取題目列表 (Error: " + e.message + ")");
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
// Fetch User Progress
|
| 110 |
+
let userProgress = {};
|
| 111 |
+
if (userId) {
|
| 112 |
+
try {
|
| 113 |
+
userProgress = await getUserProgress(userId);
|
| 114 |
+
} catch (e) {
|
| 115 |
+
console.error("Failed to fetch progress", e);
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
const levelGroups = {
|
| 120 |
+
beginner: cachedChallenges.filter(c => c.level === 'beginner'),
|
| 121 |
+
intermediate: cachedChallenges.filter(c => c.level === 'intermediate'),
|
| 122 |
+
advanced: cachedChallenges.filter(c => c.level === 'advanced')
|
| 123 |
+
};
|
| 124 |
+
|
| 125 |
+
const levelNames = {
|
| 126 |
+
beginner: "初級 (Beginner)",
|
| 127 |
+
intermediate: "中級 (Intermediate)",
|
| 128 |
+
advanced: "高級 (Advanced)"
|
| 129 |
+
};
|
| 130 |
+
|
| 131 |
+
// Accordion Layout
|
| 132 |
+
return `
|
| 133 |
+
<div class="min-h-screen p-4 pb-32 max-w-md mx-auto sm:max-w-4xl">
|
| 134 |
+
<header class="flex justify-between items-center mb-6 sticky top-0 bg-slate-900/95 backdrop-blur z-20 py-4 px-2 -mx-2 border-b border-gray-800">
|
| 135 |
<div class="flex flex-col">
|
| 136 |
<div class="flex items-center space-x-2">
|
| 137 |
<div class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
|
|
|
|
| 167 |
</svg>
|
| 168 |
</div>
|
| 169 |
</summary>
|
|
|
|
| 170 |
<div class="p-4 pt-0 grid grid-cols-1 gap-4 mt-4">
|
| 171 |
${tasks.length > 0 ? tasks.map(c => renderTaskCard(c, userProgress)).join('') : '<div class="text-gray-500 text-sm italic">本區段尚無題目</div>'}
|
| 172 |
</div>
|
|
|
|
| 173 |
</details>
|
| 174 |
`;
|
| 175 |
}).join('')}
|
| 176 |
</div>
|
| 177 |
|
| 178 |
+
<!-- Peer Learning FAB -->
|
| 179 |
<button onclick="window.openPeerModal()" class="fixed bottom-6 right-6 bg-purple-600 hover:bg-purple-500 text-white rounded-full p-4 shadow-xl shadow-purple-600/40 transition-transform hover:scale-110 active:scale-90 z-40"
|
| 180 |
title="查看同學作業">
|
| 181 |
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| 182 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0z" />
|
| 183 |
</svg>
|
| 184 |
</button>
|
| 185 |
+
</div>
|
| 186 |
+
`;
|
| 187 |
}
|
| 188 |
|
| 189 |
export function setupStudentEvents() {
|
|
|
|
| 213 |
};
|
| 214 |
|
| 215 |
window.submitLevel = async (challengeId) => {
|
| 216 |
+
const input = document.getElementById(`input-${challengeId}`);
|
| 217 |
+
const errorMsg = document.getElementById(`error-${challengeId}`);
|
| 218 |
const prompt = input.value;
|
| 219 |
const roomCode = localStorage.getItem('vibecoding_room_code');
|
| 220 |
const userId = localStorage.getItem('vibecoding_user_id');
|
|
|
|
| 260 |
const newCardHTML = renderTaskCard(challenge, newProgress);
|
| 261 |
|
| 262 |
// 3. Replace in DOM
|
| 263 |
+
const oldCard = document.getElementById(`card-${challengeId}`);
|
| 264 |
if (oldCard) {
|
| 265 |
oldCard.outerHTML = newCardHTML;
|
| 266 |
} else {
|
|
|
|
| 327 |
let optionsHtml = '<option value="" disabled selected>選擇題目...</option>';
|
| 328 |
if (cachedChallenges.length > 0) {
|
| 329 |
optionsHtml += cachedChallenges.map(c =>
|
| 330 |
+
`<option value="${c.id}">[${c.level}] ${c.title}</option>`
|
| 331 |
).join('');
|
| 332 |
}
|
| 333 |
|
| 334 |
return `
|
| 335 |
+
<div id="peer-modal" class="fixed inset-0 bg-black bg-opacity-80 backdrop-blur-sm hidden flex items-center justify-center z-50 p-4">
|
| 336 |
<div class="bg-gray-800 rounded-2xl w-full max-w-md h-[80vh] flex flex-col border border-gray-700 shadow-2xl">
|
| 337 |
<div class="p-6 border-b border-gray-700 flex justify-between items-center">
|
| 338 |
<h3 class="text-xl font-bold text-white">同學的成功提示詞</h3>
|
|
|
|
| 349 |
<div class="text-center text-gray-500 mt-10">請選擇一個題目來查看</div>
|
| 350 |
</div>
|
| 351 |
</div>
|
| 352 |
+
</div>
|
| 353 |
`;
|
| 354 |
}
|
| 355 |
|
|
|
|
| 385 |
const isLiked = p.likedBy && p.likedBy.includes(currentUserId);
|
| 386 |
|
| 387 |
return `
|
| 388 |
+
<div class="bg-gray-700/30 p-4 rounded-xl border border-gray-600">
|
| 389 |
<div class="flex items-center justify-between mb-2">
|
| 390 |
<div class="flex items-center space-x-2">
|
| 391 |
<div class="w-6 h-6 rounded-full bg-cyan-600 flex items-center justify-center text-xs font-bold text-white">
|
|
|
|
| 404 |
</button>
|
| 405 |
</div>
|
| 406 |
<p class="text-gray-300 font-mono text-sm bg-black/20 p-3 rounded-lg border border-gray-700/50 whitespace-pre-wrap">${p.prompt}</p>
|
| 407 |
+
</div>
|
| 408 |
`}).join('');
|
| 409 |
|
| 410 |
// Attach challenge title for notification context
|
| 411 |
+
window.currentPeerChallengeTitle = document.querySelector(`#peer-challenge-select option[value="${challengeId}"]`).text;
|
| 412 |
};
|
| 413 |
|
| 414 |
// Like Handler
|