// assets/scripts/team.js import { esc, createTranslator, ANIMATION_MS, removeWithAnimation } from './utils.js'; const t = createTranslator('Team'); document.addEventListener('DOMContentLoaded', () => { // ── Tabs ────────────────────────────────────────────────────────────────── document.querySelectorAll('.crud-tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('.crud-tab').forEach(t => t.classList.toggle('crud-tab--active', t === tab) ); document.querySelectorAll('[data-tab-panel]').forEach(panel => { panel.hidden = panel.dataset.tabPanel !== tab.dataset.tab; }); }); }); // ── Einlade-Modal ───────────────────────────────────────────────────────── const modal = document.getElementById('team-modal'); const errorsBox = document.getElementById('team-modal-errors'); const openModal = () => { modal.hidden = false; }; const closeModal = () => { modal.hidden = true; errorsBox.hidden = true; ['inv-firstName', 'inv-lastName', 'inv-email'].forEach(id => { document.getElementById(id).value = ''; }); const defaultRole = modal.querySelector('input[name="inv-role"][value="member"]'); if (defaultRole) defaultRole.checked = true; }; document.getElementById('team-invite-btn').addEventListener('click', openModal); document.getElementById('team-modal-close').addEventListener('click', closeModal); document.getElementById('team-modal-cancel').addEventListener('click', closeModal); modal.addEventListener('click', e => { if (e.target === modal) closeModal(); }); const submitBtn = document.getElementById('team-modal-submit'); submitBtn.addEventListener('click', async () => { const payload = { firstName: document.getElementById('inv-firstName').value.trim(), lastName: document.getElementById('inv-lastName').value.trim(), email: document.getElementById('inv-email').value.trim(), role: modal.querySelector('input[name="inv-role"]:checked')?.value ?? 'member', }; submitBtn.disabled = true; try { const res = await fetch('/api/team/invite', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await res.json(); if (!res.ok) { errorsBox.hidden = false; const errors = data.errors ?? [data.error]; errorsBox.innerHTML = ''; return; } closeModal(); window.location.reload(); } catch { errorsBox.hidden = false; errorsBox.innerHTML = ``; } finally { submitBtn.disabled = false; } }); // ── Listen-Delegation: aktive User + Einladungen ────────────────────────── const list = document.getElementById('team-list'); if (list) { list.addEventListener('click', e => { const actionEl = e.target.closest('[data-action]'); if (!actionEl) return; const row = e.target.closest('.crud-row'); if (!row) return; switch (actionEl.dataset.action) { case 'edit': openEdit(row); break; case 'save': saveEdit(row); break; case 'cancel': closeEdit(row); break; case 'delete': deleteMember(row); break; case 'delete-invite': deleteInvite(actionEl.dataset.id, row); break; } }); } // ── Listen-Delegation: archivierte User ─────────────────────────────────── const archivedList = document.getElementById('team-list-archived'); if (archivedList) { archivedList.addEventListener('click', e => { const actionEl = e.target.closest('[data-action]'); if (!actionEl) return; const row = e.target.closest('.crud-row'); if (!row) return; if (actionEl.dataset.action === 'unarchive') { unarchiveMember(row); } }); } // ── Inline Edit ─────────────────────────────────────────────────────────── function openEdit(row) { row.querySelector('.crud-row__display').hidden = true; row.querySelector('.crud-row__edit').hidden = false; row.querySelector('.edit-first-name')?.focus(); } function closeEdit(row) { row.querySelector('.crud-row__display').hidden = false; row.querySelector('.crud-row__edit').hidden = true; row.querySelector('.edit-first-name').value = row.dataset.firstName ?? ''; row.querySelector('.edit-last-name').value = row.dataset.lastName ?? ''; row.querySelector('.edit-email').value = row.dataset.email ?? ''; row.querySelector('.edit-note').value = row.dataset.note ?? ''; const currentRole = row.dataset.role; row.querySelectorAll('.edit-role').forEach(radio => { radio.checked = radio.value === currentRole; }); } async function saveEdit(row) { const saveBtn = row.querySelector('[data-action="save"]'); if (saveBtn?.disabled) return; const id = row.dataset.id; const firstName = row.querySelector('.edit-first-name').value.trim(); const lastName = row.querySelector('.edit-last-name').value.trim(); const email = row.querySelector('.edit-email').value.trim(); const note = row.querySelector('.edit-note').value || null; const role = row.querySelector('.edit-role:checked')?.value ?? row.dataset.role; if (saveBtn) saveBtn.disabled = true; try { const res = await fetch(`/api/team/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstName, lastName, email, note, role }), }); if (!res.ok) { const data = await res.json(); alert((data.errors ?? [data.error]).join('\n')); return; } const data = await res.json(); updateDisplay(row, data); closeEdit(row); } catch { alert(t('errorSave')); } finally { if (saveBtn) saveBtn.disabled = false; } } function updateDisplay(row, data) { row.querySelector('.crud-row__name').textContent = data.fullName; row.querySelector('.crud-row__meta').textContent = `(${data.roleLabel})`; row.dataset.firstName = data.firstName; row.dataset.lastName = data.lastName; row.dataset.email = data.email; row.dataset.note = data.note ?? ''; row.dataset.role = data.role; row.querySelector('.edit-first-name').value = data.firstName; row.querySelector('.edit-last-name').value = data.lastName; row.querySelector('.edit-email').value = data.email; row.querySelector('.edit-note').value = data.note ?? ''; row.querySelectorAll('.edit-role').forEach(radio => { radio.checked = radio.value === data.role; }); } // ── Delete ──────────────────────────────────────────────────────────────── async function deleteMember(row) { if (!confirm(t('confirmDelete'))) return; try { const res = await fetch(`/api/team/${row.dataset.id}`, { method: 'DELETE' }); if (res.status === 409) { if (confirm(t('confirmArchive'))) { await archiveMember(row); } return; } if (!res.ok) { const data = await res.json().catch(() => ({})); alert(data.error ?? t('errorDelete')); return; } removeWithAnimation(row, 'crud-row--removing'); } catch { alert(t('errorDelete')); } } async function deleteInvite(id, row) { if (!confirm(t('confirmRevokeInvite'))) return; try { const res = await fetch(`/api/team/invite/${id}`, { method: 'DELETE' }); if (!res.ok) { const data = await res.json().catch(() => ({})); alert(data.error ?? t('errorGeneric')); return; } removeWithAnimation(row, 'crud-row--removing'); } catch { alert(t('errorGeneric')); } } // ── Archive / Unarchive ─────────────────────────────────────────────────── async function archiveMember(row) { try { const res = await fetch(`/api/team/${row.dataset.id}/archive`, { method: 'PATCH' }); if (!res.ok) { alert(t('errorArchive')); return; } removeWithAnimation(row, 'crud-row--removing'); setTimeout(() => window.location.reload(), ANIMATION_MS); } catch { alert(t('errorArchive')); } } async function unarchiveMember(row) { try { const res = await fetch(`/api/team/${row.dataset.id}/unarchive`, { method: 'PATCH' }); if (!res.ok) { alert(t('errorRestore')); return; } removeWithAnimation(row, 'crud-row--removing'); setTimeout(() => window.location.reload(), ANIMATION_MS); } catch { alert(t('errorRestore')); } } });