diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 678c9c7..36be5f5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,1210 +1,60 @@ -import { useEffect, useState } from 'react'; -import { - Alert, Avatar, Button, Card, CardContent, CardHeader, Chip, - Input, Separator, Skeleton, Spinner, Switch, Tab, Tabs, - TextArea -} from '@heroui/react'; -import { - Activity, AudioLines, Bot, Cable, CalendarDays, ChevronRight, ClipboardList, - Command, Home, LifeBuoy, LogIn, Logs, MessageSquare, Music, Pencil, - Puzzle, RadioTower, ScanSearch, Settings, Shield, Sparkles, Tag, - Ticket, Trash2, UserRound, Users, Wrench -} from 'lucide-react'; -import { apiFetch } from './utils/api'; -import { formatDate, guildIconUrl } from './utils/formatters'; -import { navItems } from './utils/constants'; -import { Sidebar } from './components/layout/Sidebar'; -import { Topbar } from './components/layout/Topbar'; -import { SectionCard } from './components/shared/SectionCard'; -import { FormPanel } from './components/shared/FormPanel'; -import { ListPanel } from './components/shared/ListPanel'; -import { StatCard } from './components/shared/StatCard'; -import { ActivityTile } from './components/shared/ActivityTile'; -import { LoadingSkeleton } from './components/shared/LoadingSkeleton'; -import type { - AppConfig, User, Guild, NavKey, TicketRecord, StatusService, - EventItem, ReactionRoleSet, ModuleItem, LogEntry, SettingsState, - SupportLoginConfig, SupportLoginStatus, RegisterForm, RegisterFormField, - RegisterApplication, MusicSession -} from './types'; +import { useApp } from './context/AppContext'; +import { AppLayout } from './components/layout/AppLayout'; +import { GuildSelect } from './pages/GuildSelect'; +import { Dashboard } from './pages/Dashboard'; +import { Tickets } from './pages/Tickets'; +import { SupportLogin } from './pages/SupportLogin'; +import { Automod } from './pages/Automod'; +import { Welcome } from './pages/Welcome'; +import { DynamicVoice } from './pages/DynamicVoice'; +import { Birthday } from './pages/Birthday'; +import { ReactionRoles } from './pages/ReactionRoles'; +import { Statuspage } from './pages/Statuspage'; +import { ServerStats } from './pages/ServerStats'; +import { Register } from './pages/Register'; +import { MusicPage } from './pages/Music'; +import { SettingsPage } from './pages/Settings'; +import { ModulesPage } from './pages/Modules'; +import { Events } from './pages/Events'; +import { Admin } from './pages/Admin'; -const appConfig: AppConfig = (window as any).__PAPO__ || {}; +function AppContent() { + const { guilds, currentGuildId, section } = useApp(); + + if (!guilds.length) { + return ; + } + + if (!currentGuildId) { + return ; + } + + switch (section) { + case 'overview': return ; + case 'tickets': return ; + case 'supportlogin': return ; + case 'automod': return ; + case 'welcome': return ; + case 'dynamicvoice': return ; + case 'birthday': return ; + case 'reactionroles': return ; + case 'statuspage': return ; + case 'serverstats': return ; + case 'register': return ; + case 'music': return ; + case 'settings': return ; + case 'modules': return ; + case 'events': return ; + case 'admin': return ; + default: return ; + } +} function App() { - const [loading, setLoading] = useState(true); - const [user, setUser] = useState(null); - const [guilds, setGuilds] = useState([]); - const [currentGuildId, setCurrentGuildId] = useState(appConfig.initialGuildId || ''); - const [section, setSection] = useState('overview'); - const [guildInfo, setGuildInfo] = useState(null); - const [overview, setOverview] = useState(null); - const [activity, setActivity] = useState(null); - const [logs, setLogs] = useState([]); - const [tickets, setTickets] = useState([]); - const [pipeline, setPipeline] = useState>({}); - const [sla, setSla] = useState({ supporters: [], days: [] }); - const [automations, setAutomations] = useState([]); - const [kbArticles, setKbArticles] = useState([]); - const [settings, setSettings] = useState({}); - const [modules, setModules] = useState([]); - const [birthday, setBirthday] = useState({ config: {}, birthdays: [] }); - const [reactionRoles, setReactionRoles] = useState([]); - const [statuspage, setStatuspage] = useState({ services: [] }); - const [serverStats, setServerStats] = useState({ items: [] }); - const [events, setEvents] = useState([]); - const [admin, setAdmin] = useState({ overview: null, activity: null, logs: [] }); - const [statusMessage, setStatusMessage] = useState(''); - const [ticketTab, setTicketTab] = useState('overview'); - const [automationDraft, setAutomationDraft] = useState({ name: '', conditionValue: '', actionValue: '' }); - const [kbDraft, setKbDraft] = useState({ title: '', keywords: '', content: '' }); - const [eventDraft, setEventDraft] = useState({ title: '', description: '', channelId: '', startsAt: '' }); - const [statusDraft, setStatusDraft] = useState(null); - const [statsDraft, setStatsDraft] = useState(null); - const [reactionDraft, setReactionDraft] = useState({ title: '', channelId: '', entries: '' }); - - const [supportLogin, setSupportLogin] = useState<{ config: SupportLoginConfig; status: SupportLoginStatus; supportRoleId?: string } | null>(null); - const [registerForms, setRegisterForms] = useState([]); - const [registerApps, setRegisterApps] = useState([]); - const [formDraft, setFormDraft] = useState({ name: '', description: '', reviewChannelId: '', notifyRoleIds: '', fields: '' }); - const [editingFormId, setEditingFormId] = useState(null); - const [registerTab, setRegisterTab] = useState('forms'); - const [musicStatus, setMusicStatus] = useState<{ activeGuilds: number; sessions: MusicSession[] }>({ activeGuilds: 0, sessions: [] }); - const [kbEditDraft, setKbEditDraft] = useState<{ id: string; title: string; keywords: string; content: string } | null>(null); - const [automationEditDraft, setAutomationEditDraft] = useState<{ id: string; name: string; conditionValue: string; actionValue: string } | null>(null); - const [statusServiceDraft, setStatusServiceDraft] = useState<{ id?: string; name: string; url: string; status: string }>({ name: '', url: '', status: 'unknown' }); - const [statsItemDraft, setStatsItemDraft] = useState<{ id?: string; label: string; type: string }>({ label: '', type: 'members' }); - const [ticketDetail, setTicketDetail] = useState(null); - const [ticketMessages, setTicketMessages] = useState([]); - - useEffect(() => { - const hash = window.location.hash.replace('#', '') as NavKey; - if (navItems.some((item) => item.key === hash)) setSection(hash); - }, []); - - useEffect(() => { window.location.hash = section; }, [section]); - useEffect(() => { void bootstrap(); }, []); - useEffect(() => { if (currentGuildId) void loadGuildData(currentGuildId); }, [currentGuildId]); - - async function bootstrap() { - try { - const me = await apiFetch<{ user: User }>('/me'); - const guildRes = await apiFetch<{ guilds: Guild[] }>('/guilds'); - setUser(me.user); - setGuilds(guildRes.guilds || []); - if (!currentGuildId && guildRes.guilds?.length) setCurrentGuildId(guildRes.guilds[0].id); - } finally { setLoading(false); } - } - - async function loadGuildData(guildId: string) { - setStatusMessage('Lade Daten...'); - try { - const [guildInfoRes, overviewRes, activityRes, logsRes, settingsRes, modulesRes, - birthdayRes, reactionRes, statusRes, statsRes, eventsRes, supportLoginRes, - registerFormsRes, registerAppsRes] = await Promise.all([ - apiFetch(`/guild/info?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/overview?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/guild/activity?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/guild/logs?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/settings?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/modules?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/birthday?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/reactionroles?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/statuspage?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/server-stats?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/events?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/tickets/support-login?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/register/forms?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/register/apps?guildId=${encodeURIComponent(guildId)}`) - ]); - setGuildInfo(guildInfoRes.guild || null); - setOverview(overviewRes); - setMusicStatus(overviewRes.music || { activeGuilds: 0, sessions: [] }); - setActivity(activityRes.activity || {}); - setLogs(logsRes.logs || []); - setSettings(settingsRes.settings || {}); - setModules(modulesRes.modules || []); - setBirthday(birthdayRes); - setReactionRoles(reactionRes.sets || []); - setStatuspage(statusRes.config || { services: [] }); - setServerStats(statsRes.config || { items: [] }); - setStatsDraft(statsRes.config || { items: [] }); - setStatusDraft(statusRes.config || { services: [] }); - setEvents(eventsRes.events || []); - setSupportLogin(supportLoginRes); - setRegisterForms(registerFormsRes.forms || []); - setRegisterApps(registerAppsRes.applications || []); - setReactionDraft({ title: '', channelId: '', entries: '' }); - await Promise.all([loadTicketData(guildId), loadAdminData()]); - setStatusMessage(''); - } catch { setStatusMessage('Daten konnten nicht geladen werden'); } - } - - async function loadTicketData(guildId: string) { - const [ticketRes, pipelineRes, slaRes, automationRes, kbRes] = await Promise.all([ - apiFetch(`/tickets?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/tickets/pipeline?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/tickets/sla?guildId=${encodeURIComponent(guildId)}&range=30`), - apiFetch(`/automations?guildId=${encodeURIComponent(guildId)}`), - apiFetch(`/kb?guildId=${encodeURIComponent(guildId)}`) - ]); - setTickets(ticketRes.tickets || []); - setPipeline(pipelineRes.pipeline || {}); - setSla(slaRes || { supporters: [], days: [] }); - setAutomations(automationRes.rules || []); - setKbArticles(kbRes.articles || []); - } - - async function loadAdminData() { - if (!user?.isAdmin) return; - const [overviewRes, , logsRes] = await Promise.all([ - apiFetch('/admin/overview'), - apiFetch('/admin/activity'), - apiFetch('/admin/logs') - ]); - setAdmin({ overview: overviewRes, activity: null, logs: logsRes.logs || [] }); - } - - async function saveSettingsPayload(payload: Record, okMessage: string) { - if (!currentGuildId) return; - await apiFetch('/settings', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, ...payload }) }); - setStatusMessage(okMessage); - await loadGuildData(currentGuildId); - } - - async function saveBirthday() { - await apiFetch('/birthday', { - method: 'POST', - body: JSON.stringify({ - guildId: currentGuildId, - enabled: birthday.config?.enabled ?? true, - channelId: birthday.config?.channelId || '', - sendHour: birthday.config?.sendHour || 9, - messageTemplate: birthday.config?.messageTemplate || '' - }) - }); - setStatusMessage('Birthday gespeichert'); - await loadGuildData(currentGuildId); - } - - async function saveStatuspage() { - await apiFetch('/statuspage', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, config: statusDraft }) }); - setStatusMessage('Statuspage gespeichert'); - await loadGuildData(currentGuildId); - } - - async function saveServerStats() { - await apiFetch('/server-stats', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, config: statsDraft }) }); - setStatusMessage('Server Stats gespeichert'); - await loadGuildData(currentGuildId); - } - - async function saveEvent() { - if (!eventDraft.title) return; - await apiFetch('/events', { - method: 'POST', - body: JSON.stringify({ - guildId: currentGuildId, title: eventDraft.title, - description: eventDraft.description, channelId: eventDraft.channelId || undefined, - startsAt: eventDraft.startsAt || undefined - }) - }); - setEventDraft({ title: '', description: '', channelId: '', startsAt: '' }); - await loadGuildData(currentGuildId); - setStatusMessage('Event gespeichert'); - } - - async function deleteEvent(id: string) { - await apiFetch(`/events/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); - await loadGuildData(currentGuildId); - } - - async function saveReactionRole() { - const entries = reactionDraft.entries.split('\n').map((l) => l.trim()).filter(Boolean) - .map((line) => { const p = line.split('|').map((s) => s.trim()); return { emoji: p[0], roleId: p[1], label: p[2], description: p[3] }; }) - .filter((e) => e.emoji && e.roleId); - await apiFetch('/reactionroles', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, channelId: reactionDraft.channelId, title: reactionDraft.title, entries }) }); - await loadGuildData(currentGuildId); - setStatusMessage('Reaction Role gespeichert'); - } - - async function toggleModule(key: string, enabled: boolean) { - await saveSettingsPayload({ [key]: enabled }, `${key} aktualisiert`); - } - - async function saveAutomation() { - await apiFetch('/automations', { - method: 'POST', - body: JSON.stringify({ - guildId: currentGuildId, name: automationDraft.name || 'Automation', - condition: { category: automationDraft.conditionValue }, - action: { type: 'reminder', message: automationDraft.actionValue || 'Reminder' }, active: true - }) - }); - setAutomationDraft({ name: '', conditionValue: '', actionValue: '' }); - await loadTicketData(currentGuildId); - } - - async function saveKbArticle() { - await apiFetch('/kb', { - method: 'POST', - body: JSON.stringify({ guildId: currentGuildId, title: kbDraft.title || 'Artikel', keywords: kbDraft.keywords, content: kbDraft.content }) - }); - setKbDraft({ title: '', keywords: '', content: '' }); - await loadTicketData(currentGuildId); - } - - async function updateKbArticle(id: string) { - if (!kbEditDraft) return; - await apiFetch(`/kb/${id}`, { - method: 'PUT', - body: JSON.stringify({ guildId: currentGuildId, title: kbEditDraft.title, keywords: kbEditDraft.keywords, content: kbEditDraft.content }) - }); - setKbEditDraft(null); - setStatusMessage('KB-Artikel aktualisiert'); - await loadTicketData(currentGuildId); - } - - async function deleteKbArticle(id: string) { - await apiFetch(`/kb/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); - setStatusMessage('KB-Artikel gelöscht'); - await loadTicketData(currentGuildId); - } - - async function updateAutomation(id: string) { - if (!automationEditDraft) return; - await apiFetch(`/automations/${id}`, { - method: 'PUT', - body: JSON.stringify({ guildId: currentGuildId, name: automationEditDraft.name, condition: { category: automationEditDraft.conditionValue }, action: { type: 'reminder', message: automationEditDraft.actionValue }, active: true }) - }); - setAutomationEditDraft(null); - setStatusMessage('Automation aktualisiert'); - await loadTicketData(currentGuildId); - } - - async function deleteAutomation(id: string) { - await apiFetch(`/automations/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); - setStatusMessage('Automation gelöscht'); - await loadTicketData(currentGuildId); - } - - async function saveSupportLogin() { - if (!supportLogin) return; - await apiFetch('/tickets/support-login', { - method: 'POST', - body: JSON.stringify({ guildId: currentGuildId, ...supportLogin.config }) - }); - setStatusMessage('Support Login gespeichert'); - await loadGuildData(currentGuildId); - } - - async function saveForm() { - const fields: RegisterFormField[] = formDraft.fields.split('\n').filter(Boolean).map((line) => { - const parts = line.split('|').map((s) => s.trim()); - return { label: parts[0] || 'Feld', type: (parts[1] || 'text') as any, required: parts[2] === 'required', options: parts[3] ? parts[3].split(',').map((s) => s.trim()) : undefined }; - }); - const body: any = { guildId: currentGuildId, name: formDraft.name, description: formDraft.description, reviewChannelId: formDraft.reviewChannelId || undefined, notifyRoleIds: formDraft.notifyRoleIds.split(',').map((s) => s.trim()).filter(Boolean), fields, isActive: true }; - if (editingFormId) { - await apiFetch(`/register/forms/${editingFormId}`, { method: 'PUT', body: JSON.stringify(body) }); - setStatusMessage('Formular aktualisiert'); - } else { - await apiFetch('/register/forms', { method: 'POST', body: JSON.stringify(body) }); - setStatusMessage('Formular erstellt'); - } - setFormDraft({ name: '', description: '', reviewChannelId: '', notifyRoleIds: '', fields: '' }); - setEditingFormId(null); - await loadGuildData(currentGuildId); - } - - async function deleteForm(id: string) { - await apiFetch(`/register/forms/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); - setStatusMessage('Formular gelöscht'); - await loadGuildData(currentGuildId); - } - - async function sendFormPanel(formId: string) { - if (!supportLogin?.config?.panelChannelId) { setStatusMessage('Bitte zuerst Support Login konfigurieren'); return; } - await apiFetch(`/register/forms/${formId}/panel`, { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, channelId: supportLogin.config.panelChannelId }) }); - setStatusMessage('Panel gesendet'); - } - - async function addStatusService() { - if (!statusServiceDraft.name) return; - await apiFetch('/statuspage/service', { - method: 'POST', - body: JSON.stringify({ guildId: currentGuildId, name: statusServiceDraft.name, url: statusServiceDraft.url, status: statusServiceDraft.status }) - }); - setStatusServiceDraft({ name: '', url: '', status: 'unknown' }); - setStatusMessage('Service hinzugefügt'); - await loadGuildData(currentGuildId); - } - - async function deleteStatusService(id: string) { - await apiFetch(`/statuspage/service/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); - setStatusMessage('Service entfernt'); - await loadGuildData(currentGuildId); - } - - async function addStatsItem() { - if (!statsItemDraft.label) return; - const draft = statsDraft || { enabled: true, categoryName: '', refreshMinutes: 10, items: [] }; - const items = [...(draft.items || []), { key: statsItemDraft.label.toLowerCase().replace(/\s+/g, '_'), label: statsItemDraft.label, type: statsItemDraft.type }]; - const updated = { ...draft, items }; - setStatsDraft(updated); - await apiFetch('/server-stats', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, config: updated }) }); - setStatsItemDraft({ label: '', type: 'members' }); - setStatusMessage('Stat-Item hinzugefügt'); - await loadGuildData(currentGuildId); - } - - async function deleteStatsItem(index: number) { - const draft = statsDraft || { enabled: true, categoryName: '', refreshMinutes: 10, items: [] }; - const items = (draft.items || []).filter((_: any, i: number) => i !== index); - const updated = { ...draft, items }; - setStatsDraft(updated); - await apiFetch('/server-stats', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, config: updated }) }); - setStatusMessage('Stat-Item entfernt'); - await loadGuildData(currentGuildId); - } - - async function loadTicketMessages(ticketId: string) { - const res = await apiFetch(`/tickets/${ticketId}/messages`); - setTicketMessages(res.messages || []); - } - - async function updateTicketStatus(ticketId: string, status: string) { - await apiFetch(`/tickets/${ticketId}/status`, { method: 'POST', body: JSON.stringify({ status }) }); - setStatusMessage('Status aktualisiert'); - await loadGuildData(currentGuildId); - } - - async function closeTicket(ticketId: string) { - await apiFetch(`/tickets/${ticketId}/close`, { method: 'POST', body: JSON.stringify({ guildId: currentGuildId }) }); - setStatusMessage('Ticket geschlossen'); - await loadGuildData(currentGuildId); - } - - const selectedGuild = guilds.find((g) => g.id === currentGuildId) || null; - const moduleFlags = guildInfo?.modules || {}; - - function handleLogout() { - window.location.href = `${appConfig.baseAuth || '/auth'}/logout`; - } - - if (loading) { - return ( -
- -
- ); - } - - if (!selectedGuild) { - return ( -
-

Wähle einen Server

-

Wähle einen Discord-Server aus, um das Dashboard zu öffnen.

-
- {guilds.map((guild) => ( - setCurrentGuildId(guild.id)}> - - -
-
{guild.name}
-
ID: {guild.id}
-
-
-
- ))} -
-
- ); - } - return ( -
-
- -
- -
-
- -
- -
-
- - {section === 'overview' && ( -
- - -
- -
-
{guildInfo?.name || selectedGuild.name}
-
ID: {guildInfo?.id || selectedGuild.id}
-
- {Object.entries(moduleFlags).map(([key, enabled]) => ( - - {key.replace('Enabled', '').toUpperCase()} - - ))} -
-
-
- } variant="dot"> - Bot aktiv - -
-
- -
- - -

Guild Infos

-

Wichtige Daten auf einen Blick

-
- -
- } label="Owner" value={guildInfo?.owner?.tag || '-'} /> - } label="Erstellt" value={formatDate(guildInfo?.createdAt)} /> - } label="Member" value={String(guildInfo?.memberCount ?? 0)} /> - } label="Channels" value={`${guildInfo?.textCount || 0} / ${guildInfo?.voiceCount || 0}`} /> - } label="Offene Tickets" value={String(overview?.tickets?.open ?? 0)} /> - } label="IP / Closed" value={`${overview?.tickets?.inProgress ?? 0} / ${overview?.tickets?.closed ?? 0}`} /> -
- -
-
- - - -

Activity

-

Live-Statistiken aus deinem Bot

-
- - } label="Messages (24h)" value={activity?.messages24h ?? 0} /> - } label="Commands (24h)" value={activity?.commands24h ?? 0} /> - } label="Automod (24h)" value={activity?.automod24h ?? 0} /> - - -
- - - -
-

Guild Logs

-

Neueste Ereignisse

-
- -
- -
- {logs.length ? logs.map((log, i) => ( - - -
- - {(log.level || 'info').toUpperCase()} - - {formatDate(log.timestamp)} -
-
{log.category ? `[${log.category}] ` : ''}{log.message || '-'}
-
-
- )) : ( -

Keine Logs

- )} -
-
-
-
- - -
- {navItems.filter((item) => !['overview', 'admin'].includes(item.key)).slice(0, 6).map((item) => ( - setSection(item.key)}> - -
{item.icon}
-
{item.label}
-
Modul verwalten
-
-
- ))} -
-
-
- )} - - {section === 'tickets' && ( - - setTicketTab(String(key))}> - - - - - - - - {ticketTab === 'overview' && ( -
- - {tickets.length ? tickets.map((t, i) => ( - - -
-
{t.topic || 'Ticket'}
- - {t.status || 'open'} - -
-
- {t.category || 'Allgemein'} · {formatDate(t.createdAt)} - {t.claimedById ? ` · Claimed` : ''} -
-
- - - -
-
-
- )) :

Keine Tickets

} -
- - {tickets.length ? tickets.map((t, i) => ( -
{t.category || 'Allgemein'} · {formatDate(t.createdAt)}
- )) :

Keine Daten

} -
- {ticketDetail && ( - - -

{ticketDetail.topic || 'Ticket-Details'}

- -
- -
-
Status: {ticketDetail.status}
-
Priorität: {ticketDetail.priority || 'normal'}
-
Kategorie: {ticketDetail.category || '-'}
-
- -

Nachrichten ({ticketMessages.length})

-
- {ticketMessages.length ? ticketMessages.map((msg, i) => ( -
- {msg.author?.username || msg.authorId}: - {msg.content || '(Embed)'} -
- )) :

Keine Nachrichten geladen

} -
-
-
- )} -
- )} - - {ticketTab === 'pipeline' && ( -
- {['neu', 'in_bearbeitung', 'warten_auf_user', 'erledigt'].map((state) => ( - - {(pipeline[state] || []).length ? (pipeline[state] || []).map((t, i) => ( - - -
{t.topic || t.id}
-
- -
-
-
- )) :

Keine Tickets

} -
- ))} -
- )} - - {ticketTab === 'sla' && ( -
- - {(sla.supporters || []).length ? sla.supporters.map((row: any, i: number) => ( -
- {row.supporter || '-'} · {row.tickets || 0} Tickets · {row.ttc || '-'} TTC · {row.ttfr || '-'} TTFR -
- )) :

Keine Daten

} -
- - {(sla.days || []).length ? sla.days.map((row: any, i: number) => ( -
- {row.date || '-'} · {row.tickets || 0} Tickets · {row.ttc || '-'} TTC · {row.ttfr || '-'} TTFR -
- )) :

Keine Daten

} -
-
- )} - - {ticketTab === 'automations' && ( -
- - {automations.length ? automations.map((rule, i) => ( - - -
-
{rule.name || 'Automation'}
- - {rule.active !== false ? 'Aktiv' : 'Inaktiv'} - -
-
- - -
-
-
- )) :

Keine Regeln

} -
- {automationEditDraft ? ( - - setAutomationEditDraft((s) => ({ ...s, name: v }))} /> - setAutomationEditDraft((s) => ({ ...s, conditionValue: v }))} /> -