Upload new version

This commit is contained in:
2026-01-20 01:12:22 +01:00
parent abd74a9a50
commit 3d559924a9
15 changed files with 1632 additions and 517 deletions

View File

@@ -25,6 +25,15 @@ let teamTable;
let userForm;
let senderTable;
let senderForm;
let sectionsList;
let sectionsCreateForm;
let sectionNameInput;
let sectionsDeleteDialog;
let sectionsDeleteForm;
let sectionsDeleteTarget;
let sectionsDeleteText;
let sectionsDeleteCancel;
let sectionDragId = null;
let menuInitialized = false;
let menuOpen = false;
let debugButton;
@@ -75,6 +84,14 @@ export function initAccountPage() {
adminTablesAddBtn = document.getElementById('adminBridgeTablesAdd');
adminTablesRemoveBtn = document.getElementById('adminBridgeTablesRemove');
adminLoadBridgeBtn = document.getElementById('btn-admin-load-bridge');
sectionsList = document.getElementById('sectionsList');
sectionsCreateForm = document.getElementById('sectionsCreateForm');
sectionNameInput = document.getElementById('sectionNameInput');
sectionsDeleteDialog = document.getElementById('sectionsDeleteDialog');
sectionsDeleteForm = document.getElementById('sectionsDeleteForm');
sectionsDeleteTarget = document.getElementById('sectionsDeleteTarget');
sectionsDeleteText = document.getElementById('sectionsDeleteText');
sectionsDeleteCancel = document.getElementById('sectionsDeleteCancel');
document.getElementById('btn-user-add')?.addEventListener('click', () => openUserForm());
document.getElementById('userFormCancel')?.addEventListener('click', () => closeUserForm());
@@ -122,6 +139,8 @@ export function initAccountPage() {
refreshBridgeTablesFromEndpoint();
});
initSectionsManager();
window.addEventListener('bridge-setup-updated', (ev) => {
const setup = ev?.detail || {};
refreshAdminTables(setup.tables || [], state.settings.bridge_tables || []);
@@ -132,6 +151,151 @@ export function initAccountPage() {
updateRoleVisibility();
}
function initSectionsManager() {
if (!sectionsList || !sectionsCreateForm || !sectionNameInput) return;
sectionsCreateForm.addEventListener('submit', async (ev) => {
ev.preventDefault();
const name = sectionNameInput.value.trim();
if (!name) return;
try {
const res = await apiAction('sections_config.create', { method: 'POST', data: { name } });
if (!res?.ok) throw new Error(res?.error || 'Erstellen fehlgeschlagen');
sectionNameInput.value = '';
await loadSectionsConfig();
toast('Section erstellt', true);
} catch (err) {
toast(err.message || 'Erstellen fehlgeschlagen', false);
}
});
sectionsDeleteCancel && (sectionsDeleteCancel.onclick = () => {
sectionsDeleteDialog?.close();
});
sectionsDeleteForm?.addEventListener('submit', async (ev) => {
ev.preventDefault();
const id = Number(sectionsDeleteForm?.dataset?.sectionId || 0);
const target = Number(sectionsDeleteTarget?.value || 0);
if (!id || !target) return;
try {
const res = await apiAction('sections_config.delete', { method: 'POST', data: { id, move_to: target } });
if (!res?.ok) throw new Error(res?.error || 'Löschen fehlgeschlagen');
sectionsDeleteDialog?.close();
await loadSectionsConfig();
toast('Section gelöscht', true);
} catch (err) {
toast(err.message || 'Löschen fehlgeschlagen', false);
}
});
loadSectionsConfig();
}
async function loadSectionsConfig() {
try {
const res = await apiAction('sections_config.list', { method: 'GET' });
const items = Array.isArray(res?.items) ? res.items : [];
renderSectionsList(items);
} catch (err) {
toast(err.message || 'Sections konnten nicht geladen werden', false);
}
}
function renderSectionsList(items) {
if (!sectionsList) return;
const rows = items || [];
sectionsList.innerHTML = rows.map((item) => {
const isTemplate = Number(item.is_template) === 1;
const dragAttr = isTemplate ? '' : 'draggable="true"';
const badge = isTemplate ? '<span class="text-xs text-sky-700 bg-sky-100 px-2 py-0.5 rounded-full">Fix</span>' : '';
const editBtn = isTemplate ? '' : `<button type="button" class="btn text-xs" data-edit="${item.id}">Umbenennen</button>`;
const delBtn = isTemplate ? '' : `<button type="button" class="btn btn-danger text-xs" data-del="${item.id}">Löschen</button>`;
return `<li class="section-item flex items-center gap-3 border rounded-lg px-3 py-2 bg-white" data-id="${item.id}" ${dragAttr}>
<span class="cursor-grab text-slate-400 select-none">☰</span>
<div class="flex-1">
<div class="font-medium">${escapeHtml(item.name || '')}</div>
<div class="text-xs text-slate-500">${escapeHtml(item.slug || '')}</div>
</div>
${badge}
<div class="flex gap-2">${editBtn}${delBtn}</div>
</li>`;
}).join('');
sectionsList.querySelectorAll('[data-edit]').forEach(btn => btn.addEventListener('click', async () => {
const id = Number(btn.dataset.edit || 0);
const current = rows.find(r => Number(r.id) === id);
if (!current) return;
const next = prompt('Neuer Name', current.name || '');
if (!next || next.trim() === current.name) return;
try {
const res = await apiAction('sections_config.update', { method: 'POST', data: { id, name: next.trim() } });
if (!res?.ok) throw new Error(res?.error || 'Speichern fehlgeschlagen');
await loadSectionsConfig();
toast('Section gespeichert', true);
} catch (err) {
toast(err.message || 'Speichern fehlgeschlagen', false);
}
}));
sectionsList.querySelectorAll('[data-del]').forEach(btn => btn.addEventListener('click', () => {
const id = Number(btn.dataset.del || 0);
const current = rows.find(r => Number(r.id) === id);
if (!current) return;
const targets = rows.filter(r => Number(r.id) !== id);
if (!targets.length) {
toast('Keine Ziel-Section verfügbar', false);
return;
}
if (sectionsDeleteText) {
sectionsDeleteText.textContent = `Section "${current.name}" löschen?`;
}
if (sectionsDeleteTarget) {
sectionsDeleteTarget.innerHTML = targets
.map(r => `<option value="${r.id}">${escapeHtml(r.name || '')}</option>`)
.join('');
}
if (sectionsDeleteForm) {
sectionsDeleteForm.dataset.sectionId = String(id);
}
sectionsDeleteDialog?.showModal?.();
}));
sectionsList.querySelectorAll('[draggable="true"]').forEach(item => {
item.addEventListener('dragstart', (ev) => {
sectionDragId = item.dataset.id || null;
ev.dataTransfer?.setData('text/plain', sectionDragId || '');
});
item.addEventListener('dragend', () => {
sectionDragId = null;
});
item.addEventListener('dragover', (ev) => {
ev.preventDefault();
});
item.addEventListener('drop', async (ev) => {
ev.preventDefault();
const targetId = item.dataset.id || null;
if (!sectionDragId || !targetId || sectionDragId === targetId) return;
const ids = Array.from(sectionsList.querySelectorAll('[data-id]')).map(el => el.getAttribute('data-id'));
const fromIndex = ids.indexOf(sectionDragId);
const toIndex = ids.indexOf(targetId);
if (fromIndex === -1 || toIndex === -1) return;
ids.splice(fromIndex, 1);
ids.splice(toIndex, 0, sectionDragId);
try {
const res = await apiAction('sections_config.reorder', { method: 'POST', data: { order: ids } });
if (!res?.ok) throw new Error(res?.error || 'Sortierung fehlgeschlagen');
await loadSectionsConfig();
toast('Sortierung gespeichert', true);
} catch (err) {
toast(err.message || 'Sortierung fehlgeschlagen', false);
} finally {
sectionDragId = null;
}
});
});
}
function isOwner() {
return (window.__currentUser?.role || '').toLowerCase() === 'owner';
}