This commit is contained in:
22
public/ts/components/admin/index.ts
Normal file
22
public/ts/components/admin/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function initAdminSection(guildId: string) {
|
||||
const section = document.getElementById('section-admin');
|
||||
if (!section) return;
|
||||
section.innerHTML = '<p class="muted">Lade Admin-Daten...</p>';
|
||||
try {
|
||||
const data: any = await api.settings(guildId);
|
||||
section.innerHTML = `
|
||||
<h2 class="section-title">Admin</h2>
|
||||
<div class="card">
|
||||
<p class="muted">Rohdaten (nur Admin):</p>
|
||||
<pre style="white-space:pre-wrap;max-height:320px;overflow:auto;">${JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
section.innerHTML = '<div class="empty-state">Admin-Daten konnten nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Admin-Daten', true);
|
||||
}
|
||||
}
|
||||
88
public/ts/components/dashboard.ts
Normal file
88
public/ts/components/dashboard.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { api } from '../services/api.js';
|
||||
import { getConfig, getState, setState } from '../state/store.js';
|
||||
import { showToast } from '../ui/toast.js';
|
||||
import { renderOverview } from './overview.js';
|
||||
import { initTicketsSection } from './tickets/index.js';
|
||||
import { initModulesSection } from './modules/index.js';
|
||||
import { initEventsSection } from './events/index.js';
|
||||
import { initAdminSection } from './admin/index.js';
|
||||
import { renderSettingsSection } from './settings.js';
|
||||
|
||||
let overviewInterval: number | null = null;
|
||||
let ticketsInterval: number | null = null;
|
||||
|
||||
async function populateGuildSelect() {
|
||||
const select = document.getElementById('guildSelect') as HTMLSelectElement | null;
|
||||
const cfg = getConfig();
|
||||
if (!select || !cfg) return;
|
||||
select.innerHTML = `<option>Loading...</option>`;
|
||||
try {
|
||||
const data = await api.guilds();
|
||||
select.innerHTML = '';
|
||||
data.guilds.forEach((g) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = g.id;
|
||||
opt.textContent = g.name;
|
||||
if (g.id === cfg.initialGuildId) opt.selected = true;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
const current = select.value || cfg.initialGuildId || data.guilds[0]?.id;
|
||||
setState({ guildId: current || undefined });
|
||||
select.value = current || '';
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showToast('Guilds konnten nicht geladen werden', true);
|
||||
}
|
||||
}
|
||||
|
||||
function registerGuildChange() {
|
||||
const select = document.getElementById('guildSelect') as HTMLSelectElement | null;
|
||||
if (!select) return;
|
||||
select.addEventListener('change', () => {
|
||||
const guildId = select.value;
|
||||
setState({ guildId });
|
||||
refreshSections();
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshSections() {
|
||||
const { guildId } = getState();
|
||||
if (!guildId) return;
|
||||
await renderOverview(guildId);
|
||||
await initTicketsSection(guildId);
|
||||
await initModulesSection(guildId);
|
||||
await renderSettingsSection(guildId);
|
||||
await initEventsSection(guildId);
|
||||
const cfg = getConfig();
|
||||
if (cfg?.isAdmin) {
|
||||
await initAdminSection(guildId);
|
||||
}
|
||||
}
|
||||
|
||||
function setupPolling() {
|
||||
const { guildId } = getState();
|
||||
if (overviewInterval) window.clearInterval(overviewInterval);
|
||||
if (ticketsInterval) window.clearInterval(ticketsInterval);
|
||||
overviewInterval = window.setInterval(() => {
|
||||
const current = getState().guildId;
|
||||
if (current) renderOverview(current);
|
||||
}, 10000);
|
||||
ticketsInterval = window.setInterval(() => {
|
||||
const current = getState().guildId;
|
||||
if (current) initTicketsSection(current);
|
||||
}, 12000);
|
||||
}
|
||||
|
||||
export function initDashboardView() {
|
||||
const cfg = getConfig();
|
||||
const logoutBtn = document.getElementById('logoutBtn');
|
||||
if (logoutBtn && cfg) {
|
||||
logoutBtn.addEventListener('click', () => (window.location.href = `${cfg.baseAuth}/logout`));
|
||||
}
|
||||
|
||||
populateGuildSelect().then(() => {
|
||||
registerGuildChange();
|
||||
refreshSections();
|
||||
setupPolling();
|
||||
});
|
||||
}
|
||||
39
public/ts/components/events/index.ts
Normal file
39
public/ts/components/events/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function initEventsSection(guildId: string) {
|
||||
const section = document.getElementById('section-events');
|
||||
if (!section) return;
|
||||
section.innerHTML = '<p class="muted">Lade Events...</p>';
|
||||
try {
|
||||
const data: any = await api.events(guildId);
|
||||
const events = data?.events || data || [];
|
||||
section.innerHTML = '<h2 class="section-title">Events</h2>';
|
||||
if (!events.length) {
|
||||
section.innerHTML += '<div class="empty-state">Keine Events geplant.</div>';
|
||||
return;
|
||||
}
|
||||
const list = document.createElement('div');
|
||||
list.className = 'ticket-list';
|
||||
events.forEach((ev: any) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'ticket-item';
|
||||
item.innerHTML = `
|
||||
<div class="row" style="justify-content:space-between;">
|
||||
<div>
|
||||
<div style="font-weight:750;">${ev.title || 'Event'}</div>
|
||||
<div class="muted">${ev.date || ''}</div>
|
||||
</div>
|
||||
<span class="pill">${ev.status || 'open'}</span>
|
||||
</div>
|
||||
<div class="muted">${ev.description || ''}</div>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
section.appendChild(list);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
section.innerHTML = '<div class="empty-state">Events konnten nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Events', true);
|
||||
}
|
||||
}
|
||||
56
public/ts/components/guildSelect.ts
Normal file
56
public/ts/components/guildSelect.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { api } from '../services/api.js';
|
||||
import { getConfig } from '../state/store.js';
|
||||
import { showToast } from '../ui/toast.js';
|
||||
|
||||
export async function initSelectionView() {
|
||||
const cfg = getConfig();
|
||||
const grid = document.getElementById('guildGrid');
|
||||
const logoutBtn = document.getElementById('logoutBtn');
|
||||
const userInfo = document.getElementById('userInfo');
|
||||
|
||||
if (logoutBtn && cfg) {
|
||||
logoutBtn.addEventListener('click', () => {
|
||||
window.location.href = `${cfg.baseAuth}/logout`;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const me = await api.me();
|
||||
if (userInfo && me?.user) userInfo.textContent = `${me.user.username}#${me.user.discriminator}`;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (!grid || !cfg) return;
|
||||
grid.innerHTML = '<div class="muted">Lade Guilds...</div>';
|
||||
try {
|
||||
const data = await api.guilds();
|
||||
grid.innerHTML = '';
|
||||
(data.guilds || []).forEach((g) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card clickable';
|
||||
card.innerHTML = `
|
||||
<div class="row">
|
||||
<img src="${g.icon ? `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.png` : 'https://cdn.discordapp.com/embed/avatars/0.png'}" alt="icon" style="width:42px;height:42px;border-radius:12px;object-fit:cover;"/>
|
||||
<div>
|
||||
<div style="font-weight:700;">${g.name}</div>
|
||||
<div class="muted">ID: ${g.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:10px;" class="pill">Zum Dashboard</div>
|
||||
`;
|
||||
card.addEventListener('click', () => {
|
||||
const qs = g.id ? `?guildId=${encodeURIComponent(g.id)}` : '';
|
||||
window.location.href = `${cfg.baseDashboard}${qs}`;
|
||||
});
|
||||
grid.appendChild(card);
|
||||
});
|
||||
if (!data.guilds?.length) {
|
||||
grid.innerHTML = '<div class="empty-state">Bot ist in keiner Guild. Bitte Bot einladen.</div>';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
grid.innerHTML = '<div class="empty-state">Fehler beim Laden der Guilds</div>';
|
||||
showToast('Guilds konnten nicht geladen werden', true);
|
||||
}
|
||||
}
|
||||
22
public/ts/components/modules/dynamicVoice.ts
Normal file
22
public/ts/components/modules/dynamicVoice.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderDynamicVoiceModule(guildId: string) {
|
||||
const container = document.getElementById('module-dynamicvoice');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Dynamic Voice...</p>';
|
||||
try {
|
||||
const data: any = await api.dynamicVoice(guildId);
|
||||
const cfg = data?.config || data?.dynamicVoiceConfig || {};
|
||||
container.innerHTML = `
|
||||
<h3 class="label">Dynamic Voice</h3>
|
||||
<p class="muted">Lobby: ${cfg.lobbyChannelId || '-'}</p>
|
||||
<p class="muted">Template: ${cfg.template || '-'}</p>
|
||||
<p class="muted">User-Limit: ${cfg.userLimit ?? '-'}</p>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Dynamic Voice konnte nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden von Dynamic Voice', true);
|
||||
}
|
||||
}
|
||||
99
public/ts/components/modules/index.ts
Normal file
99
public/ts/components/modules/index.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
import { setSwitch, getSwitch } from '../../ui/switch.js';
|
||||
import { renderWelcomeModule } from './welcome.js';
|
||||
import { renderLoggingModule } from './logging.js';
|
||||
import { renderReactionRolesModule } from './reactionRoles.js';
|
||||
import { renderDynamicVoiceModule } from './dynamicVoice.js';
|
||||
import { renderStatuspageModule } from './statuspage.js';
|
||||
import { renderServerStatsModule } from './serverstats.js';
|
||||
|
||||
export async function initModulesSection(guildId: string) {
|
||||
const section = document.getElementById('section-modules');
|
||||
if (!section) return;
|
||||
section.innerHTML = `
|
||||
<h2 class="section-title">Module</h2>
|
||||
<div class="card">
|
||||
<div class="module-list" id="module-toggles"></div>
|
||||
</div>
|
||||
<div class="grid" style="margin-top:16px;">
|
||||
<div class="card" id="module-welcome"></div>
|
||||
<div class="card" id="module-logging"></div>
|
||||
<div class="card" id="module-reactionroles"></div>
|
||||
<div class="card" id="module-dynamicvoice"></div>
|
||||
<div class="card" id="module-statuspage"></div>
|
||||
<div class="card" id="module-serverstats"></div>
|
||||
</div>
|
||||
`;
|
||||
await Promise.all([
|
||||
renderModuleToggles(guildId),
|
||||
renderWelcomeModule(guildId),
|
||||
renderLoggingModule(guildId),
|
||||
renderReactionRolesModule(guildId),
|
||||
renderDynamicVoiceModule(guildId),
|
||||
renderStatuspageModule(guildId),
|
||||
renderServerStatsModule(guildId)
|
||||
]);
|
||||
}
|
||||
|
||||
async function renderModuleToggles(guildId: string) {
|
||||
const container = document.getElementById('module-toggles');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Module...</p>';
|
||||
try {
|
||||
const data: any = await api.modules(guildId);
|
||||
const modules = data?.modules || data || {};
|
||||
container.innerHTML = '';
|
||||
const entries: Array<{ key: string; label: string; desc: string }> = [
|
||||
{ key: 'ticketsEnabled', label: 'Tickets', desc: 'Ticket-System aktivieren' },
|
||||
{ key: 'automodEnabled', label: 'Automod', desc: 'Moderations-Filter' },
|
||||
{ key: 'welcomeEnabled', label: 'Welcome', desc: 'Begrueßungsnachrichten' },
|
||||
{ key: 'musicEnabled', label: 'Musik', desc: 'Musiksteuerung' },
|
||||
{ key: 'levelingEnabled', label: 'Leveling', desc: 'XP/Level System' },
|
||||
{ key: 'statuspageEnabled', label: 'Statuspage', desc: 'Statusberichte' },
|
||||
{ key: 'serverStatsEnabled', label: 'Server Stats', desc: 'Stat-Channel' },
|
||||
{ key: 'birthdayEnabled', label: 'Birthday', desc: 'Geburtstagsmodul' },
|
||||
{ key: 'reactionRolesEnabled', label: 'Reaction Roles', desc: 'Selbstzuweisbare Rollen' },
|
||||
{ key: 'eventsEnabled', label: 'Events', desc: 'Event-Planung' },
|
||||
{ key: 'dynamicVoiceEnabled', label: 'Dynamic Voice', desc: 'Dynamische Voice Channels' }
|
||||
];
|
||||
entries.forEach((entry) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'module-item';
|
||||
row.innerHTML = `
|
||||
<div class="module-meta">
|
||||
<div class="module-title">${entry.label}</div>
|
||||
<div class="module-desc">${entry.desc}</div>
|
||||
</div>
|
||||
<div class="switch ${modules[entry.key] ? 'on' : ''}" data-key="${entry.key}"></div>
|
||||
`;
|
||||
const toggle = row.querySelector('.switch') as HTMLElement;
|
||||
toggle.addEventListener('click', async () => {
|
||||
setSwitch(toggle, !getSwitch(toggle));
|
||||
await saveModules(guildId);
|
||||
});
|
||||
container.appendChild(row);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Module konnten nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Module', true);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveModules(guildId: string) {
|
||||
const toggles = Array.from(document.querySelectorAll<HTMLElement>('#module-toggles .switch'));
|
||||
const payload: Record<string, unknown> = { guildId };
|
||||
toggles.forEach((t) => {
|
||||
const key = t.dataset.key;
|
||||
if (!key) return;
|
||||
payload[key] = t.classList.contains('on');
|
||||
});
|
||||
try {
|
||||
await api.saveSettings(payload);
|
||||
showToast('Module gespeichert');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showToast('Module speichern fehlgeschlagen', true);
|
||||
}
|
||||
}
|
||||
22
public/ts/components/modules/logging.ts
Normal file
22
public/ts/components/modules/logging.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderLoggingModule(guildId: string) {
|
||||
const container = document.getElementById('module-logging');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Logging...</p>';
|
||||
try {
|
||||
const data: any = await api.settings(guildId);
|
||||
const cfg = data?.settings?.loggingConfig || data?.loggingConfig || {};
|
||||
container.innerHTML = `
|
||||
<h3 class="label">Logging</h3>
|
||||
<p class="muted">Channel: ${cfg.logChannelId || '-'}</p>
|
||||
<p class="muted">Join/Leave: ${cfg.categories?.joinLeave ? 'an' : 'aus'}</p>
|
||||
<p class="muted">System: ${cfg.categories?.system ? 'an' : 'aus'}</p>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Logging konnte nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden von Logging', true);
|
||||
}
|
||||
}
|
||||
33
public/ts/components/modules/reactionRoles.ts
Normal file
33
public/ts/components/modules/reactionRoles.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderReactionRolesModule(guildId: string) {
|
||||
const container = document.getElementById('module-reactionroles');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Reaction Roles...</p>';
|
||||
try {
|
||||
const data: any = await api.reactionRoles(guildId);
|
||||
const entries = data?.entries || data?.reactionRoles || [];
|
||||
container.innerHTML = '<h3 class="label">Reaction Roles</h3>';
|
||||
if (!entries.length) {
|
||||
container.innerHTML += '<div class="empty-state">Keine Reaction Roles.</div>';
|
||||
return;
|
||||
}
|
||||
const list = document.createElement('div');
|
||||
list.className = 'ticket-list';
|
||||
entries.slice(0, 3).forEach((e: any) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'ticket-item';
|
||||
item.innerHTML = `
|
||||
<div style="font-weight:750;">${e.title || e.messageId || 'Eintrag'}</div>
|
||||
<div class="muted">${e.channelId || ''}</div>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
container.appendChild(list);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Reaction Roles konnten nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Reaction Roles', true);
|
||||
}
|
||||
}
|
||||
23
public/ts/components/modules/serverstats.ts
Normal file
23
public/ts/components/modules/serverstats.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderServerStatsModule(guildId: string) {
|
||||
const container = document.getElementById('module-serverstats');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Server Stats...</p>';
|
||||
try {
|
||||
const data: any = await api.serverStats(guildId);
|
||||
const cfg = data?.config || data || {};
|
||||
const items = cfg.items || [];
|
||||
container.innerHTML = `
|
||||
<h3 class="label">Server Stats</h3>
|
||||
<p class="muted">Kategorie: ${cfg.categoryId || '-'}</p>
|
||||
<p class="muted">Refresh: ${cfg.refresh || '-'}m</p>
|
||||
<p class="muted">Items: ${items.length}</p>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Server Stats konnten nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Server Stats', true);
|
||||
}
|
||||
}
|
||||
23
public/ts/components/modules/statuspage.ts
Normal file
23
public/ts/components/modules/statuspage.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderStatuspageModule(guildId: string) {
|
||||
const container = document.getElementById('module-statuspage');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Statuspage...</p>';
|
||||
try {
|
||||
const data: any = await api.statuspage(guildId);
|
||||
const cfg = data?.config || data || {};
|
||||
const services = cfg.services || [];
|
||||
container.innerHTML = `
|
||||
<h3 class="label">Statuspage</h3>
|
||||
<p class="muted">Channel: ${cfg.channelId || '-'}</p>
|
||||
<p class="muted">Intervall: ${cfg.interval || '-'}m</p>
|
||||
<p class="muted">Services: ${services.length}</p>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Statuspage konnte nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Statuspage', true);
|
||||
}
|
||||
}
|
||||
22
public/ts/components/modules/welcome.ts
Normal file
22
public/ts/components/modules/welcome.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderWelcomeModule(guildId: string) {
|
||||
const container = document.getElementById('module-welcome');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Welcome...</p>';
|
||||
try {
|
||||
const data: any = await api.settings(guildId);
|
||||
const cfg = data?.settings?.welcomeConfig || data?.welcomeConfig || {};
|
||||
container.innerHTML = `
|
||||
<h3 class="label">Welcome</h3>
|
||||
<p class="muted">Channel: ${cfg.channelId || '-'}</p>
|
||||
<p class="muted">Embed Titel: ${cfg.embedTitle || '-'}</p>
|
||||
<p class="muted">Status: ${data?.settings?.welcomeEnabled ? 'aktiv' : 'inaktiv'}</p>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Welcome konnte nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden von Welcome', true);
|
||||
}
|
||||
}
|
||||
33
public/ts/components/overview.ts
Normal file
33
public/ts/components/overview.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { api } from '../services/api.js';
|
||||
import { showToast } from '../ui/toast.js';
|
||||
|
||||
export async function renderOverview(guildId: string) {
|
||||
const section = document.getElementById('section-overview');
|
||||
if (!section) return;
|
||||
section.innerHTML = '<p class="muted">Lade Uebersicht...</p>';
|
||||
try {
|
||||
const data: any = await api.overview(guildId);
|
||||
const stats = data?.stats || {};
|
||||
section.innerHTML = `
|
||||
<h2 class="section-title">Uebersicht</h2>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<p class="label">Tickets offen</p>
|
||||
<p class="stat">${stats.openTickets ?? '-'}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="label">Module aktiv</p>
|
||||
<p class="stat">${stats.activeModules ?? '-'}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="label">Events geplant</p>
|
||||
<p class="stat">${stats.events ?? '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
section.innerHTML = '<div class="empty-state">Uebersicht konnte nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Uebersicht', true);
|
||||
}
|
||||
}
|
||||
22
public/ts/components/settings.ts
Normal file
22
public/ts/components/settings.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from '../services/api.js';
|
||||
import { showToast } from '../ui/toast.js';
|
||||
|
||||
export async function renderSettingsSection(guildId: string) {
|
||||
const section = document.getElementById('section-settings');
|
||||
if (!section) return;
|
||||
section.innerHTML = '<p class="muted">Lade Einstellungen...</p>';
|
||||
try {
|
||||
const data: any = await api.settings(guildId);
|
||||
const settings = data?.settings || {};
|
||||
section.innerHTML = `
|
||||
<h2 class="section-title">Einstellungen</h2>
|
||||
<div class="card">
|
||||
<pre style="white-space:pre-wrap;">${JSON.stringify(settings, null, 2)}</pre>
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
section.innerHTML = '<div class="empty-state">Einstellungen konnten nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Einstellungen', true);
|
||||
}
|
||||
}
|
||||
38
public/ts/components/tickets/automations.ts
Normal file
38
public/ts/components/tickets/automations.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderAutomations(guildId: string) {
|
||||
const container = document.getElementById('tickets-automations');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Automationen...</p>';
|
||||
try {
|
||||
const data: any = await api.automations(guildId);
|
||||
const rules = data?.rules || data || [];
|
||||
if (!rules.length) {
|
||||
container.innerHTML = '<div class="empty-state">Keine Regeln angelegt.</div>';
|
||||
return;
|
||||
}
|
||||
const list = document.createElement('div');
|
||||
list.className = 'ticket-list';
|
||||
rules.forEach((r: any) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'ticket-item';
|
||||
item.innerHTML = `
|
||||
<div class="row" style="justify-content:space-between;">
|
||||
<div>
|
||||
<div style="font-weight:750;">${r.name || 'Regel'}</div>
|
||||
<div class="muted">${r.condition?.type || r.condition?.status || ''}</div>
|
||||
</div>
|
||||
<span class="pill">${r.active ? 'aktiv' : 'inaktiv'}</span>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
container.innerHTML = '<h3 class="label">Automationen</h3>';
|
||||
container.appendChild(list);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Automationen konnten nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Automationen', true);
|
||||
}
|
||||
}
|
||||
29
public/ts/components/tickets/index.ts
Normal file
29
public/ts/components/tickets/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { renderTicketList } from './list.js';
|
||||
import { renderPipeline } from './pipeline.js';
|
||||
import { renderSla } from './sla.js';
|
||||
import { renderAutomations } from './automations.js';
|
||||
import { renderKb } from './kb.js';
|
||||
|
||||
export async function initTicketsSection(guildId: string) {
|
||||
const section = document.getElementById('section-tickets');
|
||||
if (!section) return;
|
||||
section.innerHTML = `
|
||||
<h2 class="section-title">Tickets</h2>
|
||||
<div class="tickets-grid">
|
||||
<div class="card" id="tickets-list"></div>
|
||||
<div class="card" id="tickets-pipeline"></div>
|
||||
<div class="card" id="tickets-sla"></div>
|
||||
</div>
|
||||
<div class="grid" style="margin-top:16px;">
|
||||
<div class="card" id="tickets-automations"></div>
|
||||
<div class="card" id="tickets-kb"></div>
|
||||
</div>
|
||||
`;
|
||||
await Promise.all([
|
||||
renderTicketList(guildId),
|
||||
renderPipeline(guildId),
|
||||
renderSla(guildId),
|
||||
renderAutomations(guildId),
|
||||
renderKb(guildId)
|
||||
]);
|
||||
}
|
||||
33
public/ts/components/tickets/kb.ts
Normal file
33
public/ts/components/tickets/kb.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderKb(guildId: string) {
|
||||
const container = document.getElementById('tickets-kb');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Knowledge Base...</p>';
|
||||
try {
|
||||
const data: any = await api.kb(guildId);
|
||||
const entries = data?.articles || data?.kb || [];
|
||||
if (!entries.length) {
|
||||
container.innerHTML = '<div class="empty-state">Keine KB-Eintraege.</div>';
|
||||
return;
|
||||
}
|
||||
const list = document.createElement('div');
|
||||
list.className = 'ticket-list';
|
||||
entries.slice(0, 4).forEach((k: any) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'ticket-item';
|
||||
item.innerHTML = `
|
||||
<div style="font-weight:750;">${k.title || 'Artikel'}</div>
|
||||
<div class="muted">${k.keywords || ''}</div>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
container.innerHTML = '<h3 class="label">Knowledge Base</h3>';
|
||||
container.appendChild(list);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">KB konnte nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der KB', true);
|
||||
}
|
||||
}
|
||||
39
public/ts/components/tickets/list.ts
Normal file
39
public/ts/components/tickets/list.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderTicketList(guildId: string) {
|
||||
const container = document.getElementById('tickets-list');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Tickets...</p>';
|
||||
try {
|
||||
const data: any = await api.tickets(guildId);
|
||||
const tickets = data?.tickets || [];
|
||||
if (!tickets.length) {
|
||||
container.innerHTML = '<div class="empty-state">Keine Tickets</div>';
|
||||
return;
|
||||
}
|
||||
const list = document.createElement('div');
|
||||
list.className = 'ticket-list';
|
||||
tickets.slice(0, 5).forEach((t: any) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'ticket-item';
|
||||
item.innerHTML = `
|
||||
<div class="row" style="justify-content:space-between;">
|
||||
<div>
|
||||
<div style="font-weight:750;font-size:15px;">${t.title || t.id}</div>
|
||||
<div class="muted">${t.user || ''}</div>
|
||||
</div>
|
||||
<div class="ticket-status status-${t.status || 'open'}">${t.status || 'open'}</div>
|
||||
</div>
|
||||
<div class="muted">${t.description || ''}</div>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
});
|
||||
container.innerHTML = '<h3 class="label">Aktuelle Tickets</h3>';
|
||||
container.appendChild(list);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Tickets konnten nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Tickets', true);
|
||||
}
|
||||
}
|
||||
33
public/ts/components/tickets/pipeline.ts
Normal file
33
public/ts/components/tickets/pipeline.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderPipeline(guildId: string) {
|
||||
const container = document.getElementById('tickets-pipeline');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade Pipeline...</p>';
|
||||
try {
|
||||
const data: any = await api.pipeline(guildId);
|
||||
const lanes = data?.lanes || [];
|
||||
container.innerHTML = '<h3 class="label">Pipeline</h3>';
|
||||
if (!lanes.length) {
|
||||
container.innerHTML += '<div class="empty-state">Keine Pipeline-Daten</div>';
|
||||
return;
|
||||
}
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'grid';
|
||||
lanes.forEach((lane: any) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
card.innerHTML = `
|
||||
<p class="label">${lane.name || 'Lane'}</p>
|
||||
<p class="stat">${lane.count ?? 0}</p>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
container.appendChild(grid);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">Pipeline konnte nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der Pipeline', true);
|
||||
}
|
||||
}
|
||||
21
public/ts/components/tickets/sla.ts
Normal file
21
public/ts/components/tickets/sla.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { api } from '../../services/api.js';
|
||||
import { showToast } from '../../ui/toast.js';
|
||||
|
||||
export async function renderSla(guildId: string) {
|
||||
const container = document.getElementById('tickets-sla');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="muted">Lade SLA...</p>';
|
||||
try {
|
||||
const data: any = await api.sla(guildId);
|
||||
const stats = data?.stats || {};
|
||||
container.innerHTML = `
|
||||
<h3 class="label">SLA</h3>
|
||||
<p class="stat">${stats.averageResponse ?? '-'}m</p>
|
||||
<p class="muted">Durchschnittliche Antwortzeit</p>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
container.innerHTML = '<div class="empty-state">SLA konnte nicht geladen werden.</div>';
|
||||
showToast('Fehler beim Laden der SLA', true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user