Lashtw commited on
Commit
d789f7b
·
verified ·
1 Parent(s): 93f0573

Upload 10 files

Browse files
src/views/InstructorView.js CHANGED
@@ -612,7 +612,7 @@ export function setupInstructorEvents() {
612
  if (confirm('確定要退回此學員的進度嗎?學員將需要重新作答。')) {
613
  try {
614
  const { resetProgress } = await import("../services/classroom.js");
615
- await resetProgress(roomCode, userId, challengeId);
616
  alert('已成功退回,學員將需要重新作答。');
617
  window.closeBroadcastModal();
618
  } catch (e) {
 
612
  if (confirm('確定要退回此學員的進度嗎?學員將需要重新作答。')) {
613
  try {
614
  const { resetProgress } = await import("../services/classroom.js");
615
+ await resetProgress(userId, roomCode, challengeId);
616
  alert('已成功退回,學員將需要重新作答。');
617
  window.closeBroadcastModal();
618
  } catch (e) {
src/views/StudentView.js CHANGED
@@ -326,19 +326,14 @@ export async function renderStudentView() {
326
  // Setup Real-time Subscription
327
  if (window.currentProgressUnsub) window.currentProgressUnsub();
328
  window.currentProgressUnsub = subscribeToUserProgress(userId, (newProgressMap) => {
329
- // Merge updates
330
- const updatedProgress = { ...userProgress, ...newProgressMap };
331
 
332
- // Smart Update: Check if visual refresh is needed
333
  const newState = calculateMonsterState(updatedProgress, classSize, userProfile);
334
  const fixedContainer = document.getElementById('monster-container-fixed');
335
  const currentMonsterId = fixedContainer?.getAttribute('data-monster-id');
336
 
337
- // Tolerance for scale check usually not needed if we want scale to update visuals immediately?
338
- // Actually, scale change usually means totalCompleted changed.
339
- // If we want smooth growth, replacing DOM resets animation which looks slight jumpy but acceptable.
340
- // But the user complained about "position reset" (walk cycle reset).
341
-
342
  if (fixedContainer && String(currentMonsterId) === String(newState.monster.id)) {
343
  // Monster ID is same (no evolution/devolution).
344
  // Just update stats tooltip
@@ -347,29 +342,36 @@ export async function renderStudentView() {
347
  statsContainer.innerHTML = renderMonsterStats(newState);
348
  }
349
 
350
- // What if level up (scale change)?
351
- // If we don't replace DOM, scale won't update in style attribute.
352
- // We should update the style manually.
353
- const artContainer = fixedContainer.querySelector('.pixel-art-container');
354
- if (artContainer && newState.currentScale) {
355
- // Update animation with new scale
356
- // Note: Modifying 'transform' directly might conflict with keyframes unless keyframes use relative or we update style variable.
357
- // Keyframes use: scale(${currentScale}). This is hardcoded in specific keyframes string in <style>.
358
- // We can't easily update keyframes dynamic values without replacing style block.
359
-
360
- // If totalCompleted changed (level up), user *might* accept a reset because they levelled up.
361
- // But simply giving a heart shouldn't reset.
362
-
363
- const oldTotal = parseInt(fixedContainer.querySelector('.bg-gray-900\\/90')?.textContent?.replace('Lv.', '') || '1') - 1;
364
- if (oldTotal !== newState.totalCompleted) {
365
- // Level changed -> Scale changed -> Re-render full (reset animation is fine for Level Up)
366
- monsterContainer.innerHTML = renderMonsterSection(updatedProgress, classSize, userProfile);
367
- }
368
  }
369
  } else {
370
- // Monster changed or clean slate -> Full Render
371
  monsterContainer.innerHTML = renderMonsterSection(updatedProgress, classSize, userProfile);
372
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  });
374
 
375
  // Accordion Layout
 
326
  // Setup Real-time Subscription
327
  if (window.currentProgressUnsub) window.currentProgressUnsub();
328
  window.currentProgressUnsub = subscribeToUserProgress(userId, (newProgressMap) => {
329
+ // Merge updates (Actually newProgressMap is complete source of truth from firestore listener)
330
+ const updatedProgress = newProgressMap;
331
 
332
+ // 1. Update Monster (Visuals)
333
  const newState = calculateMonsterState(updatedProgress, classSize, userProfile);
334
  const fixedContainer = document.getElementById('monster-container-fixed');
335
  const currentMonsterId = fixedContainer?.getAttribute('data-monster-id');
336
 
 
 
 
 
 
337
  if (fixedContainer && String(currentMonsterId) === String(newState.monster.id)) {
338
  // Monster ID is same (no evolution/devolution).
339
  // Just update stats tooltip
 
342
  statsContainer.innerHTML = renderMonsterStats(newState);
343
  }
344
 
345
+ // Check level up (totalCompleted) to update scale/animation if needed
346
+ const oldTotal = parseInt(fixedContainer.querySelector('.bg-gray-900\\/90')?.textContent?.replace('Lv.', '') || '1') - 1;
347
+ if (oldTotal !== newState.totalCompleted) {
348
+ monsterContainer.innerHTML = renderMonsterSection(updatedProgress, classSize, userProfile);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  }
350
  } else {
351
+ // Full Monster Render
352
  monsterContainer.innerHTML = renderMonsterSection(updatedProgress, classSize, userProfile);
353
  }
354
+
355
+ // 2. Update Task List (Accordions) - Realtime "Reject" support
356
+ const levelGroups = {
357
+ beginner: cachedChallenges.filter(c => c.level === 'beginner'),
358
+ intermediate: cachedChallenges.filter(c => c.level === 'intermediate'),
359
+ advanced: cachedChallenges.filter(c => c.level === 'advanced')
360
+ };
361
+ const levelNames = {
362
+ beginner: "初級 (Beginner)",
363
+ intermediate: "中級 (Intermediate)",
364
+ advanced: "高級 (Advanced)"
365
+ };
366
+
367
+ ['beginner', 'intermediate', 'advanced'].forEach(level => {
368
+ const detailEl = document.getElementById(`details-group-${level}`);
369
+ if (detailEl) {
370
+ // renderLevelGroup checks existing DOM for 'open' state, so it persists
371
+ const newHTML = renderLevelGroup(level, levelGroups[level], updatedProgress, levelNames);
372
+ detailEl.outerHTML = newHTML;
373
+ }
374
+ });
375
  });
376
 
377
  // Accordion Layout