Lashtw commited on
Commit
f256bb2
·
verified ·
1 Parent(s): 051bc5c

Upload 8 files

Browse files
Files changed (1) hide show
  1. 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() { <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">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 - ${ challengeId } `);
179
- const errorMsg = document.getElementById(`error - ${ challengeId } `);
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 - ${ challengeId } `);
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
- `< option value = "${c.id}" > [${ c.level }] ${ c.title }</option > `
293
  ).join('');
294
  }
295
 
296
  return `
297
- < 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" >
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
- </div >
315
  `;
316
  }
317
 
@@ -347,7 +385,7 @@ window.loadPeerPrompts = async (challengeId) => {
347
  const isLiked = p.likedBy && p.likedBy.includes(currentUserId);
348
 
349
  return `
350
- < div class="bg-gray-700/30 p-4 rounded-xl border border-gray-600" >
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 - challenge - select option[value = "${challengeId}"]`).text;
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