/* FluentGerman.ai — Admin panel logic */ document.addEventListener('DOMContentLoaded', () => { if (!requireAuth() || !requireAdmin()) return; const user = getUser(); document.getElementById('admin-name').textContent = user?.username || 'Admin'; // Tab switching const tabs = document.querySelectorAll('.tab'); const panels = document.querySelectorAll('.tab-panel'); tabs.forEach(tab => { tab.addEventListener('click', () => { tabs.forEach(t => t.classList.remove('active')); panels.forEach(p => p.classList.add('hidden')); tab.classList.add('active'); document.getElementById(tab.dataset.panel).classList.remove('hidden'); }); }); // ── Users ────────────────────────────────────────────────────── const usersBody = document.getElementById('users-body'); const userModal = document.getElementById('user-modal'); const userForm = document.getElementById('user-form'); let editingUserId = null; async function loadUsers() { try { const users = await apiJSON('/users/'); usersBody.innerHTML = ''; if (users.length === 0) { usersBody.innerHTML = 'No clients yet'; return; } users.forEach(u => { const row = document.createElement('tr'); row.innerHTML = ` ${u.username} ${u.email} ${u.is_active ? 'Active' : 'Inactive'} ${new Date(u.created_at).toLocaleDateString()} `; usersBody.appendChild(row); }); } catch (e) { showToast(e.message, 'error'); } } window.showUserModal = (editing = false) => { editingUserId = null; document.getElementById('user-modal-title').textContent = 'Add Client'; userForm.reset(); document.getElementById('user-password').required = true; userModal.classList.remove('hidden'); }; window.closeUserModal = () => { userModal.classList.add('hidden'); editingUserId = null; }; window.editUser = async (id) => { try { const users = await apiJSON('/users/'); const u = users.find(x => x.id === id); if (!u) return; editingUserId = id; document.getElementById('user-modal-title').textContent = 'Edit Client'; document.getElementById('user-username').value = u.username; document.getElementById('user-email').value = u.email; document.getElementById('user-password').value = ''; document.getElementById('user-password').required = false; userModal.classList.remove('hidden'); } catch (e) { showToast(e.message, 'error'); } }; userForm.addEventListener('submit', async (e) => { e.preventDefault(); const data = { username: document.getElementById('user-username').value.trim(), email: document.getElementById('user-email').value.trim(), }; const password = document.getElementById('user-password').value; if (password) data.password = password; try { if (editingUserId) { await apiJSON(`/users/${editingUserId}`, { method: 'PUT', body: JSON.stringify(data), }); showToast('Client updated'); } else { data.password = password; await apiJSON('/users/', { method: 'POST', body: JSON.stringify(data), }); showToast('Client created'); } closeUserModal(); loadUsers(); } catch (e) { showToast(e.message, 'error'); } }); window.deleteUser = async (id) => { if (!confirm('Delete this client? This will also remove all their instructions.')) return; try { await api(`/users/${id}`, { method: 'DELETE' }); showToast('Client deleted'); loadUsers(); } catch (e) { showToast(e.message, 'error'); } }; // ── Instructions ─────────────────────────────────────────────── const instructionsBody = document.getElementById('instructions-body'); const instrModal = document.getElementById('instruction-modal'); const instrForm = document.getElementById('instruction-form'); const instrUserSelect = document.getElementById('instr-user'); let editingInstrId = null; let currentFilterUserId = null; async function loadInstructions(userId = null) { try { const url = userId ? `/instructions/?user_id=${userId}` : '/instructions/'; const instructions = await apiJSON(url); instructionsBody.innerHTML = ''; if (instructions.length === 0) { instructionsBody.innerHTML = 'No instructions yet'; return; } instructions.forEach(inst => { const row = document.createElement('tr'); row.innerHTML = ` ${inst.title} ${inst.type} ${inst.user_id || 'Global'} ${inst.content.substring(0, 60)}${inst.content.length > 60 ? '...' : ''} `; instructionsBody.appendChild(row); }); } catch (e) { showToast(e.message, 'error'); } } async function loadUserOptions() { try { const users = await apiJSON('/users/'); instrUserSelect.innerHTML = ''; users.forEach(u => { instrUserSelect.innerHTML += ``; }); } catch (e) { // silently fail } } window.manageInstructions = (userId, username) => { currentFilterUserId = userId; // Switch to instructions tab tabs.forEach(t => t.classList.remove('active')); panels.forEach(p => p.classList.add('hidden')); document.querySelector('[data-panel="instructions-panel"]').classList.add('active'); document.getElementById('instructions-panel').classList.remove('hidden'); loadInstructions(userId); }; window.showInstructionModal = () => { editingInstrId = null; document.getElementById('instr-modal-title').textContent = 'Add Instruction'; instrForm.reset(); loadUserOptions(); instrModal.classList.remove('hidden'); }; window.closeInstructionModal = () => { instrModal.classList.add('hidden'); editingInstrId = null; }; window.editInstruction = async (id) => { try { const instructions = await apiJSON('/instructions/'); const inst = instructions.find(x => x.id === id); if (!inst) return; editingInstrId = id; await loadUserOptions(); document.getElementById('instr-modal-title').textContent = 'Edit Instruction'; document.getElementById('instr-title').value = inst.title; document.getElementById('instr-content').value = inst.content; document.getElementById('instr-type').value = inst.type; instrUserSelect.value = inst.user_id || ''; instrModal.classList.remove('hidden'); } catch (e) { showToast(e.message, 'error'); } }; instrForm.addEventListener('submit', async (e) => { e.preventDefault(); const data = { title: document.getElementById('instr-title').value.trim(), content: document.getElementById('instr-content').value.trim(), type: document.getElementById('instr-type').value, user_id: instrUserSelect.value ? parseInt(instrUserSelect.value) : null, }; try { if (editingInstrId) { await apiJSON(`/instructions/${editingInstrId}`, { method: 'PUT', body: JSON.stringify(data), }); showToast('Instruction updated'); } else { await apiJSON('/instructions/', { method: 'POST', body: JSON.stringify(data), }); showToast('Instruction created'); } closeInstructionModal(); loadInstructions(currentFilterUserId); } catch (e) { showToast(e.message, 'error'); } }); window.deleteInstruction = async (id) => { if (!confirm('Delete this instruction?')) return; try { await api(`/instructions/${id}`, { method: 'DELETE' }); showToast('Instruction deleted'); loadInstructions(currentFilterUserId); } catch (e) { showToast(e.message, 'error'); } }; // ── File upload for instructions ──────────────────────────────── window.uploadInstructionFile = () => { const input = document.createElement('input'); input.type = 'file'; input.accept = '.txt,.md'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const text = await file.text(); document.getElementById('instr-title').value = file.name.replace(/\.[^.]+$/, ''); document.getElementById('instr-content').value = text; showInstructionModal(); }; input.click(); }; // ── Voice instruction generation ─────────────────────────────── const voiceGenBtn = document.getElementById('voice-gen-btn'); const voiceGenOutput = document.getElementById('voice-gen-output'); const voiceGenText = document.getElementById('voice-gen-text'); const voice = new VoiceManager(); voice.init().then(() => { voice.onResult = (text) => { document.getElementById('voice-raw-text').value = text; }; voice.onStateChange = (recording) => { const btn = document.getElementById('voice-record-btn'); btn.classList.toggle('recording', recording); btn.textContent = recording ? '⏹ Stop Recording' : '🎤 Start Recording'; }; }); window.toggleVoiceRecord = () => voice.toggleRecording(); window.generateInstruction = async () => { const rawText = document.getElementById('voice-raw-text').value.trim(); if (!rawText) { showToast('Please record or type some text first', 'error'); return; } voiceGenBtn.disabled = true; voiceGenBtn.textContent = 'Generating...'; try { const result = await apiJSON('/voice/generate-instruction', { method: 'POST', body: JSON.stringify({ raw_text: rawText }), }); voiceGenText.value = result.instruction; voiceGenOutput.classList.remove('hidden'); } catch (e) { showToast(e.message, 'error'); } voiceGenBtn.disabled = false; voiceGenBtn.textContent = '✨ Generate Instruction'; }; window.saveGeneratedInstruction = () => { const content = voiceGenText.value.trim(); if (!content) return; loadUserOptions(); document.getElementById('instr-content').value = content; document.getElementById('instr-title').value = 'Voice Generated Instruction'; showInstructionModal(); }; // ── Init ─────────────────────────────────────────────────────── loadUsers(); loadInstructions(); document.getElementById('logout-btn').addEventListener('click', logout); });