Spaces:
Running
Running
Upload 8 files
Browse files- src/views/InstructorView.js +74 -7
src/views/InstructorView.js
CHANGED
|
@@ -874,13 +874,51 @@ export function setupInstructorEvents() {
|
|
| 874 |
data-id="${p.id}"
|
| 875 |
onchange="handlePromptSelection(this)">
|
| 876 |
</div>
|
| 877 |
-
<
|
| 878 |
-
<div class="text-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 879 |
`;
|
| 880 |
container.appendChild(card);
|
| 881 |
});
|
| 882 |
};
|
| 883 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 884 |
// Selection Logic
|
| 885 |
let selectedPrompts = []; // Stores IDs
|
| 886 |
|
|
@@ -916,7 +954,6 @@ export function setupInstructorEvents() {
|
|
| 916 |
btn.classList.add('opacity-50', 'cursor-not-allowed');
|
| 917 |
}
|
| 918 |
}
|
| 919 |
-
|
| 920 |
// Comparison Logic
|
| 921 |
const compareBtn = document.getElementById('btn-compare-prompts');
|
| 922 |
if (compareBtn) {
|
|
@@ -945,10 +982,40 @@ export function setupInstructorEvents() {
|
|
| 945 |
});
|
| 946 |
}
|
| 947 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 948 |
window.openComparisonView = (items) => {
|
| 949 |
const modal = document.getElementById('comparison-modal');
|
| 950 |
const grid = document.getElementById('comparison-grid');
|
| 951 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 952 |
// Setup Grid Rows (Vertical Stacking)
|
| 953 |
let rowClass = 'grid-rows-1';
|
| 954 |
if (items.length === 2) rowClass = 'grid-rows-2';
|
|
@@ -963,7 +1030,7 @@ export function setupInstructorEvents() {
|
|
| 963 |
col.className = 'flex flex-row h-full bg-gray-900 p-4 overflow-hidden';
|
| 964 |
col.innerHTML = `
|
| 965 |
<div class="w-48 flex-shrink-0 border-r border-gray-700 pr-4 mr-4 flex flex-col justify-center">
|
| 966 |
-
<h3 class="text-xl font-bold text-cyan-400 mb-1">${item.author}</h3>
|
| 967 |
<p class="text-md text-gray-400 truncate" title="${item.title}">${item.title}</p>
|
| 968 |
</div>
|
| 969 |
<!-- Prompt Content: Larger Text -->
|
|
@@ -1135,10 +1202,10 @@ function renderTransposedHeatmap(students) {
|
|
| 1135 |
|
| 1136 |
if (p) {
|
| 1137 |
if (p.status === 'completed') {
|
| 1138 |
-
statusClass = 'bg-green-500/20 border-green-500/50 hover:bg-green-500/40 cursor-
|
| 1139 |
content = '✅';
|
| 1140 |
-
//
|
| 1141 |
-
action = `
|
| 1142 |
} else if (p.status === 'started') {
|
| 1143 |
// Check stuck
|
| 1144 |
const startedAt = p.timestamp ? p.timestamp.toDate() : new Date();
|
|
|
|
| 874 |
data-id="${p.id}"
|
| 875 |
onchange="handlePromptSelection(this)">
|
| 876 |
</div>
|
| 877 |
+
<!-- Prompt Content -->
|
| 878 |
+
<div class="bg-black/30 rounded p-2 flex-1 overflow-y-auto font-mono text-green-300 whitespace-pre-wrap custom-scrollbar text-base leading-snug group-hover:text-green-200 transaction-colors mb-2">${p.prompt}</div>
|
| 879 |
+
|
| 880 |
+
<!-- Footer: Time + Actions -->
|
| 881 |
+
<div class="flex justify-between items-center text-[10px] text-gray-500 mt-auto">
|
| 882 |
+
<span>${p.time}</span>
|
| 883 |
+
<div class="flex space-x-2">
|
| 884 |
+
<button onclick="window.confirmReset('${p.studentId}', '${p.challengeId}', '${p.title}')" class="px-2 py-1 bg-red-900/30 hover:bg-red-800 text-red-400 rounded transition-colors text-xs border border-red-800/50" title="退回重做">
|
| 885 |
+
退回
|
| 886 |
+
</button>
|
| 887 |
+
<button onclick="window.broadcastPrompt('${p.studentId}', '${p.challengeId}')" class="px-2 py-1 bg-cyan-900/30 hover:bg-cyan-800 text-cyan-400 rounded transition-colors text-xs border border-cyan-800/50" title="投放此作品">
|
| 888 |
+
投放
|
| 889 |
+
</button>
|
| 890 |
+
</div>
|
| 891 |
+
</div>
|
| 892 |
`;
|
| 893 |
container.appendChild(card);
|
| 894 |
});
|
| 895 |
};
|
| 896 |
|
| 897 |
+
// Helper Actions
|
| 898 |
+
window.confirmReset = async (userId, challengeId, title) => {
|
| 899 |
+
if (confirm(`確定要退回 ${title} 嗎?此動作將清除學員目前的進度。`)) {
|
| 900 |
+
const roomCode = localStorage.getItem('vibecoding_instructor_room');
|
| 901 |
+
if (userId && challengeId && roomCode) {
|
| 902 |
+
try {
|
| 903 |
+
const { resetProgress } = await import("../services/classroom.js");
|
| 904 |
+
await resetProgress(userId, roomCode, challengeId);
|
| 905 |
+
// Refresh current list if open? (It will stay open but might not update immediately if realtime check isn't hooked to modal content. But subscriptions update `currentStudents`. We might need to refresh list)
|
| 906 |
+
// For now, simple alert or auto-close
|
| 907 |
+
alert("已退回");
|
| 908 |
+
// close modal to refresh data context
|
| 909 |
+
document.getElementById('prompt-list-modal').classList.add('hidden');
|
| 910 |
+
} catch (e) {
|
| 911 |
+
console.error(e);
|
| 912 |
+
alert("退回失敗");
|
| 913 |
+
}
|
| 914 |
+
}
|
| 915 |
+
}
|
| 916 |
+
};
|
| 917 |
+
|
| 918 |
+
window.broadcastPrompt = (userId, challengeId) => {
|
| 919 |
+
window.showBroadcastModal(userId, challengeId);
|
| 920 |
+
};
|
| 921 |
+
|
| 922 |
// Selection Logic
|
| 923 |
let selectedPrompts = []; // Stores IDs
|
| 924 |
|
|
|
|
| 954 |
btn.classList.add('opacity-50', 'cursor-not-allowed');
|
| 955 |
}
|
| 956 |
}
|
|
|
|
| 957 |
// Comparison Logic
|
| 958 |
const compareBtn = document.getElementById('btn-compare-prompts');
|
| 959 |
if (compareBtn) {
|
|
|
|
| 982 |
});
|
| 983 |
}
|
| 984 |
|
| 985 |
+
let isAnonymous = false;
|
| 986 |
+
|
| 987 |
+
window.toggleAnonymous = (btn) => {
|
| 988 |
+
isAnonymous = !isAnonymous;
|
| 989 |
+
btn.textContent = isAnonymous ? '🙈 顯示姓名' : '👀 隱藏姓名';
|
| 990 |
+
btn.classList.toggle('bg-gray-700');
|
| 991 |
+
btn.classList.toggle('bg-purple-700');
|
| 992 |
+
|
| 993 |
+
// Update DOM
|
| 994 |
+
document.querySelectorAll('.comparison-author').forEach(el => {
|
| 995 |
+
if (isAnonymous) {
|
| 996 |
+
el.dataset.original = el.textContent;
|
| 997 |
+
el.textContent = '學員';
|
| 998 |
+
el.classList.add('blur-sm'); // Optional Effect
|
| 999 |
+
setTimeout(() => el.classList.remove('blur-sm'), 300);
|
| 1000 |
+
} else {
|
| 1001 |
+
if (el.dataset.original) el.textContent = el.dataset.original;
|
| 1002 |
+
}
|
| 1003 |
+
});
|
| 1004 |
+
};
|
| 1005 |
+
|
| 1006 |
window.openComparisonView = (items) => {
|
| 1007 |
const modal = document.getElementById('comparison-modal');
|
| 1008 |
const grid = document.getElementById('comparison-grid');
|
| 1009 |
|
| 1010 |
+
// Reset Anonymous State
|
| 1011 |
+
isAnonymous = false;
|
| 1012 |
+
const anonBtn = document.getElementById('btn-anonymous-toggle');
|
| 1013 |
+
if (anonBtn) {
|
| 1014 |
+
anonBtn.textContent = '👀 隱藏姓名';
|
| 1015 |
+
anonBtn.classList.remove('bg-purple-700');
|
| 1016 |
+
anonBtn.classList.add('bg-gray-700');
|
| 1017 |
+
}
|
| 1018 |
+
|
| 1019 |
// Setup Grid Rows (Vertical Stacking)
|
| 1020 |
let rowClass = 'grid-rows-1';
|
| 1021 |
if (items.length === 2) rowClass = 'grid-rows-2';
|
|
|
|
| 1030 |
col.className = 'flex flex-row h-full bg-gray-900 p-4 overflow-hidden';
|
| 1031 |
col.innerHTML = `
|
| 1032 |
<div class="w-48 flex-shrink-0 border-r border-gray-700 pr-4 mr-4 flex flex-col justify-center">
|
| 1033 |
+
<h3 class="text-xl font-bold text-cyan-400 mb-1 comparison-author">${item.author}</h3>
|
| 1034 |
<p class="text-md text-gray-400 truncate" title="${item.title}">${item.title}</p>
|
| 1035 |
</div>
|
| 1036 |
<!-- Prompt Content: Larger Text -->
|
|
|
|
| 1202 |
|
| 1203 |
if (p) {
|
| 1204 |
if (p.status === 'completed') {
|
| 1205 |
+
statusClass = 'bg-green-500/20 border-green-500/50 hover:bg-green-500/40 cursor-default shadow-[0_0_10px_rgba(34,197,94,0.1)]';
|
| 1206 |
content = '✅';
|
| 1207 |
+
// Action removed: Moved to prompt list view
|
| 1208 |
+
action = `title="完成 - 請點擊標題查看詳情"`;
|
| 1209 |
} else if (p.status === 'started') {
|
| 1210 |
// Check stuck
|
| 1211 |
const startedAt = p.timestamp ? p.timestamp.toDate() : new Date();
|