File size: 8,739 Bytes
66269b4
 
 
 
 
 
 
901982e
 
66269b4
 
 
 
 
 
 
 
 
901982e
66269b4
901982e
 
 
 
 
 
 
41fc024
684f9a1
 
901982e
8c465da
 
 
 
 
 
 
 
684f9a1
 
 
 
901982e
 
 
 
 
66269b4
901982e
 
 
 
 
 
 
 
 
 
 
66269b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
901982e
 
 
 
 
 
684f9a1
 
 
 
 
901982e
8c465da
 
 
 
 
 
 
 
 
 
 
 
 
 
901982e
 
 
 
 
 
8c465da
901982e
 
 
 
 
 
 
8c465da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
901982e
 
 
 
 
 
 
 
66269b4
 
 
901982e
66269b4
 
 
 
 
 
 
 
 
 
 
 
 
 
901982e
 
66269b4
 
 
901982e
 
 
 
66269b4
901982e
 
66269b4
901982e
 
 
 
66269b4
901982e
 
 
66269b4
 
901982e
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import { createRoom, subscribeToRoom, getChallenges } from "../services/classroom.js";

let cachedChallenges = [];

export async function renderInstructorView() {
    // Pre-fetch challenges for table headers
    cachedChallenges = await getChallenges();

    return `

    <div id="auth-modal" class="fixed inset-0 bg-black bg-opacity-90 backdrop-blur z-50 flex items-center justify-center">

        <div class="bg-gray-800 p-8 rounded-xl border border-gray-600 shadow-2xl max-w-sm w-full">

            <h2 class="text-xl font-bold text-center mb-6 text-white">🔒 講師身分驗證</h2>

            <input type="password" id="instructor-password" class="w-full bg-gray-900 border border-gray-700 rounded p-3 text-white text-center text-lg tracking-widest mb-4 focus:border-cyan-500 focus:outline-none" placeholder="輸入密碼">

            <button id="auth-btn" class="w-full bg-purple-600 hover:bg-purple-500 text-white font-bold py-3 rounded-lg transition-colors">確認進入</button>

        </div>

    </div>



    <div class="min-h-screen p-6 pb-20">

        <!-- Header -->

        <header class="flex flex-col md:flex-row justify-between items-center mb-10 bg-gray-800 bg-opacity-50 p-4 rounded-xl border border-gray-700 backdrop-blur-sm space-y-4 md:space-y-0">

            <h1 class="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-600">

                講師儀表板 Instructor Dashboard

            </h1>

            <div id="room-info" class="hidden flex items-center space-x-4">

                <span class="text-gray-400">教室代碼</span>

                <span id="display-room-code" class="text-3xl font-mono font-bold text-cyan-400 tracking-widest bg-gray-900 px-4 py-2 rounded-lg border border-cyan-500/30 shadow-[0_0_15px_rgba(34,211,238,0.3)]"></span>

            </div>

            <div class="flex space-x-3">

                <button id="nav-admin-btn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded-lg transition-all border border-gray-600">

                    管理題目 (Admin)

                </button>

                <div id="create-room-container" class="flex items-center space-x-2">

                    <div class="flex items-center bg-gray-900 rounded-lg border border-gray-700 p-1">

                        <input type="text" id="rejoin-room-code" placeholder="輸入舊代碼" class="bg-transparent text-white px-2 py-1 w-24 text-center focus:outline-none text-sm">

                        <button id="rejoin-room-btn" class="bg-gray-700 hover:bg-gray-600 text-xs text-white px-2 py-1 rounded transition-colors">

                            重回

                        </button>

                    </div>

                    <span class="text-gray-500">or</span>

                    <button id="create-room-btn" class="bg-purple-600 hover:bg-purple-500 text-white font-bold py-2 px-6 rounded-lg transition-all shadow-lg shadow-purple-500/30">

                        建立新教室

                    </button>

                </div>

            </div>

        </header>



        <!-- Student List -->

        <div id="dashboard-content" class="hidden">

            <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" id="students-grid">

                <!-- Student Cards will go here -->

                <div class="text-center text-gray-500 col-span-full py-20">

                    等待學員加入...

                </div>

            </div>

        </div>

    </div>

    `;
}

export function setupInstructorEvents() {
    // Auth Logic
    const authBtn = document.getElementById('auth-btn');
    const pwdInput = document.getElementById('instructor-password');
    const authModal = document.getElementById('auth-modal');

    authBtn.addEventListener('click', () => checkPassword());
    pwdInput.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') checkPassword();
    });

    function checkPassword() {
        if (pwdInput.value === '88300') {
            authModal.classList.add('hidden');
        } else {
            alert('密碼錯誤');
            pwdInput.value = '';
        }
    }

    const createBtn = document.getElementById('create-room-btn');
    const roomInfo = document.getElementById('room-info');
    const createContainer = document.getElementById('create-room-container');
    const dashboardContent = document.getElementById('dashboard-content');
    const displayRoomCode = document.getElementById('display-room-code');
    const studentsGrid = document.getElementById('students-grid');
    const navAdminBtn = document.getElementById('nav-admin-btn');

    navAdminBtn.addEventListener('click', () => {
        window.location.hash = 'admin';
    });

    // Auto-fill room code from local storage
    const savedRoomCode = localStorage.getItem('vibecoding_instructor_room');
    if (savedRoomCode) {
        document.getElementById('rejoin-room-code').value = savedRoomCode;
    }

    const rejoinBtn = document.getElementById('rejoin-room-btn');
    rejoinBtn.addEventListener('click', () => {
        const code = document.getElementById('rejoin-room-code').value.trim();
        if (!code) return alert('請輸入教室代碼');

        enterRoom(code);
    });

    createBtn.addEventListener('click', async () => {
        try {
            createBtn.disabled = true;
            createBtn.textContent = "建立中...";

            const roomCode = await createRoom();
            enterRoom(roomCode);

        } catch (error) {
            console.error(error);
            alert("建立教室失敗");
            createBtn.disabled = false;
        }
    });

    function enterRoom(roomCode) {
        // UI Switch
        createContainer.classList.add('hidden');
        roomInfo.classList.remove('hidden');
        dashboardContent.classList.remove('hidden');
        displayRoomCode.textContent = roomCode;

        // Save to local storage
        localStorage.setItem('vibecoding_instructor_room', roomCode);

        // Subscribe to updates
        subscribeToRoom(roomCode, (students) => {
            renderStudentCards(students, studentsGrid);
        });
    }
}

function renderStudentCards(students, container) {
    if (students.length === 0) {
        container.innerHTML = '<div class="text-center text-gray-500 col-span-full py-20">尚無學員加入</div>';
        return;
    }

    // Sort students by join time (if available) or random
    // students.sort((a,b) => a.joinedAt - b.joinedAt);

    container.innerHTML = students.map(student => {
        const progress = student.progress || {}; // Map of challengeId -> {status, prompt ...}

        // Progress Summary
        let totalCompleted = 0;
        let badgesHtml = cachedChallenges.map(c => {
            const isCompleted = progress[c.id]?.status === 'completed';
            if (isCompleted) totalCompleted++;

            // Only show completed dots/badges or progress bar to save space?
            // User requested "Card showing status". 15 items is a lot for small badges.
            // Let's us simple dots color-coded by level.

            const colors = { beginner: 'cyan', intermediate: 'blue', advanced: 'purple' };
            const color = colors[c.level] || 'gray';

            return `

                <div class="w-3 h-3 rounded-full ${isCompleted ? `bg-${color}-500 shadow-[0_0_5px_${color}]` : 'bg-gray-700'} 

                     title="${c.title} (${c.level})" 

                ></div>

            `;
        }).join('');

        return `

        <div class="bg-gray-800 bg-opacity-40 backdrop-blur rounded-xl border border-gray-700 p-4 hover:border-gray-500 transition-all flex flex-col">

            <div class="flex items-center justify-between mb-4">

                <div class="flex items-center space-x-3">

                    <div class="w-10 h-10 rounded-full bg-gradient-to-br from-gray-700 to-gray-600 flex items-center justify-center text-lg font-bold text-white uppercase">

                        ${student.nickname[0]}

                    </div>

                    <div>

                        <h3 class="font-bold text-white">${student.nickname}</h3>

                        <p class="text-xs text-gray-400">完成度: ${totalCompleted} / ${cachedChallenges.length}</p>

                    </div>

                </div>

            </div>

            

            <div class="flex flex-wrap gap-2 mt-auto">

                ${badgesHtml}

            </div>

        </div>

        `;
    }).join('');
}