Lashtw commited on
Commit
1f2360a
·
verified ·
1 Parent(s): 5637c13

Upload 8 files

Browse files
Files changed (2) hide show
  1. src/services/classroom.js +56 -17
  2. src/views/StudentView.js +37 -1
src/services/classroom.js CHANGED
@@ -244,32 +244,71 @@ export async function getPeerPrompts(roomCode, challengeId) {
244
  // Let's store nickname in progress for easier read? Or fetch users.
245
  // For now, let's fetch matching progress, then unique userIds, then fetch those users.
246
 
 
 
247
  const q = query(
248
  progressRef,
249
- where("roomCode", "==", roomCode),
250
  where("challengeId", "==", challengeId),
251
  where("status", "==", "completed")
252
  );
253
 
254
- const snapshot = await getDocs(q);
255
- const entries = [];
256
-
257
- for (const doc of snapshot.docs) {
258
- const data = doc.data();
259
- // Fetch nickname (not efficient N+1, but okay for prototype with small rooms)
260
- // Optimization: Cache users or store nickname in progress.
261
- // Let's assume we can fetch user.
262
- const userSnap = await getDoc(doc(db, USERS_COLLECTION, data.userId));
263
- if (userSnap.exists()) {
264
- entries.push({
265
- nickname: userSnap.data().nickname,
266
- prompt: data.submission_prompt,
267
- timestamp: data.timestamp
268
- });
 
 
 
 
 
 
 
 
 
 
 
269
  }
 
 
 
 
 
270
  }
 
271
 
272
- return entries;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  }
274
 
275
  /**
 
244
  // Let's store nickname in progress for easier read? Or fetch users.
245
  // For now, let's fetch matching progress, then unique userIds, then fetch those users.
246
 
247
+ // Simplified query for prototype: Fetch ALL completions for this challenge, then filter in memory.
248
+ // This avoids needing complex Composite Indexes for every room/challenge combination.
249
  const q = query(
250
  progressRef,
 
251
  where("challengeId", "==", challengeId),
252
  where("status", "==", "completed")
253
  );
254
 
255
+ try {
256
+ const snapshot = await getDocs(q);
257
+ const entries = [];
258
+
259
+ for (const docSnapshot of snapshot.docs) {
260
+ const data = docSnapshot.data();
261
+
262
+ // In-Memory Filtering for Room Code
263
+ // Note: Older data might not have roomCode. If undefined and we are in a room, maybe exclude?
264
+ // Or include if we want to share across global (but user said "this classroom").
265
+ // Let's strict check: Must match roomCode.
266
+ if (String(data.roomCode) !== String(roomCode)) {
267
+ continue;
268
+ }
269
+
270
+ // Fetch nickname
271
+ // Optimization: In a real app, store nickname in 'progress' to avoid N+1 queries.
272
+ // For now, fetch user.
273
+ const userSnap = await getDoc(doc(db, USERS_COLLECTION, data.userId));
274
+ if (userSnap.exists()) {
275
+ entries.push({
276
+ nickname: userSnap.data().nickname,
277
+ prompt: data.submission_prompt,
278
+ timestamp: data.timestamp
279
+ });
280
+ }
281
  }
282
+ return entries;
283
+
284
+ } catch (e) {
285
+ console.error("Error fetching peer prompts:", e);
286
+ return [];
287
  }
288
+ }
289
 
290
+ /**
291
+ * Resets a user's progress for a specific challenge (sets status to 'started')
292
+ * @param {string} userId
293
+ * @param {string} roomCode
294
+ * @param {string} challengeId
295
+ */
296
+ export async function resetProgress(userId, roomCode, challengeId) {
297
+ const progressRef = collection(db, PROGRESS_COLLECTION);
298
+ const q = query(
299
+ progressRef,
300
+ where("userId", "==", userId),
301
+ where("challengeId", "==", challengeId)
302
+ );
303
+ const snapshot = await getDocs(q);
304
+
305
+ if (!snapshot.empty) {
306
+ // Reset status to 'started' so they can submit again
307
+ await updateDoc(snapshot.docs[0].ref, {
308
+ status: 'started',
309
+ timestamp: serverTimestamp()
310
+ });
311
+ }
312
  }
313
 
314
  /**
src/views/StudentView.js CHANGED
@@ -1,4 +1,4 @@
1
- import { submitPrompt, getChallenges, startChallenge, getUserProgress, getPeerPrompts } from "../services/classroom.js";
2
 
3
  // Cache challenges locally
4
  let cachedChallenges = [];
@@ -54,6 +54,9 @@ export async function renderStudentView() {
54
  <h3 class="font-bold text-gray-300 group-hover:text-white transition-colors">${c.title}</h3>
55
  </div>
56
  <div class="flex items-center space-x-3">
 
 
 
57
  <span class="text-xs text-green-400 font-mono bg-green-900/30 px-2 py-1 rounded">已通關</span>
58
  <button onclick="document.getElementById('detail-${c.id}').classList.toggle('hidden')" class="text-gray-500 hover:text-white">
59
  <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
@@ -250,6 +253,39 @@ export function setupStudentEvents() {
250
  alert("提交失敗: " + error.message);
251
  }
252
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  }
254
 
255
  function participantDataCheck(roomCode, userId) {
 
1
+ import { submitPrompt, getChallenges, startChallenge, getUserProgress, getPeerPrompts, resetProgress } from "../services/classroom.js";
2
 
3
  // Cache challenges locally
4
  let cachedChallenges = [];
 
54
  <h3 class="font-bold text-gray-300 group-hover:text-white transition-colors">${c.title}</h3>
55
  </div>
56
  <div class="flex items-center space-x-3">
57
+ <button onclick="window.resetLevel('${c.id}')" class="text-xs bg-red-900/50 hover:bg-red-700 text-red-300 border border-red-800 px-2 py-1 rounded transition-colors" title="重置進度 (Reset)">
58
+ ↺ 重置
59
+ </button>
60
  <span class="text-xs text-green-400 font-mono bg-green-900/30 px-2 py-1 rounded">已通關</span>
61
  <button onclick="document.getElementById('detail-${c.id}').classList.toggle('hidden')" class="text-gray-500 hover:text-white">
62
  <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
 
253
  alert("提交失敗: " + error.message);
254
  }
255
  };
256
+
257
+ window.resetLevel = async (challengeId) => {
258
+ if (!confirm("確定要重置這一題的進度嗎?(提示詞將會保留,但狀態會變回進行中)")) return;
259
+
260
+ const roomCode = localStorage.getItem('vibecoding_room_code');
261
+ const userId = localStorage.getItem('vibecoding_user_id');
262
+
263
+ try {
264
+ // Import and call resetProgress (Need to make sure it is imported or available globally?
265
+ // Ideally import it. But setupStudentEvents is in module scope so imports are available.
266
+ // Wait, import 'resetProgress' is not in the top import list yet. I need to add it.)
267
+ // Let's assume I will update the import in the next step or use the global trick if needed.
268
+ // But I should edit the import first.
269
+ // For now, let's assume it is there. I will add it to the import list in a parallel or subsequent edit.
270
+
271
+ // Checking imports above... I see 'getUserProgress' but not 'resetProgress'. I must update imports.
272
+ // I'll do it in a separate edit step to be safe.
273
+
274
+ // For now, just the logic:
275
+ const { resetProgress } = await import("../services/classroom.js"); // Dynamic import to avoid changing top file lines again?
276
+ // Or just rely on previous 'replace' having updated the file?
277
+ // Actually, I should update the top import.
278
+
279
+ await resetProgress(userId, roomCode, challengeId);
280
+
281
+ const app = document.querySelector('#app');
282
+ app.innerHTML = await renderStudentView();
283
+
284
+ } catch (e) {
285
+ console.error(e);
286
+ alert("重置失敗");
287
+ }
288
+ };
289
  }
290
 
291
  function participantDataCheck(roomCode, userId) {