feat: vollständiges Dashboard-Redesign mit HeroUI - monolithische App.tsx aufgelöst, 16 Seiten, Context-API, collapsible Sidebar, neues Dashboard-Layout
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled

This commit is contained in:
Pepe44DEV
2026-07-01 06:15:56 +02:00
parent ccaf7bd4d2
commit e2d8002bf2
29 changed files with 2776 additions and 1296 deletions

View File

@@ -0,0 +1,93 @@
import { Card, CardContent, CardHeader, Input, Button, Chip, Switch, Separator } 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';
import type { StatusService } from '../types';
export function Statuspage() {
const { statusDraft, setStatusDraft, saveStatuspage, statusServiceDraft, setStatusServiceDraft, addStatusService, deleteStatusService } = useApp();
const services = ((statusDraft?.services || []) as StatusService[]);
return (
<SectionCard title="Statuspage" subtitle="Statusseite und Service-Liste verwalten">
<div className="grid gap-5 xl:grid-cols-[420px_1fr]">
<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={statusDraft?.enabled !== false} onValueChange={(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 }))}
/>
<Input
label="Intervall (ms)"
type="number"
value={String(statusDraft?.intervalMs || 60000)}
onValueChange={(v) => setStatusDraft((s) => ({ ...(s || {}), intervalMs: Number(v || 60000) }))}
/>
<Button color="primary" startContent={<Save size={16} />} onPress={saveStatuspage}>
Statuspage speichern
</Button>
</CardContent>
</Card>
<Card className="border border-default-100 bg-default-50/20">
<CardHeader className="px-5 pt-5 pb-0">
<h3 className="text-base font-semibold">Services ({services.length})</h3>
</CardHeader>
<CardContent className="flex flex-col gap-3 p-5">
{services.length ? services.map((service) => (
<div key={service.id} className="flex items-center justify-between rounded-xl border border-default-100 bg-default-50/30 px-4 py-3 text-small">
<div className="flex items-center gap-3 min-w-0">
<div className="size-2 rounded-full shrink-0" />
<div className="min-w-0">
<span className="font-medium">{service.name || 'Service'}</span>
<span className="ml-2 text-default-400 text-tiny truncate">{service.url || ''}</span>
</div>
</div>
<div className="flex items-center gap-2">
<Chip size="sm" variant="flat" color={
service.status === 'operational' ? 'success' :
service.status === 'degraded' ? 'warning' :
service.status === 'down' ? 'danger' : 'default'
}>{service.status || 'unknown'}</Chip>
<Button isIconOnly size="sm" variant="light" color="danger" onPress={() => deleteStatusService(service.id)}>
<Trash2 size={14} />
</Button>
</div>
</div>
)) : (
<div className="flex flex-col items-center gap-2 py-4 text-center text-tiny text-default-400">
<ActivityIcon size={20} />
Keine Services
</div>
)}
<Separator />
<div>
<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 }))} />
<Button size="sm" color="primary" startContent={<Plus size={14} />} onPress={addStatusService}>
Hinzuf<EFBFBD>gen
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
</SectionCard>
);
}