refactor frontend forms for updated HeroUI inputs
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled
This commit is contained in:
@@ -79,10 +79,10 @@ export function Header() {
|
||||
}
|
||||
|
||||
function useHeaderData() {
|
||||
const { guildInfo, guilds, currentGuildId } = useApp();
|
||||
const { guildInfo, guilds, currentGuildId, statusMessage } = useApp();
|
||||
const selectedGuild = guilds.find((g) => g.id === currentGuildId);
|
||||
return {
|
||||
guildName: guildInfo?.name || selectedGuild?.name || 'Dashboard',
|
||||
statusMessage: null,
|
||||
statusMessage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardHeader, CardContent } from '@heroui/react';
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription } from '@heroui/react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type Props = {
|
||||
@@ -12,9 +12,9 @@ export function SectionCard({ title, subtitle, children, action }: Props) {
|
||||
return (
|
||||
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
|
||||
<CardHeader className="flex items-start justify-between gap-4 px-6 pt-6 pb-0">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-xl font-bold tracking-tight">{title}</h2>
|
||||
{subtitle && <p className="mt-1 text-small text-default-500">{subtitle}</p>}
|
||||
<div className="min-w-0 flex flex-col gap-1">
|
||||
<CardTitle>{title}</CardTitle>
|
||||
{subtitle && <CardDescription>{subtitle}</CardDescription>}
|
||||
</div>
|
||||
{action && <div className="shrink-0">{action}</div>}
|
||||
</CardHeader>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Shield, Filter, Link, Ban, AlertTriangle, Save } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -14,37 +14,41 @@ export function Automod() {
|
||||
<h3 className="text-base font-semibold">Filter konfigurieren</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Switch isSelected={settings.automodEnabled !== false} onValueChange={(v) => setSettings((s) => ({ ...s, automodEnabled: v }))}>
|
||||
<Switch isSelected={settings.automodEnabled !== false} onChange={(v) => setSettings((s) => ({ ...s, automodEnabled: v }))}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={16} /> Automod aktiv
|
||||
</div>
|
||||
</Switch>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Switch isSelected={settings.automodConfig?.badWordFilter ?? false} onValueChange={(v) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), badWordFilter: v } }))}>
|
||||
<Switch isSelected={settings.automodConfig?.badWordFilter ?? false} onChange={(v) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), badWordFilter: v } }))}>
|
||||
<div className="flex items-center gap-2"><Ban size={14} /> Bad-Word-Filter</div>
|
||||
</Switch>
|
||||
<Switch isSelected={settings.automodConfig?.linkFilter ?? false} onValueChange={(v) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), linkFilter: v } }))}>
|
||||
<Switch isSelected={settings.automodConfig?.linkFilter ?? false} onChange={(v) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), linkFilter: v } }))}>
|
||||
<div className="flex items-center gap-2"><Link size={14} /> Link-Filter</div>
|
||||
</Switch>
|
||||
<Switch isSelected={settings.automodConfig?.spamFilter ?? false} onValueChange={(v) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), spamFilter: v } }))}>
|
||||
<Switch isSelected={settings.automodConfig?.spamFilter ?? false} onChange={(v) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), spamFilter: v } }))}>
|
||||
<div className="flex items-center gap-2"><AlertTriangle size={14} /> Spam-Filter</div>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Log Channel ID"
|
||||
placeholder="Channel ID f<>r Logs"
|
||||
value={settings.automodConfig?.logChannelId || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), logChannelId: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Log Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel ID f<>r Logs"
|
||||
value={settings.automodConfig?.logChannelId || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), logChannelId: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<TextArea
|
||||
label="Whitelist Links (Komma-getrennt)"
|
||||
value={(settings.automodConfig?.linkWhitelist || []).join(', ')}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), linkWhitelist: v.split(',').map((x) => x.trim()).filter(Boolean) } }))}
|
||||
placeholder="trusted-domain.com, another-safe.site"
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Whitelist Links (Komma-getrennt)</Label>
|
||||
<TextArea
|
||||
value={(settings.automodConfig?.linkWhitelist || []).join(', ')}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, automodConfig: { ...(s.automodConfig || {}), linkWhitelist: e.target.value.split(',').map((x) => x.trim()).filter(Boolean) } }))}
|
||||
placeholder="trusted-domain.com, another-safe.site"
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Separator />
|
||||
|
||||
@@ -64,13 +68,13 @@ export function Automod() {
|
||||
<p className="text-default-500">Die Automod-Einstellungen werden nach dem Speichern sofort aktiv.</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-default-100 bg-default-50/30 px-4 py-3 text-small">
|
||||
<p className="text-default-500">Bad-Word-Filter entfernt Nachrichten mit unerw<EFBFBD>nschten Begriffen.</p>
|
||||
<p className="text-default-500">Bad-Word-Filter entfernt Nachrichten mit unerw<EFBFBD>nschten Begriffen.</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-default-100 bg-default-50/30 px-4 py-3 text-small">
|
||||
<p className="text-default-500">Link-Filter blockiert bekannte sch<EFBFBD>dliche Domains und nicht-whitelistete Links.</p>
|
||||
<p className="text-default-500">Link-Filter blockiert bekannte sch<EFBFBD>dliche Domains und nicht-whitelistete Links.</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-default-100 bg-default-50/30 px-4 py-3 text-small">
|
||||
<p className="text-default-500">Spam-Filter erkennt und unterdr<EFBFBD>ckt Mehrfachnachrichten in kurzer Zeit.</p>
|
||||
<p className="text-default-500">Spam-Filter erkennt und unterdr<EFBFBD>ckt Mehrfachnachrichten in kurzer Zeit.</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||
import { CalendarDays, Save, Cake, Clock } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -7,40 +7,46 @@ export function Birthday() {
|
||||
const { birthday, setBirthday, saveBirthday } = useApp();
|
||||
|
||||
return (
|
||||
<SectionCard title="Birthday" subtitle="Geburtstags-Feature und gespeicherte Eintr<74>ge">
|
||||
<SectionCard title="Birthday" subtitle="Geburtstags-Feature und gespeicherte Eintr<74>ge">
|
||||
<div className="grid gap-5 xl:grid-cols-2">
|
||||
<Card className="border border-default-100 bg-default-50/20">
|
||||
<CardHeader className="px-5 pt-5 pb-0">
|
||||
<h3 className="text-base font-semibold">Konfiguration</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Switch isSelected={birthday.config?.enabled !== false} onValueChange={(v) => setBirthday((s) => ({ ...s, config: { ...s.config, enabled: v } }))}>
|
||||
<Switch isSelected={birthday.config?.enabled !== false} onChange={(v) => setBirthday((s) => ({ ...s, config: { ...s.config, enabled: v } }))}>
|
||||
<div className="flex items-center gap-2"><Cake size={16} /> Birthday aktiv</div>
|
||||
</Switch>
|
||||
|
||||
<Input
|
||||
label="Channel ID"
|
||||
placeholder="Channel f<>r Geburtstagsnachrichten"
|
||||
value={birthday.config?.channelId || ''}
|
||||
onValueChange={(v) => setBirthday((s) => ({ ...s, config: { ...s.config, channelId: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel f<>r Geburtstagsnachrichten"
|
||||
value={birthday.config?.channelId || ''}
|
||||
onChange={(e) => setBirthday((s) => ({ ...s, config: { ...s.config, channelId: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Sendezeit (Stunde)"
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
value={String(birthday.config?.sendHour ?? 9)}
|
||||
onValueChange={(v) => setBirthday((s) => ({ ...s, config: { ...s.config, sendHour: Number(v || 0) } }))}
|
||||
startContent={<Clock size={16} className="text-default-400" />}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Sendezeit (Stunde)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
value={String(birthday.config?.sendHour ?? 9)}
|
||||
onChange={(e) => setBirthday((s) => ({ ...s, config: { ...s.config, sendHour: Number(e.target.value || 0) } }))}
|
||||
startContent={<Clock size={16} className="text-default-400" />}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<TextArea
|
||||
label="Template"
|
||||
placeholder="Alles Gute zum Geburtstag, {user}!"
|
||||
value={birthday.config?.messageTemplate || ''}
|
||||
onValueChange={(v) => setBirthday((s) => ({ ...s, config: { ...s.config, messageTemplate: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Template</Label>
|
||||
<TextArea
|
||||
placeholder="Alles Gute zum Geburtstag, {user}!"
|
||||
value={birthday.config?.messageTemplate || ''}
|
||||
onChange={(e) => setBirthday((s) => ({ ...s, config: { ...s.config, messageTemplate: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Separator />
|
||||
|
||||
@@ -66,7 +72,7 @@ export function Birthday() {
|
||||
)) : (
|
||||
<div className="flex flex-col items-center gap-2 py-4 text-center text-tiny text-default-400">
|
||||
<CalendarDays size={20} />
|
||||
Keine Eintr<EFBFBD>ge
|
||||
Keine Eintr<EFBFBD>ge
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, Button, Chip, Switch, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||
import { AudioLines, Save, Mic, Users } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -14,30 +14,36 @@ export function DynamicVoice() {
|
||||
<h3 className="text-base font-semibold">Konfiguration</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Switch isSelected={settings.dynamicVoiceEnabled !== false} onValueChange={(v) => setSettings((s) => ({ ...s, dynamicVoiceEnabled: v }))}>
|
||||
<Switch isSelected={settings.dynamicVoiceEnabled !== false} onChange={(v) => setSettings((s) => ({ ...s, dynamicVoiceEnabled: v }))}>
|
||||
<div className="flex items-center gap-2"><AudioLines size={16} /> Dynamic Voice aktiv</div>
|
||||
</Switch>
|
||||
|
||||
<Input
|
||||
label="Lobby Channel ID"
|
||||
placeholder="Channel ID der Lobby"
|
||||
value={settings.dynamicVoiceConfig?.lobbyChannelId || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, dynamicVoiceConfig: { ...(s.dynamicVoiceConfig || {}), lobbyChannelId: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Lobby Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel ID der Lobby"
|
||||
value={settings.dynamicVoiceConfig?.lobbyChannelId || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, dynamicVoiceConfig: { ...(s.dynamicVoiceConfig || {}), lobbyChannelId: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Kategorie ID"
|
||||
placeholder="Kategorie f<>r neue Channels"
|
||||
value={settings.dynamicVoiceConfig?.categoryId || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, dynamicVoiceConfig: { ...(s.dynamicVoiceConfig || {}), categoryId: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Kategorie ID</Label>
|
||||
<Input
|
||||
placeholder="Kategorie f<>r neue Channels"
|
||||
value={settings.dynamicVoiceConfig?.categoryId || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, dynamicVoiceConfig: { ...(s.dynamicVoiceConfig || {}), categoryId: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Template"
|
||||
placeholder="Channel-Name Template"
|
||||
value={settings.dynamicVoiceConfig?.template || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, dynamicVoiceConfig: { ...(s.dynamicVoiceConfig || {}), template: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Template</Label>
|
||||
<Input
|
||||
placeholder="Channel-Name Template"
|
||||
value={settings.dynamicVoiceConfig?.template || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, dynamicVoiceConfig: { ...(s.dynamicVoiceConfig || {}), template: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Separator />
|
||||
|
||||
@@ -59,7 +65,7 @@ export function DynamicVoice() {
|
||||
</div>
|
||||
<div className="flex items-center gap-3 rounded-xl border border-default-100 bg-default-50/30 px-4 py-3 text-small">
|
||||
<Users size={16} className="text-success-400" />
|
||||
<span className="text-default-500">Channel-Owner k<EFBFBD>nnen Limits und Berechtigungen verwalten</span>
|
||||
<span className="text-default-500">Channel-Owner k<EFBFBD>nnen Limits und Berechtigungen verwalten</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Separator, TextField, Label } from '@heroui/react';
|
||||
import { CalendarDays, Trash2, Plus, Clock } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -22,7 +22,7 @@ export function Events() {
|
||||
<span className="font-semibold text-small truncate">{event.title}</span>
|
||||
</div>
|
||||
<Button color="danger" size="sm" variant="flat" startContent={<Trash2 size={14} />} onPress={() => deleteEvent(event.id)}>
|
||||
L<EFBFBD>schen
|
||||
L<EFBFBD>schen
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-small text-default-400">{event.description || 'Keine Beschreibung'}</p>
|
||||
@@ -46,34 +46,42 @@ export function Events() {
|
||||
<h3 className="text-base font-semibold">Neues Event</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Input
|
||||
label="Titel"
|
||||
placeholder="Event Name"
|
||||
value={eventDraft.title}
|
||||
onValueChange={(v) => setEventDraft((s) => ({ ...s, title: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Titel</Label>
|
||||
<Input
|
||||
placeholder="Event Name"
|
||||
value={eventDraft.title}
|
||||
onChange={(e) => setEventDraft((s) => ({ ...s, title: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<TextArea
|
||||
label="Beschreibung"
|
||||
placeholder="Event Beschreibung"
|
||||
value={eventDraft.description}
|
||||
onValueChange={(v) => setEventDraft((s) => ({ ...s, description: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Beschreibung</Label>
|
||||
<TextArea
|
||||
placeholder="Event Beschreibung"
|
||||
value={eventDraft.description}
|
||||
onChange={(e) => setEventDraft((s) => ({ ...s, description: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Channel ID"
|
||||
placeholder="Channel f<>r Erinnerungen"
|
||||
value={eventDraft.channelId}
|
||||
onValueChange={(v) => setEventDraft((s) => ({ ...s, channelId: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel f<>r Erinnerungen"
|
||||
value={eventDraft.channelId}
|
||||
onChange={(e) => setEventDraft((s) => ({ ...s, channelId: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Start (ISO)"
|
||||
type="datetime-local"
|
||||
placeholder="2024-12-24T18:00"
|
||||
value={eventDraft.startsAt}
|
||||
onValueChange={(v) => setEventDraft((s) => ({ ...s, startsAt: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Start (ISO)</Label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
placeholder="2024-12-24T18:00"
|
||||
value={eventDraft.startsAt}
|
||||
onChange={(e) => setEventDraft((s) => ({ ...s, startsAt: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Button color="primary" startContent={<Plus size={16} />} onPress={saveEvent}>
|
||||
Event speichern
|
||||
|
||||
@@ -28,7 +28,7 @@ export function ModulesPage() {
|
||||
<div className="text-small text-default-400 truncate">{module.description}</div>
|
||||
)}
|
||||
</div>
|
||||
<Switch isSelected={module.enabled} onValueChange={(v) => toggleModule(module.key, v)} />
|
||||
<Switch isSelected={module.enabled} onChange={(v) => toggleModule(module.key, v)} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
@@ -52,7 +52,7 @@ export function ModulesPage() {
|
||||
<div className="text-small text-default-400 truncate">{module.description}</div>
|
||||
)}
|
||||
</div>
|
||||
<Switch isSelected={module.enabled} onValueChange={(v) => toggleModule(module.key, v)} />
|
||||
<Switch isSelected={module.enabled} onChange={(v) => toggleModule(module.key, v)} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
@@ -63,7 +63,7 @@ export function ModulesPage() {
|
||||
{modules.length === 0 && (
|
||||
<div className="flex flex-col items-center gap-2 py-8 text-center text-small text-default-400">
|
||||
<Puzzle size={24} />
|
||||
Keine Module verf<EFBFBD>gbar
|
||||
Keine Module verf<EFBFBD>gbar
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Tag, Save, Hash, List } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -22,7 +22,7 @@ export function ReactionRoles() {
|
||||
<div className="font-semibold text-small truncate">{set.title || 'Reaction Role'}</div>
|
||||
<div className="text-tiny text-default-400 truncate">Channel: {set.channelId || '-'}</div>
|
||||
</div>
|
||||
<Chip size="sm" variant="flat">{(set.entries?.length || 0)} Eintr<EFBFBD>ge</Chip>
|
||||
<Chip size="sm" variant="flat">{(set.entries?.length || 0)} Eintr<EFBFBD>ge</Chip>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)) : (
|
||||
@@ -39,27 +39,31 @@ export function ReactionRoles() {
|
||||
<h3 className="text-base font-semibold">Neues Set</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Input
|
||||
label="Titel"
|
||||
placeholder="Rollenauswahl"
|
||||
value={reactionDraft.title}
|
||||
onValueChange={(v) => setReactionDraft((s) => ({ ...s, title: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Titel</Label>
|
||||
<Input
|
||||
placeholder="Rollenauswahl"
|
||||
value={reactionDraft.title}
|
||||
onChange={(e) => setReactionDraft((s) => ({ ...s, title: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Channel ID"
|
||||
placeholder="Channel f<>r die Nachricht"
|
||||
value={reactionDraft.channelId}
|
||||
onValueChange={(v) => setReactionDraft((s) => ({ ...s, channelId: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel f<>r die Nachricht"
|
||||
value={reactionDraft.channelId}
|
||||
onChange={(e) => setReactionDraft((s) => ({ ...s, channelId: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<div>
|
||||
<label className="block text-small font-medium mb-1">Eintr<EFBFBD>ge</label>
|
||||
<label className="block text-small font-medium mb-1">Eintr<EFBFBD>ge</label>
|
||||
<TextArea
|
||||
placeholder="Emoji | Role ID | Label :emoji: | 123456789 | Rolle 1 :wave: | 987654321 | Rolle 2"
|
||||
minRows={6}
|
||||
value={reactionDraft.entries}
|
||||
onValueChange={(v) => setReactionDraft((s) => ({ ...s, entries: v }))}
|
||||
onChange={(e) => setReactionDraft((s) => ({ ...s, entries: e.target.value }))}
|
||||
/>
|
||||
<p className="mt-1 text-tiny text-default-400">
|
||||
Pro Zeile: Emoji | Role ID | Label (optional) | Beschreibung (optional)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Tabs, Tab, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Tabs, Tab, Separator, TextField, Label } from '@heroui/react';
|
||||
import { ClipboardList, Pencil, Trash2, Send, Plus, FileText } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -12,10 +12,10 @@ export function Register() {
|
||||
} = useApp();
|
||||
|
||||
return (
|
||||
<SectionCard title="Registrierungsformulare" subtitle="Bewerbungs-Formulare und eingegangene Antr<74>ge verwalten.">
|
||||
<SectionCard title="Registrierungsformulare" subtitle="Bewerbungs-Formulare und eingegangene Antr<74>ge verwalten.">
|
||||
<Tabs aria-label="Register Tabs" color="primary" selectedKey={registerTab} variant="bordered" onSelectionChange={(key) => setRegisterTab(String(key))}>
|
||||
<Tab key="forms" title="Formulare" />
|
||||
<Tab key="apps" title="Antr<EFBFBD>ge" />
|
||||
<Tab key="forms">Formulare</Tab>
|
||||
<Tab key="apps">Anträge</Tab>
|
||||
</Tabs>
|
||||
|
||||
{registerTab === 'forms' && (
|
||||
@@ -76,11 +76,26 @@ export function Register() {
|
||||
<h3 className="text-base font-semibold">{editingFormId ? 'Formular bearbeiten' : 'Neues Formular'}</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Input label="Name" value={formDraft.name} onValueChange={(v) => setFormDraft((s) => ({ ...s, name: v }))} />
|
||||
<Input label="Beschreibung" value={formDraft.description} onValueChange={(v) => setFormDraft((s) => ({ ...s, description: v }))} />
|
||||
<Input label="Review Channel ID" value={formDraft.reviewChannelId} onValueChange={(v) => setFormDraft((s) => ({ ...s, reviewChannelId: v }))} />
|
||||
<Input label="Benachrichtigungs-Rollen (Komma-getrennt)" value={formDraft.notifyRoleIds} onValueChange={(v) => setFormDraft((s) => ({ ...s, notifyRoleIds: v }))} />
|
||||
<TextArea label="Felder (label|type|required|options)" minRows={6} value={formDraft.fields} onValueChange={(v) => setFormDraft((s) => ({ ...s, fields: v }))} />
|
||||
<TextField>
|
||||
<Label>Name</Label>
|
||||
<Input value={formDraft.name} onChange={(e) => setFormDraft((s) => ({ ...s, name: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Beschreibung</Label>
|
||||
<Input value={formDraft.description} onChange={(e) => setFormDraft((s) => ({ ...s, description: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Review Channel ID</Label>
|
||||
<Input value={formDraft.reviewChannelId} onChange={(e) => setFormDraft((s) => ({ ...s, reviewChannelId: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Benachrichtigungs-Rollen (Komma-getrennt)</Label>
|
||||
<Input value={formDraft.notifyRoleIds} onChange={(e) => setFormDraft((s) => ({ ...s, notifyRoleIds: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Felder (label|type|required|options)</Label>
|
||||
<TextArea minRows={6} value={formDraft.fields} onChange={(e) => setFormDraft((s) => ({ ...s, fields: e.target.value }))} />
|
||||
</TextField>
|
||||
<p className="text-tiny text-default-400">
|
||||
Pro Zeile: label | type (text/paragraph/select/multi) | required | option1,option2
|
||||
</p>
|
||||
@@ -99,7 +114,7 @@ export function Register() {
|
||||
|
||||
{registerTab === 'apps' && (
|
||||
<div className="mt-5">
|
||||
<h3 className="mb-3 text-base font-semibold">Eingegangene Antr<EFBFBD>ge ({registerApps.length})</h3>
|
||||
<h3 className="mb-3 text-base font-semibold">Eingegangene Antr<EFBFBD>ge ({registerApps.length})</h3>
|
||||
<div className="space-y-3">
|
||||
{registerApps.length ? registerApps.map((app) => (
|
||||
<Card key={app.id} className="border border-default-100 bg-default-50/20">
|
||||
@@ -126,7 +141,7 @@ export function Register() {
|
||||
)) : (
|
||||
<div className="flex flex-col items-center gap-2 py-8 text-center text-small text-default-400">
|
||||
<ClipboardList size={24} />
|
||||
Keine Antr<EFBFBD>ge
|
||||
Keine Antr<EFBFBD>ge
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, Button, Chip, Switch, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Activity, Save, Trash2, Plus, BarChart3 } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -16,23 +16,27 @@ export function ServerStats() {
|
||||
<h3 className="text-base font-semibold">Konfiguration</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Switch isSelected={statsDraft?.enabled === true} onValueChange={(v) => setStatsDraft((s) => ({ ...(s || {}), enabled: v }))}>
|
||||
<Switch isSelected={statsDraft?.enabled === true} onChange={(v) => setStatsDraft((s) => ({ ...(s || {}), enabled: v }))}>
|
||||
<div className="flex items-center gap-2"><BarChart3 size={16} /> Server Stats aktiv</div>
|
||||
</Switch>
|
||||
|
||||
<Input
|
||||
label="Kategorie-Name"
|
||||
placeholder="?? Server Stats"
|
||||
value={statsDraft?.categoryName || ''}
|
||||
onValueChange={(v) => setStatsDraft((s) => ({ ...(s || {}), categoryName: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Kategorie-Name</Label>
|
||||
<Input
|
||||
placeholder="?? Server Stats"
|
||||
value={statsDraft?.categoryName || ''}
|
||||
onChange={(e) => setStatsDraft((s) => ({ ...(s || {}), categoryName: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Refresh (Minuten)"
|
||||
type="number"
|
||||
value={String(statsDraft?.refreshMinutes || 10)}
|
||||
onValueChange={(v) => setStatsDraft((s) => ({ ...(s || {}), refreshMinutes: Number(v || 10) }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Refresh (Minuten)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={String(statsDraft?.refreshMinutes || 10)}
|
||||
onChange={(e) => setStatsDraft((s) => ({ ...(s || {}), refreshMinutes: Number(e.target.value || 10) }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Button color="primary" startContent={<Save size={16} />} onPress={saveServerStats}>
|
||||
Server Stats speichern
|
||||
@@ -66,9 +70,9 @@ export function ServerStats() {
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<h4 className="text-small font-semibold mb-2">Item hinzuf<EFBFBD>gen</h4>
|
||||
<h4 className="text-small font-semibold mb-2">Item hinzuf<EFBFBD>gen</h4>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Input placeholder="Label" value={statsItemDraft.label} onValueChange={(v) => setStatsItemDraft((s) => ({ ...s, label: v }))} />
|
||||
<Input placeholder="Label" value={statsItemDraft.label} onChange={(e) => setStatsItemDraft((s) => ({ ...s, label: e.target.value }))} />
|
||||
<select
|
||||
className="w-full rounded-xl border border-default-200 bg-default-50 px-3 py-2 text-sm outline-none"
|
||||
value={statsItemDraft.type}
|
||||
@@ -82,7 +86,7 @@ export function ServerStats() {
|
||||
<option value="custom">Custom</option>
|
||||
</select>
|
||||
<Button size="sm" color="primary" startContent={<Plus size={14} />} onPress={addStatsItem}>
|
||||
Hinzuf<EFBFBD>gen
|
||||
Hinzuf<EFBFBD>gen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, Button, Switch, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, Button, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Settings, Save, Logs, Bell, Shield, Edit3, Trash2 } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -14,26 +14,32 @@ export function SettingsPage() {
|
||||
<h3 className="text-base font-semibold">Allgemein</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Input
|
||||
label="Welcome Channel ID"
|
||||
placeholder="Channel ID"
|
||||
value={settings.welcomeChannelId || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, welcomeChannelId: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Welcome Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel ID"
|
||||
value={settings.welcomeChannelId || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, welcomeChannelId: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Log Channel ID"
|
||||
placeholder="Channel ID"
|
||||
value={settings.logChannelId || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, logChannelId: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Log Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel ID"
|
||||
value={settings.logChannelId || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, logChannelId: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Support Role ID"
|
||||
placeholder="Role ID"
|
||||
value={settings.supportRoleId || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, supportRoleId: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Support Role ID</Label>
|
||||
<Input
|
||||
placeholder="Role ID"
|
||||
value={settings.supportRoleId || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, supportRoleId: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Separator />
|
||||
|
||||
@@ -48,23 +54,23 @@ export function SettingsPage() {
|
||||
<h3 className="text-base font-semibold">Logging Kategorien</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.joinLeave !== false} onValueChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), joinLeave: v } } }))}>
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.joinLeave !== false} onChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), joinLeave: v } } }))}>
|
||||
<div className="flex items-center gap-2"><Logs size={14} /> Join / Leave loggen</div>
|
||||
</Switch>
|
||||
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.messageEdit !== false} onValueChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), messageEdit: v } } }))}>
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.messageEdit !== false} onChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), messageEdit: v } } }))}>
|
||||
<div className="flex items-center gap-2"><Edit3 size={14} /> Message Edit loggen</div>
|
||||
</Switch>
|
||||
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.messageDelete !== false} onValueChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), messageDelete: v } } }))}>
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.messageDelete !== false} onChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), messageDelete: v } } }))}>
|
||||
<div className="flex items-center gap-2"><Trash2 size={14} /> Message Delete loggen</div>
|
||||
</Switch>
|
||||
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.automodActions !== false} onValueChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), automodActions: v } } }))}>
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.automodActions !== false} onChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), automodActions: v } } }))}>
|
||||
<div className="flex items-center gap-2"><Shield size={14} /> Automod Actions loggen</div>
|
||||
</Switch>
|
||||
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.ticketActions !== false} onValueChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), ticketActions: v } } }))}>
|
||||
<Switch isSelected={settings.loggingConfig?.categories?.ticketActions !== false} onChange={(v) => setSettings((s) => ({ ...s, loggingConfig: { ...(s.loggingConfig || {}), categories: { ...(s.loggingConfig?.categories || {}), ticketActions: v } } }))}>
|
||||
<div className="flex items-center gap-2"><Bell size={14} /> Ticket Actions loggen</div>
|
||||
</Switch>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, Button, Chip, Switch, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||
import { RadioTower, Save, Trash2, Plus, Activity as ActivityIcon } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -17,23 +17,27 @@ export function Statuspage() {
|
||||
<h3 className="text-base font-semibold">Konfiguration</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Switch isSelected={statusDraft?.enabled !== false} onValueChange={(v) => setStatusDraft((s) => ({ ...(s || {}), enabled: v }))}>
|
||||
<Switch isSelected={statusDraft?.enabled !== false} onChange={(v) => setStatusDraft((s) => ({ ...(s || {}), enabled: v }))}>
|
||||
<div className="flex items-center gap-2"><RadioTower size={16} /> Statuspage aktiv</div>
|
||||
</Switch>
|
||||
|
||||
<Input
|
||||
label="Channel ID"
|
||||
placeholder="Channel f<>r Status-Updates"
|
||||
value={statusDraft?.channelId || ''}
|
||||
onValueChange={(v) => setStatusDraft((s) => ({ ...(s || {}), channelId: v }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel f<>r Status-Updates"
|
||||
value={statusDraft?.channelId || ''}
|
||||
onChange={(e) => setStatusDraft((s) => ({ ...(s || {}), channelId: e.target.value }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Intervall (ms)"
|
||||
type="number"
|
||||
value={String(statusDraft?.intervalMs || 60000)}
|
||||
onValueChange={(v) => setStatusDraft((s) => ({ ...(s || {}), intervalMs: Number(v || 60000) }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Intervall (ms)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={String(statusDraft?.intervalMs || 60000)}
|
||||
onChange={(e) => setStatusDraft((s) => ({ ...(s || {}), intervalMs: Number(e.target.value || 60000) }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Button color="primary" startContent={<Save size={16} />} onPress={saveStatuspage}>
|
||||
Statuspage speichern
|
||||
@@ -76,12 +80,12 @@ export function Statuspage() {
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<h4 className="text-small font-semibold mb-2">Service hinzuf<EFBFBD>gen</h4>
|
||||
<h4 className="text-small font-semibold mb-2">Service hinzuf<EFBFBD>gen</h4>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Input placeholder="Name" value={statusServiceDraft.name} onValueChange={(v) => setStatusServiceDraft((s) => ({ ...s, name: v }))} />
|
||||
<Input placeholder="URL (optional)" value={statusServiceDraft.url} onValueChange={(v) => setStatusServiceDraft((s) => ({ ...s, url: v }))} />
|
||||
<Input placeholder="Name" value={statusServiceDraft.name} onChange={(e) => setStatusServiceDraft((s) => ({ ...s, name: e.target.value }))} />
|
||||
<Input placeholder="URL (optional)" value={statusServiceDraft.url} onChange={(e) => setStatusServiceDraft((s) => ({ ...s, url: e.target.value }))} />
|
||||
<Button size="sm" color="primary" startContent={<Plus size={14} />} onPress={addStatusService}>
|
||||
Hinzuf<EFBFBD>gen
|
||||
Hinzuf<EFBFBD>gen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||
import { LogIn, UserRound, Eye, Save, Send, RefreshCw } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -7,7 +7,7 @@ export function SupportLogin() {
|
||||
const { supportLogin, setSupportLogin, saveSupportLogin } = useApp();
|
||||
|
||||
return (
|
||||
<SectionCard title="Support Login" subtitle="Login-Panel f<>r Supporter konfigurieren">
|
||||
<SectionCard title="Support Login" subtitle="Login-Panel f<>r Supporter konfigurieren">
|
||||
<div className="grid gap-5 xl:grid-cols-[1fr_400px]">
|
||||
<Card className="border border-default-100 bg-default-50/20">
|
||||
<CardHeader className="px-5 pt-5 pb-0">
|
||||
@@ -16,41 +16,51 @@ export function SupportLogin() {
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Switch
|
||||
isSelected={supportLogin?.config?.autoRefresh !== false}
|
||||
onValueChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, autoRefresh: v } } : s)}
|
||||
onChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, autoRefresh: v } } : s)}
|
||||
>
|
||||
Auto-Refresh aktiv
|
||||
</Switch>
|
||||
|
||||
<Input
|
||||
label="Panel Channel ID"
|
||||
placeholder="Channel ID eingeben"
|
||||
value={supportLogin?.config?.panelChannelId || ''}
|
||||
onValueChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, panelChannelId: v } } : s)}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Panel Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel ID eingeben"
|
||||
value={supportLogin?.config?.panelChannelId || ''}
|
||||
onChange={(e) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, panelChannelId: e.target.value } } : s)}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Titel"
|
||||
value={supportLogin?.config?.title || 'Support Login'}
|
||||
onValueChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, title: v } } : s)}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Titel</Label>
|
||||
<Input
|
||||
value={supportLogin?.config?.title || 'Support Login'}
|
||||
onChange={(e) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, title: e.target.value } } : s)}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<TextArea
|
||||
label="Beschreibung"
|
||||
value={supportLogin?.config?.description || ''}
|
||||
onValueChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, description: v } } : s)}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Beschreibung</Label>
|
||||
<TextArea
|
||||
value={supportLogin?.config?.description || ''}
|
||||
onChange={(e) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, description: e.target.value } } : s)}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Login Button Label"
|
||||
value={supportLogin?.config?.loginLabel || 'Ich bin jetzt im Support'}
|
||||
onValueChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, loginLabel: v } } : s)}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Login Button Label</Label>
|
||||
<Input
|
||||
value={supportLogin?.config?.loginLabel || 'Ich bin jetzt im Support'}
|
||||
onChange={(e) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, loginLabel: e.target.value } } : s)}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Logout Button Label"
|
||||
value={supportLogin?.config?.logoutLabel || 'Ich bin nicht mehr im Support'}
|
||||
onValueChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, logoutLabel: v } } : s)}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Logout Button Label</Label>
|
||||
<Input
|
||||
value={supportLogin?.config?.logoutLabel || 'Ich bin nicht mehr im Support'}
|
||||
onChange={(e) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, logoutLabel: e.target.value } } : s)}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Separator />
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Card, CardContent, CardHeader, Chip, Button, Tabs, Tab, Input, TextArea, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Chip, Button, Tabs, Tab, Input, TextArea, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Ticket, Clock, UserRound, CheckCircle, MessageSquare, FileText, Pencil, Trash2, ChevronRight } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { formatDate } from '../utils/formatters';
|
||||
@@ -22,11 +22,11 @@ export function Tickets() {
|
||||
return (
|
||||
<SectionCard title="Ticketsystem" subtitle="Ticket-Übersicht, Pipeline, SLA, Automationen und Knowledge Base.">
|
||||
<Tabs aria-label="Ticket Tabs" color="primary" selectedKey={ticketTab} variant="bordered" onSelectionChange={(key) => setTicketTab(String(key))}>
|
||||
<Tab key="overview" title="Übersicht" />
|
||||
<Tab key="pipeline" title="Pipeline" />
|
||||
<Tab key="sla" title="SLA" />
|
||||
<Tab key="automations" title="Automationen" />
|
||||
<Tab key="kb" title="Knowledge Base" />
|
||||
<Tab key="overview">Übersicht</Tab>
|
||||
<Tab key="pipeline">Pipeline</Tab>
|
||||
<Tab key="sla">SLA</Tab>
|
||||
<Tab key="automations">Automationen</Tab>
|
||||
<Tab key="kb">Knowledge Base</Tab>
|
||||
</Tabs>
|
||||
|
||||
{ticketTab === 'overview' && (
|
||||
@@ -256,9 +256,18 @@ export function Tickets() {
|
||||
<h3 className="text-base font-semibold">Automation bearbeiten</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Input label="Name" value={automationEditDraft.name} onValueChange={(v) => setAutomationEditDraft((s) => ({ ...s, name: v }))} />
|
||||
<Input label="Kategorie / Zustand" value={automationEditDraft.conditionValue} onValueChange={(v) => setAutomationEditDraft((s) => ({ ...s, conditionValue: v }))} />
|
||||
<TextArea label="Aktion / Nachricht" value={automationEditDraft.actionValue} onValueChange={(v) => setAutomationEditDraft((s) => ({ ...s, actionValue: v }))} />
|
||||
<TextField>
|
||||
<Label>Name</Label>
|
||||
<Input value={automationEditDraft.name} onChange={(e) => setAutomationEditDraft((s) => ({ ...s, name: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Kategorie / Zustand</Label>
|
||||
<Input value={automationEditDraft.conditionValue} onChange={(e) => setAutomationEditDraft((s) => ({ ...s, conditionValue: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Aktion / Nachricht</Label>
|
||||
<TextArea value={automationEditDraft.actionValue} onChange={(e) => setAutomationEditDraft((s) => ({ ...s, actionValue: e.target.value }))} />
|
||||
</TextField>
|
||||
<div className="flex gap-2">
|
||||
<Button color="primary" onPress={() => updateAutomation(automationEditDraft.id)}>Aktualisieren</Button>
|
||||
<Button variant="flat" onPress={() => setAutomationEditDraft(null)}>Abbrechen</Button>
|
||||
@@ -271,9 +280,18 @@ export function Tickets() {
|
||||
<h3 className="text-base font-semibold">Neue Automation</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Input label="Name" value={automationDraft.name} onValueChange={(v) => setAutomationDraft((s) => ({ ...s, name: v }))} />
|
||||
<Input label="Kategorie / Zustand" value={automationDraft.conditionValue} onValueChange={(v) => setAutomationDraft((s) => ({ ...s, conditionValue: v }))} />
|
||||
<TextArea label="Aktion / Nachricht" value={automationDraft.actionValue} onValueChange={(v) => setAutomationDraft((s) => ({ ...s, actionValue: v }))} />
|
||||
<TextField>
|
||||
<Label>Name</Label>
|
||||
<Input value={automationDraft.name} onChange={(e) => setAutomationDraft((s) => ({ ...s, name: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Kategorie / Zustand</Label>
|
||||
<Input value={automationDraft.conditionValue} onChange={(e) => setAutomationDraft((s) => ({ ...s, conditionValue: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Aktion / Nachricht</Label>
|
||||
<TextArea value={automationDraft.actionValue} onChange={(e) => setAutomationDraft((s) => ({ ...s, actionValue: e.target.value }))} />
|
||||
</TextField>
|
||||
<Button color="primary" onPress={saveAutomation}>Automation speichern</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -315,9 +333,18 @@ export function Tickets() {
|
||||
<h3 className="text-base font-semibold">Artikel bearbeiten</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Input label="Titel" value={kbEditDraft.title} onValueChange={(v) => setKbEditDraft((s) => ({ ...s, title: v }))} />
|
||||
<Input label="Keywords" value={kbEditDraft.keywords} onValueChange={(v) => setKbEditDraft((s) => ({ ...s, keywords: v }))} />
|
||||
<TextArea label="Inhalt" minRows={5} value={kbEditDraft.content} onValueChange={(v) => setKbEditDraft((s) => ({ ...s, content: v }))} />
|
||||
<TextField>
|
||||
<Label>Titel</Label>
|
||||
<Input value={kbEditDraft.title} onChange={(e) => setKbEditDraft((s) => ({ ...s, title: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Keywords</Label>
|
||||
<Input value={kbEditDraft.keywords} onChange={(e) => setKbEditDraft((s) => ({ ...s, keywords: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Inhalt</Label>
|
||||
<TextArea minRows={5} value={kbEditDraft.content} onChange={(e) => setKbEditDraft((s) => ({ ...s, content: e.target.value }))} />
|
||||
</TextField>
|
||||
<div className="flex gap-2">
|
||||
<Button color="primary" onPress={() => updateKbArticle(kbEditDraft.id)}>Aktualisieren</Button>
|
||||
<Button variant="flat" onPress={() => setKbEditDraft(null)}>Abbrechen</Button>
|
||||
@@ -330,9 +357,18 @@ export function Tickets() {
|
||||
<h3 className="text-base font-semibold">Neuer KB-Artikel</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Input label="Titel" value={kbDraft.title} onValueChange={(v) => setKbDraft((s) => ({ ...s, title: v }))} />
|
||||
<Input label="Keywords" value={kbDraft.keywords} onValueChange={(v) => setKbDraft((s) => ({ ...s, keywords: v }))} />
|
||||
<TextArea label="Inhalt" minRows={5} value={kbDraft.content} onValueChange={(v) => setKbDraft((s) => ({ ...s, content: v }))} />
|
||||
<TextField>
|
||||
<Label>Titel</Label>
|
||||
<Input value={kbDraft.title} onChange={(e) => setKbDraft((s) => ({ ...s, title: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Keywords</Label>
|
||||
<Input value={kbDraft.keywords} onChange={(e) => setKbDraft((s) => ({ ...s, keywords: e.target.value }))} />
|
||||
</TextField>
|
||||
<TextField>
|
||||
<Label>Inhalt</Label>
|
||||
<TextArea minRows={5} value={kbDraft.content} onChange={(e) => setKbDraft((s) => ({ ...s, content: e.target.value }))} />
|
||||
</TextField>
|
||||
<Button color="primary" onPress={saveKbArticle}>Artikel speichern</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator } from '@heroui/react';
|
||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||
import { Sparkles, Save, Eye } from 'lucide-react';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import { SectionCard } from '../components/shared/SectionCard';
|
||||
@@ -14,37 +14,45 @@ export function Welcome() {
|
||||
<h3 className="text-base font-semibold">Welcome konfigurieren</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 p-5">
|
||||
<Switch isSelected={settings.welcomeConfig?.enabled !== false} onValueChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), enabled: v } }))}>
|
||||
<Switch isSelected={settings.welcomeConfig?.enabled !== false} onChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), enabled: v } }))}>
|
||||
<div className="flex items-center gap-2"><Sparkles size={16} /> Welcome aktiv</div>
|
||||
</Switch>
|
||||
|
||||
<Input
|
||||
label="Channel ID"
|
||||
placeholder="Channel ID f<>r Willkommensnachrichten"
|
||||
value={settings.welcomeConfig?.channelId || settings.welcomeChannelId || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), channelId: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Channel ID</Label>
|
||||
<Input
|
||||
placeholder="Channel ID f<>r Willkommensnachrichten"
|
||||
value={settings.welcomeConfig?.channelId || settings.welcomeChannelId || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), channelId: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Titel"
|
||||
placeholder="Willkommen {user}!"
|
||||
value={settings.welcomeConfig?.embedTitle || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedTitle: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Titel</Label>
|
||||
<Input
|
||||
placeholder="Willkommen {user}!"
|
||||
value={settings.welcomeConfig?.embedTitle || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedTitle: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<TextArea
|
||||
label="Beschreibung"
|
||||
placeholder="Beschreibung des Embeds"
|
||||
value={settings.welcomeConfig?.embedDescription || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedDescription: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Beschreibung</Label>
|
||||
<TextArea
|
||||
placeholder="Beschreibung des Embeds"
|
||||
value={settings.welcomeConfig?.embedDescription || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedDescription: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Input
|
||||
label="Footer"
|
||||
placeholder={new Date().getFullYear().toString()}
|
||||
value={settings.welcomeConfig?.embedFooter || ''}
|
||||
onValueChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedFooter: v } }))}
|
||||
/>
|
||||
<TextField>
|
||||
<Label>Footer</Label>
|
||||
<Input
|
||||
placeholder={new Date().getFullYear().toString()}
|
||||
value={settings.welcomeConfig?.embedFooter || ''}
|
||||
onChange={(e) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), embedFooter: e.target.value } }))}
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
<Separator />
|
||||
|
||||
@@ -75,7 +83,7 @@ export function Welcome() {
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-3 text-tiny text-default-400">
|
||||
Nutze {'{user}'} f<EFBFBD>r den Benutzernamen und {'{server}'} f<EFBFBD>r den Servernamen.
|
||||
Nutze {'{user}'} f<EFBFBD>r den Benutzernamen und {'{server}'} f<EFBFBD>r den Servernamen.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user