Dashboard UX cleanup: fix German typos, add loading spinner, fix popstate, create static dir
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled

This commit is contained in:
Pepe44DEV
2026-07-01 02:42:57 +02:00
parent 691447d473
commit 85fd5ec915
3 changed files with 126 additions and 21 deletions

View File

@@ -98,9 +98,11 @@ router.get('/', (req, res) => {
<div class="layout">
${sidebar}
<div class="content">
<h1>Waehle eine Guild aus</h1>
<div class="muted">Nur Guilds, auf denen der Bot ist.</div>
<main id="guildGrid"></main>
<h1>Wähle einen Server aus</h1>
<div class="muted">Nur Server, auf denen der Bot ist.</div>
<main id="guildGrid">
<div class="loading-wrap"><div class="spinner"></div><span>Lade Server...</span></div>
</main>
</div>
</div>
<script>
@@ -273,6 +275,9 @@ router.get('/', (req, res) => {
@media (max-width: 1100px) {
.tickets-grid { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
}
.spinner { width:28px; height:28px; border:3px solid rgba(255,255,255,0.12); border-top-color:var(--accent); border-radius:50%; animation:spin .6s linear infinite; margin:12px auto; }
@keyframes spin { to { transform:rotate(360deg); } }
.loading-wrap { display:flex; flex-direction:column; align-items:center; gap:6px; padding:24px; color:var(--muted); font-size:13px; }
</style>
</head>
<body>
@@ -331,10 +336,10 @@ router.get('/', (req, res) => {
<div class="card" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center; justify-content:space-between;">
<div>
<p class="section-title">Tickets</p>
<p class="section-sub">bersicht, Pipeline, SLA, Automationen, Knowledge-Base.</p>
<p class="section-sub">Übersicht, Pipeline, SLA, Automationen, Knowledge-Base.</p>
</div>
<div class="row" style="gap:8px; flex-wrap:wrap;">
<button class="secondary-btn ticket-tab-btn active" data-tab="overview">bersicht</button>
<button class="secondary-btn ticket-tab-btn active" data-tab="overview">Übersicht</button>
<button class="secondary-btn ticket-tab-btn" data-tab="pipeline">Pipeline</button>
<button class="secondary-btn ticket-tab-btn" data-tab="sla">SLA</button>
<button class="secondary-btn ticket-tab-btn" data-tab="automations">Automationen</button>
@@ -347,7 +352,7 @@ router.get('/', (req, res) => {
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
<div>
<p class="section-title">Ticketliste</p>
<p class="section-sub">Links auswhlen, Details im Modal. Plus ffnet Panel-Erstellung.</p>
<p class="section-sub">Links auswählen, Details im Modal. Plus öffnet Panel-Erstellung.</p>
</div>
<div class="row" style="gap:10px;">
<button class="secondary-btn" id="openSupportLogin" type="button">Support-Login Einstellungen</button>
@@ -382,7 +387,7 @@ router.get('/', (req, res) => {
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px;">
<div>
<p class="section-title">Status-Pipeline</p>
<p class="section-sub">Tickets nach Phase. Status per Dropdown ndern.</p>
<p class="section-sub">Tickets nach Phase. Status per Dropdown ändern.</p>
</div>
</div>
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(240px,1fr)); gap:12px;" id="pipelineGrid">
@@ -542,7 +547,7 @@ router.get('/', (req, res) => {
<div class="form-field">
<label class="form-label">Custom Badwords (kommagetrennt oder zeilenweise)</label>
<textarea id="automodBadwords" rows="3" placeholder="badword1, badword2"></textarea>
<p class="muted">Diese Woerter werden zusaetzlich zum Standard-Filter geblockt.</p>
<p class="muted">Diese Wörter werden zusätzlich zum Standard-Filter geblockt.</p>
</div>
<div class="form-field">
<label class="form-label">Whitelist-Rollen (IDs, Kommagetrennt)</label>
@@ -627,7 +632,7 @@ router.get('/', (req, res) => {
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap;">
<div>
<p class="section-title">Dynamic Voice</p>
<p class="section-sub">Lobby waehlen, Channel-Namen & Limits setzen.</p>
<p class="section-sub">Lobby wählen, Channel-Namen & Limits setzen.</p>
</div>
<div class="inline">
<span class="form-label">Aktivieren</span>
@@ -676,7 +681,7 @@ router.get('/', (req, res) => {
<div class="row" style="justify-content:space-between; align-items:flex-start; gap:12px;">
<div>
<p class="section-title">Birthday</p>
<p class="section-sub">Automatische Glueckwuensche je Guild.</p>
<p class="section-sub">Automatische Glückwünsche je Server.</p>
</div>
<div class="row" style="align-items:center; gap:10px; flex-wrap:wrap; justify-content:flex-end;">
<label class="form-label">Sendezeit (Stunde)</label>
@@ -700,7 +705,7 @@ router.get('/', (req, res) => {
<div class="row" style="justify-content:space-between; align-items:center;">
<div>
<p class="section-title">Gespeicherte Geburtstage</p>
<p class="section-sub">Eintraege werden per /birthday angelegt.</p>
<p class="section-sub">Einträge werden per /birthday angelegt.</p>
</div>
</div>
<div id="birthdayList" class="module-list"></div>
@@ -736,7 +741,7 @@ router.get('/', (req, res) => {
<textarea id="reactionRoleDescription" rows="2" placeholder="Kurze Beschreibung fuer das Embed"></textarea>
</div>
<div class="form-field">
<label class="form-label">Eintraege (Emoji | Role ID | Label | Beschreibung)</label>
<label class="form-label">Einträge (Emoji | Role ID | Label | Beschreibung)</label>
<textarea id="reactionRoleEntries" rows="4" placeholder="😀 | 123456789 | Freunde | Erhaelt Freunde Rolle"></textarea>
<p class="muted">Eine Zeile pro Zuordnung. Label/Beschreibung optional.</p>
</div>
@@ -1446,7 +1451,7 @@ router.get('/', (req, res) => {
edit.addEventListener('click', () => editServerStat(item));
const del = document.createElement('button');
del.className = 'danger-btn';
del.textContent = 'Loeschen';
del.textContent = 'Löschen';
del.addEventListener('click', () => {
serverStatsCache.items = (serverStatsCache.items || []).filter((x) => x !== item);
renderServerStats();
@@ -1999,14 +2004,14 @@ router.get('/', (req, res) => {
edit.addEventListener('click', () => fillAutomationForm(r));
const del = document.createElement('button');
del.className = 'danger-btn';
del.textContent = 'Lschen';
del.textContent = 'Löschen';
del.addEventListener('click', async () => {
const res = await fetch('/api/automations/' + r.id, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ guildId: currentGuild })
});
showToast(res.ok ? 'Regel gelscht' : 'Lschen fehlgeschlagen', !res.ok);
showToast(res.ok ? 'Regel gelöscht' : 'Löschen fehlgeschlagen', !res.ok);
if (res.ok) loadAutomations();
});
actions.appendChild(edit);
@@ -2066,14 +2071,14 @@ router.get('/', (req, res) => {
edit.addEventListener('click', () => fillKbForm(a));
const del = document.createElement('button');
del.className = 'danger-btn';
del.textContent = 'Lschen';
del.textContent = 'Löschen';
del.addEventListener('click', async () => {
const res = await fetch('/api/kb/' + a.id, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ guildId: currentGuild })
});
showToast(res.ok ? 'Artikel gelscht' : 'Lschen fehlgeschlagen', !res.ok);
showToast(res.ok ? 'Artikel gelöscht' : 'Löschen fehlgeschlagen', !res.ok);
if (res.ok) loadKb();
});
actions.appendChild(edit);
@@ -2319,7 +2324,7 @@ router.get('/', (req, res) => {
editBtn.addEventListener('click', () => fillEventForm(ev));
const delBtn = document.createElement('button');
delBtn.className = 'danger-btn';
delBtn.textContent = 'Loeschen';
delBtn.textContent = 'Löschen';
delBtn.style.padding = '8px 10px';
delBtn.addEventListener('click', () => deleteEvent(ev.id));
actions.appendChild(editBtn);
@@ -2385,7 +2390,7 @@ router.get('/', (req, res) => {
async function deleteEvent(id) {
if (!currentGuild) return;
const res = await fetch('/api/events/' + id, { method:'DELETE', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ guildId: currentGuild }) });
showToast(res.ok ? 'Event geloescht' : 'Event loeschen fehlgeschlagen', !res.ok);
showToast(res.ok ? 'Event gelöscht' : 'Event löschen fehlgeschlagen', !res.ok);
if (res.ok) loadEvents();
}
@@ -2539,7 +2544,7 @@ router.get('/', (req, res) => {
delBtn.className = 'danger-btn';
delBtn.style.padding = '8px 10px';
delBtn.style.fontSize = '12px';
delBtn.textContent = 'Loeschen';
delBtn.textContent = 'Löschen';
delBtn.addEventListener('click', () => deleteReactionRole(set.id));
actions.appendChild(editBtn);
actions.appendChild(syncBtn);
@@ -2569,7 +2574,7 @@ router.get('/', (req, res) => {
async function deleteReactionRole(id) {
if (!currentGuild) return;
const res = await fetch('/api/reactionroles/' + id, { method:'DELETE', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ guildId: currentGuild }) });
showToast(res.ok ? 'Geloescht' : 'Fehler beim Loeschen', !res.ok);
showToast(res.ok ? 'Gelöscht' : 'Fehler beim Löschen', !res.ok);
if (res.ok) {
if (editingReactionRole === id) resetReactionRoleForm();
loadReactionRoles();
@@ -2999,6 +3004,12 @@ router.get('/', (req, res) => {
});
}
window.addEventListener('popstate', () => {
const section = location.hash.replace('#', '') || 'overview';
activateSection(section);
if (section === 'admin' && isAdmin) loadAdminAll();
});
const initialSection = location.hash.replace('#', '') || 'overview';
activateSection(initialSection);