yxy
This commit is contained in:
@@ -2,6 +2,8 @@ import { apiAction } from './api.js';
|
||||
import { initUserPanel, initAccountPage } from './ui-user.js';
|
||||
import { mountLogoutButton, ensureFloatingLogout } from './ui-auth.js';
|
||||
|
||||
const pageType = document.body?.dataset?.page || 'account';
|
||||
|
||||
async function ensureAuthenticated() {
|
||||
try {
|
||||
const me = await apiAction('auth.me', { method: 'GET' });
|
||||
@@ -17,9 +19,19 @@ async function ensureAuthenticated() {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAccess() {
|
||||
const role = (window.__currentUser?.role || '').toLowerCase();
|
||||
if (pageType === 'admin' && role !== 'owner' && role !== 'admin') {
|
||||
window.location.href = '/account.php';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const ok = await ensureAuthenticated();
|
||||
if (!ok) return;
|
||||
if (!ensureAccess()) return;
|
||||
initUserPanel();
|
||||
initAccountPage();
|
||||
mountLogoutButton('#btn-logout', { redirect: '/login.php' });
|
||||
|
||||
140
public/assets/js/dashboard.js
Normal file
140
public/assets/js/dashboard.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import { apiAction, toast } from './api.js';
|
||||
import { initUserPanel } from './ui-user.js';
|
||||
import { mountLogoutButton, ensureFloatingLogout } from './ui-auth.js';
|
||||
|
||||
const state = {
|
||||
counts: { templates: 0, sections: 0, blocks: 0, snippets: 0, renders_total: 0 },
|
||||
usage: [],
|
||||
};
|
||||
|
||||
async function ensureAuthenticated() {
|
||||
try {
|
||||
const me = await apiAction('auth.me', { method: 'GET' });
|
||||
if (!me?.ok || !me?.user) {
|
||||
window.location.href = '/login.php';
|
||||
return false;
|
||||
}
|
||||
window.__currentUser = me.user;
|
||||
document.documentElement.classList.remove('auth-pending');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAccess() {
|
||||
const role = (window.__currentUser?.role || '').toLowerCase();
|
||||
if (role !== 'owner' && role !== 'admin') {
|
||||
toast('Kein Zugriff auf das Dashboard', false);
|
||||
window.location.href = '/account.php';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderCounts(counts) {
|
||||
const mapping = {
|
||||
templates: 'count-templates',
|
||||
sections: 'count-sections',
|
||||
blocks: 'count-blocks',
|
||||
snippets: 'count-snippets',
|
||||
renders_total: 'count-usage',
|
||||
};
|
||||
Object.entries(mapping).forEach(([key, id]) => {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
const value = counts[key] ?? 0;
|
||||
el.textContent = typeof value === 'number' ? value.toLocaleString('de-DE') : value;
|
||||
});
|
||||
}
|
||||
|
||||
function formatDate(value) {
|
||||
if (!value) return '–';
|
||||
try {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return date.toLocaleString('de-DE');
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function renderUsage(list) {
|
||||
const table = document.getElementById('usageTable');
|
||||
if (!table) return;
|
||||
const tbody = table.querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
if (!list.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="text-sm text-slate-500">Noch keine Daten vorhanden.</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = list.map(item => `
|
||||
<tr data-template-id="${item.template_id}">
|
||||
<td>${escapeHtml(item.name)}</td>
|
||||
<td>${item.render_count.toLocaleString('de-DE')}</td>
|
||||
<td>${escapeHtml(formatDate(item.last_rendered_at || item.updated_at))}</td>
|
||||
<td class="text-right">
|
||||
<button type="button" class="btn" data-reset="${item.template_id}">Zähler zurücksetzen</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async function loadMetrics() {
|
||||
try {
|
||||
const res = await apiAction('dashboard.metrics', { method: 'GET' });
|
||||
if (!res?.ok) throw new Error(res?.error || 'Dashboard konnte nicht geladen werden');
|
||||
state.counts = res.counts || state.counts;
|
||||
state.usage = Array.isArray(res.usage) ? res.usage : [];
|
||||
renderCounts(state.counts);
|
||||
renderUsage(state.usage);
|
||||
} catch (err) {
|
||||
toast(err.message || 'Fehler beim Laden', false);
|
||||
}
|
||||
}
|
||||
|
||||
async function resetUsage(templateId) {
|
||||
try {
|
||||
await apiAction('dashboard.reset_usage', { method: 'POST', data: { template_id: templateId } });
|
||||
toast('Zähler zurückgesetzt', true);
|
||||
await loadMetrics();
|
||||
} catch (err) {
|
||||
toast(err.message || 'Zurücksetzen fehlgeschlagen', false);
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
const refresh = document.getElementById('btn-refresh-dashboard');
|
||||
refresh?.addEventListener('click', () => loadMetrics());
|
||||
|
||||
const table = document.getElementById('usageTable');
|
||||
table?.addEventListener('click', ev => {
|
||||
const btn = ev.target.closest('button[data-reset]');
|
||||
if (!btn) return;
|
||||
const id = Number(btn.getAttribute('data-reset'));
|
||||
if (!id) return;
|
||||
if (confirm('Zähler für dieses Template wirklich löschen?')) {
|
||||
resetUsage(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const ok = await ensureAuthenticated();
|
||||
if (!ok) return;
|
||||
if (!ensureAccess()) return;
|
||||
initUserPanel();
|
||||
bindEvents();
|
||||
await loadMetrics();
|
||||
mountLogoutButton('#btn-logout', { redirect: '/login.php' });
|
||||
ensureFloatingLogout({ redirect: '/login.php' });
|
||||
});
|
||||
@@ -13,6 +13,7 @@ const state = {
|
||||
};
|
||||
|
||||
let avatarBtn;
|
||||
let userMenuPanel;
|
||||
let profileForm;
|
||||
let passwordForm;
|
||||
let settingsForm;
|
||||
@@ -22,10 +23,21 @@ let senderTable;
|
||||
let senderForm;
|
||||
let bridgePreview;
|
||||
let validateBridgeBtn;
|
||||
let menuInitialized = false;
|
||||
let menuOpen = false;
|
||||
|
||||
export function initUserPanel() {
|
||||
avatarBtn = document.getElementById('btn-user');
|
||||
userMenuPanel = document.getElementById('userMenuPanel');
|
||||
updateAvatar();
|
||||
updateRoleVisibility();
|
||||
if (!menuInitialized && avatarBtn && userMenuPanel) {
|
||||
avatarBtn.addEventListener('click', toggleUserMenu);
|
||||
document.addEventListener('click', handleDocumentClick, true);
|
||||
document.addEventListener('keydown', handleMenuKeydown);
|
||||
userMenuPanel.addEventListener('click', handleMenuItemClick);
|
||||
menuInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function initAccountPage() {
|
||||
@@ -58,22 +70,24 @@ export function initAccountPage() {
|
||||
btn.addEventListener('click', () => switchTab(btn.getAttribute('data-user-tab')));
|
||||
});
|
||||
|
||||
settingsForm?.querySelectorAll('button[data-rotate]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const type = btn.getAttribute('data-rotate');
|
||||
if (type && state.rotate[type] !== undefined) {
|
||||
state.rotate[type] = true;
|
||||
toast('Token wird nach dem Speichern erneuert.', true, { duration: 2000 });
|
||||
}
|
||||
if (settingsForm) {
|
||||
settingsForm.querySelectorAll('button[data-rotate]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const type = btn.getAttribute('data-rotate');
|
||||
if (type && state.rotate[type] !== undefined) {
|
||||
state.rotate[type] = true;
|
||||
toast('Token wird nach dem Speichern erneuert.', true, { duration: 2000 });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
settingsForm?.querySelectorAll('button[data-download]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const type = btn.getAttribute('data-download');
|
||||
if (type) downloadFile(type);
|
||||
settingsForm.querySelectorAll('button[data-download]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const type = btn.getAttribute('data-download');
|
||||
if (type) downloadFile(type);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
switchTab(state.currentTab);
|
||||
loadAccountData();
|
||||
@@ -96,6 +110,40 @@ function updateAvatar() {
|
||||
target.textContent = name ? name.trim().charAt(0).toUpperCase() : 'U';
|
||||
}
|
||||
|
||||
function toggleUserMenu(ev) {
|
||||
ev?.preventDefault();
|
||||
if (!userMenuPanel || !avatarBtn) return;
|
||||
menuOpen = !menuOpen;
|
||||
userMenuPanel.classList.toggle('hidden', !menuOpen);
|
||||
avatarBtn.setAttribute('aria-expanded', menuOpen ? 'true' : 'false');
|
||||
}
|
||||
|
||||
function closeUserMenu() {
|
||||
if (!menuOpen) return;
|
||||
menuOpen = false;
|
||||
if (userMenuPanel) userMenuPanel.classList.add('hidden');
|
||||
if (avatarBtn) avatarBtn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
function handleDocumentClick(ev) {
|
||||
if (!userMenuPanel || !avatarBtn || !menuOpen) return;
|
||||
const target = ev.target;
|
||||
if (avatarBtn.contains(target) || userMenuPanel.contains(target)) return;
|
||||
closeUserMenu();
|
||||
}
|
||||
|
||||
function handleMenuKeydown(ev) {
|
||||
if (ev.key === 'Escape') {
|
||||
closeUserMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMenuItemClick(ev) {
|
||||
const item = ev.target.closest('.user-menu-item');
|
||||
if (!item) return;
|
||||
closeUserMenu();
|
||||
}
|
||||
|
||||
function updateRoleVisibility() {
|
||||
const role = (window.__currentUser?.role || '').toLowerCase();
|
||||
document.querySelectorAll('[data-role]').forEach(el => {
|
||||
@@ -139,15 +187,17 @@ async function loadAccountData() {
|
||||
}
|
||||
fillProfileForm(res.user);
|
||||
fillSettingsForm(res.settings || {});
|
||||
if (isOwner()) {
|
||||
if (teamTable && isOwner()) {
|
||||
await loadUsers();
|
||||
}
|
||||
if (isAdmin()) {
|
||||
await loadSenders();
|
||||
} else {
|
||||
state.senders = [];
|
||||
state.senderMap = new Map();
|
||||
renderSenderList();
|
||||
if (senderTable) {
|
||||
if (isAdmin()) {
|
||||
await loadSenders();
|
||||
} else {
|
||||
state.senders = [];
|
||||
state.senderMap = new Map();
|
||||
renderSenderList();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
Reference in New Issue
Block a user