simplify heroui layouts and add sonarqube workflow
This commit is contained in:
22
.gitea/workflows/sonarqube.yml
Normal file
22
.gitea/workflows/sonarqube.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: SonarQube
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sonar:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: sonarsource/sonarqube-scan-action@v5
|
||||||
|
env:
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
SONAR_HOST_URL: http://10.0.0.15:9001
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Avatar, Button, Chip, ScrollShadow, Tooltip } from '@heroui/react';
|
|
||||||
import {
|
import {
|
||||||
ChevronLeft, ChevronRight, LogOut, Server, PanelLeftClose, PanelLeft,
|
Avatar, Button, Card, CardContent, ScrollShadow, Tooltip,
|
||||||
Activity, AudioLines, CalendarDays, ClipboardList, Home,
|
Select, SelectTrigger, SelectValue, SelectPopover, ListBox, ListBoxItem
|
||||||
LogIn, Music, Puzzle, RadioTower, Settings, Shield, Sparkles,
|
} from '@heroui/react';
|
||||||
Tag, Ticket, Wrench, Bot
|
import {
|
||||||
|
LogOut, PanelLeftClose, PanelLeft, Activity, AudioLines, CalendarDays,
|
||||||
|
ClipboardList, Home, LogIn, Music, Puzzle, RadioTower, Settings,
|
||||||
|
Shield, Sparkles, Tag, Ticket, Wrench
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useApp } from '../../context/AppContext';
|
import { useApp } from '../../context/AppContext';
|
||||||
import { guildIconUrl } from '../../utils/formatters';
|
|
||||||
|
|
||||||
const navGroups = [
|
const navGroups = [
|
||||||
{
|
{
|
||||||
label: 'Dashboard',
|
label: 'Dashboard',
|
||||||
items: [
|
items: [
|
||||||
{ key: 'overview', label: 'Übersicht', icon: <Home size={18} /> },
|
{ key: 'overview', label: 'Uebersicht', icon: <Home size={18} /> },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -61,44 +62,41 @@ export function Sidebar() {
|
|||||||
const { user, guilds, currentGuildId, section, setCurrentGuildId, setSection, handleLogout } = useApp();
|
const { user, guilds, currentGuildId, section, setCurrentGuildId, setSection, handleLogout } = useApp();
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
const selectedGuild = guilds.find((g) => g.id === currentGuildId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={`flex h-full flex-col border-r border-default-100 bg-gradient-to-b from-default-50/40 to-background transition-all duration-200 ${collapsed ? 'w-16' : 'w-64'}`}>
|
<aside className={`flex h-full flex-col transition-all duration-200 ${collapsed ? 'w-20' : 'w-72'}`}>
|
||||||
<div className={`flex items-center gap-3 px-4 pt-4 pb-3 ${collapsed ? 'justify-center' : ''}`}>
|
<div className={`flex items-center gap-3 px-4 pt-4 pb-3 ${collapsed ? 'justify-center' : ''}`}>
|
||||||
|
<Avatar name="Papo" radius="lg" />
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<>
|
|
||||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-primary-400 to-primary-600 text-lg font-bold text-white shadow-lg shadow-primary-500/25">
|
|
||||||
P
|
|
||||||
</div>
|
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="text-base font-bold">Papo</div>
|
<div className="text-base font-bold">Papo</div>
|
||||||
<div className="text-[10px] uppercase tracking-widest text-default-400">Dashboard</div>
|
<div className="text-[10px] uppercase tracking-widest text-default-400">Dashboard</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{collapsed && (
|
|
||||||
<div className="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br from-primary-400 to-primary-600 text-lg font-bold text-white shadow-lg shadow-primary-500/25">
|
|
||||||
P
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`px-3 pb-2 ${collapsed ? 'px-2' : ''}`}>
|
<div className="px-3 pb-2">
|
||||||
<select
|
<Select
|
||||||
className={`w-full rounded-xl border border-default-200 bg-default-50 text-sm text-foreground outline-none transition-colors focus:border-primary-400 ${collapsed ? 'px-1 py-2 text-center text-[10px]' : 'px-3 py-2'}`}
|
aria-label="Guild auswaehlen"
|
||||||
value={currentGuildId}
|
selectedKey={currentGuildId}
|
||||||
onChange={(e) => setCurrentGuildId(e.target.value)}
|
onSelectionChange={(key) => {
|
||||||
title={selectedGuild?.name}
|
if (typeof key === 'string') setCurrentGuildId(key);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectPopover>
|
||||||
|
<ListBox>
|
||||||
{guilds.map((g) => (
|
{guilds.map((g) => (
|
||||||
<option key={g.id} value={g.id}>{collapsed ? g.name.slice(0, 2) : g.name}</option>
|
<ListBoxItem key={g.id} id={g.id} textValue={g.name}>
|
||||||
|
{collapsed ? g.name.slice(0, 2) : g.name}
|
||||||
|
</ListBoxItem>
|
||||||
))}
|
))}
|
||||||
</select>
|
</ListBox>
|
||||||
|
</SelectPopover>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-px bg-default-200 mx-3" />
|
|
||||||
|
|
||||||
<ScrollShadow className="flex-1 px-2 py-2" hideScrollBar>
|
<ScrollShadow className="flex-1 px-2 py-2" hideScrollBar>
|
||||||
<nav className="flex flex-col gap-4">
|
<nav className="flex flex-col gap-4">
|
||||||
{navGroups.map((group) => (
|
{navGroups.map((group) => (
|
||||||
@@ -116,9 +114,10 @@ export function Sidebar() {
|
|||||||
return (
|
return (
|
||||||
<Tooltip key={item.key} content={collapsed ? item.label : ''} placement="right" offset={8}>
|
<Tooltip key={item.key} content={collapsed ? item.label : ''} placement="right" offset={8}>
|
||||||
<Button
|
<Button
|
||||||
className={`h-9 justify-start gap-3 px-2 ${isActive ? 'bg-primary-500/15 text-primary-400 font-semibold' : 'text-default-500 hover:text-foreground hover:bg-default-100/40'}`}
|
className="h-9 justify-start gap-3 px-2"
|
||||||
|
color={isActive ? 'primary' : 'default'}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
variant="light"
|
variant={isActive ? 'flat' : 'light'}
|
||||||
size="sm"
|
size="sm"
|
||||||
startContent={item.icon}
|
startContent={item.icon}
|
||||||
onPress={() => setSection(item.key)}
|
onPress={() => setSection(item.key)}
|
||||||
@@ -140,9 +139,10 @@ export function Sidebar() {
|
|||||||
)}
|
)}
|
||||||
<Tooltip content={collapsed ? 'Admin' : ''} placement="right" offset={8}>
|
<Tooltip content={collapsed ? 'Admin' : ''} placement="right" offset={8}>
|
||||||
<Button
|
<Button
|
||||||
className={`h-9 justify-start gap-3 px-2 ${section === 'admin' ? 'bg-warning-500/15 text-warning-400 font-semibold' : 'text-default-500 hover:text-foreground hover:bg-default-100/40'}`}
|
className="h-9 justify-start gap-3 px-2"
|
||||||
|
color={section === 'admin' ? 'warning' : 'default'}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
variant="light"
|
variant={section === 'admin' ? 'flat' : 'light'}
|
||||||
size="sm"
|
size="sm"
|
||||||
startContent={<Wrench size={18} />}
|
startContent={<Wrench size={18} />}
|
||||||
onPress={() => setSection('admin')}
|
onPress={() => setSection('admin')}
|
||||||
@@ -155,8 +155,6 @@ export function Sidebar() {
|
|||||||
</nav>
|
</nav>
|
||||||
</ScrollShadow>
|
</ScrollShadow>
|
||||||
|
|
||||||
<div className="h-px bg-default-200 mx-3" />
|
|
||||||
|
|
||||||
<div className="p-3 flex flex-col gap-2">
|
<div className="p-3 flex flex-col gap-2">
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
@@ -169,16 +167,14 @@ export function Sidebar() {
|
|||||||
{collapsed ? <PanelLeft size={16} /> : <PanelLeftClose size={16} />}
|
{collapsed ? <PanelLeft size={16} /> : <PanelLeftClose size={16} />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className={`flex items-center gap-3 rounded-xl border border-default-100 bg-default-50/20 p-2 ${collapsed ? 'justify-center' : ''}`}>
|
<Card>
|
||||||
|
<CardContent className={`flex items-center gap-3 p-2 ${collapsed ? 'justify-center' : ''}`}>
|
||||||
<Avatar name={user?.username} size="sm" className="shrink-0" />
|
<Avatar name={user?.username} size="sm" className="shrink-0" />
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<>
|
<>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="truncate text-xs font-semibold">{user?.username}</div>
|
<div className="truncate text-xs font-semibold">{user?.username}</div>
|
||||||
<div className="flex items-center gap-1 text-[10px] text-success-400">
|
<div className="text-[10px] text-default-400">Angemeldet</div>
|
||||||
<div className="size-1.5 rounded-full bg-success-400" />
|
|
||||||
Online
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Tooltip content="Abmelden" placement="top">
|
<Tooltip content="Abmelden" placement="top">
|
||||||
<Button isIconOnly color="danger" radius="lg" size="sm" variant="light" onPress={handleLogout}>
|
<Button isIconOnly color="danger" radius="lg" size="sm" variant="light" onPress={handleLogout}>
|
||||||
@@ -187,7 +183,8 @@ export function Sidebar() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, CardContent } from '@heroui/react';
|
import { Card, CardContent, Chip } from '@heroui/react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -9,14 +9,13 @@ type Props = {
|
|||||||
|
|
||||||
export function ActivityTile({ icon, label, value }: Props) {
|
export function ActivityTile({ icon, label, value }: Props) {
|
||||||
return (
|
return (
|
||||||
<Card className="border border-default-100 bg-default-50/20">
|
<Card>
|
||||||
<CardContent className="flex flex-row items-center justify-between gap-4 p-4">
|
<CardContent className="flex flex-row items-center justify-between gap-4 p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex size-10 items-center justify-center rounded-xl bg-primary-500/10 text-primary-400">
|
<Chip color="primary" size="sm" variant="flat" startContent={icon}>
|
||||||
{icon}
|
{label}
|
||||||
</div>
|
</Chip>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-tiny uppercase tracking-widest text-default-500">{label}</div>
|
|
||||||
<div className="text-2xl font-black">{value}</div>
|
<div className="text-2xl font-black">{value}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, CardHeader, CardContent } from '@heroui/react';
|
import { Card, CardHeader, CardContent, CardTitle } from '@heroui/react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -8,11 +8,11 @@ type Props = {
|
|||||||
|
|
||||||
export function FormPanel({ title, children }: Props) {
|
export function FormPanel({ title, children }: Props) {
|
||||||
return (
|
return (
|
||||||
<Card className="border border-default-100 bg-default-50/20">
|
<Card>
|
||||||
<CardHeader className="px-5 pt-5 pb-0">
|
<CardHeader>
|
||||||
<h3 className="text-base font-semibold">{title}</h3>
|
<CardTitle>{title}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4 px-5 py-5">{children}</CardContent>
|
<CardContent className="flex flex-col gap-4">{children}</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, CardHeader, CardContent } from '@heroui/react';
|
import { Card, CardHeader, CardContent, CardTitle } from '@heroui/react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -9,11 +9,11 @@ type Props = {
|
|||||||
|
|
||||||
export function ListPanel({ title, children, className }: Props) {
|
export function ListPanel({ title, children, className }: Props) {
|
||||||
return (
|
return (
|
||||||
<Card className={`border border-default-100 bg-default-50/20 ${className ?? ''}`}>
|
<Card className={className ?? ''}>
|
||||||
<CardHeader className="px-5 pt-5 pb-0">
|
<CardHeader>
|
||||||
<h3 className="text-base font-semibold">{title}</h3>
|
<CardTitle>{title}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-5 py-5">
|
<CardContent>
|
||||||
{children}
|
{children}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ type Props = {
|
|||||||
|
|
||||||
export function SectionCard({ title, subtitle, children, action }: Props) {
|
export function SectionCard({ title, subtitle, children, action }: Props) {
|
||||||
return (
|
return (
|
||||||
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
|
<Card>
|
||||||
<CardHeader className="flex items-start justify-between gap-4 px-6 pt-6 pb-0">
|
<CardHeader className="flex items-start justify-between gap-4">
|
||||||
<div className="min-w-0 flex flex-col gap-1">
|
<div className="min-w-0 flex flex-col gap-1">
|
||||||
<CardTitle>{title}</CardTitle>
|
<CardTitle>{title}</CardTitle>
|
||||||
{subtitle && <CardDescription>{subtitle}</CardDescription>}
|
{subtitle && <CardDescription>{subtitle}</CardDescription>}
|
||||||
</div>
|
</div>
|
||||||
{action && <div className="shrink-0">{action}</div>}
|
{action && <div className="shrink-0">{action}</div>}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-6 py-5">{children}</CardContent>
|
<CardContent>{children}</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, CardContent } from '@heroui/react';
|
import { Card, CardContent, Chip } from '@heroui/react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -10,13 +10,15 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function StatCard({ icon, label, value, trend, color = 'primary' }: Props) {
|
export function StatCard({ icon, label, value, trend, color = 'primary' }: Props) {
|
||||||
|
const chipColor = color === 'default' ? 'default' : color;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border border-default-100 bg-gradient-to-br from-default-50/20 to-background hover:border-default-200 transition-all">
|
<Card>
|
||||||
<CardContent className="flex flex-col gap-2 p-4">
|
<CardContent className="flex flex-col gap-2 p-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className={`flex size-9 items-center justify-center rounded-xl bg-${color}-500/10 text-${color}-400`}>
|
<Chip color={chipColor} size="sm" variant="flat" startContent={icon}>
|
||||||
{icon}
|
{label}
|
||||||
</div>
|
</Chip>
|
||||||
{trend && (
|
{trend && (
|
||||||
<span className={`text-tiny font-medium ${trend.startsWith('+') ? 'text-success-400' : trend.startsWith('-') ? 'text-danger-400' : 'text-default-400'}`}>
|
<span className={`text-tiny font-medium ${trend.startsWith('+') ? 'text-success-400' : trend.startsWith('-') ? 'text-danger-400' : 'text-default-400'}`}>
|
||||||
{trend}
|
{trend}
|
||||||
@@ -24,7 +26,7 @@ export function StatCard({ icon, label, value, trend, color = 'primary' }: Props
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold tracking-tight">{value}</div>
|
<div className="text-2xl font-bold tracking-tight">{value}</div>
|
||||||
<div className="text-tiny uppercase tracking-widest text-default-500">{label}</div>
|
<div className="text-tiny text-default-500">{label}</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Card, CardContent, CardHeader, Avatar, Chip, Button, Separator, ScrollShadow } from '@heroui/react';
|
import { Card, CardContent, CardHeader, Avatar, Chip, Button, ScrollShadow } from '@heroui/react';
|
||||||
import {
|
import {
|
||||||
Bot, UserRound, CalendarDays, Users, Cable, Ticket, Shield, MessageSquare,
|
Bot, CalendarDays, Users, Ticket, Shield, MessageSquare,
|
||||||
Command, ChevronRight, Activity, Clock, ArrowUpRight, RefreshCw, Send,
|
ChevronRight, Activity, Clock, ArrowUpRight, RefreshCw, Send,
|
||||||
Settings, Trash2, Sparkles, Home, Server, Hash, Gauge, Zap, Bell, Tag
|
Settings, Sparkles, Hash, Gauge, Zap, Bell, Tag, Command
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { formatDate, guildIconUrl } from '../utils/formatters';
|
import { formatDate, guildIconUrl } from '../utils/formatters';
|
||||||
import { StatCard } from '../components/shared/StatCard';
|
import { StatCard } from '../components/shared/StatCard';
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
const { guildInfo, guilds, currentGuildId, overview, activity, logs, setSection, section } = useApp();
|
const { guildInfo, guilds, currentGuildId, overview, activity, logs, setSection } = useApp();
|
||||||
const selectedGuild = guilds.find((g) => g.id === currentGuildId);
|
const selectedGuild = guilds.find((g) => g.id === currentGuildId);
|
||||||
const moduleFlags = guildInfo?.modules || {};
|
const moduleFlags = guildInfo?.modules || {};
|
||||||
|
|
||||||
@@ -17,13 +17,13 @@ export function Dashboard() {
|
|||||||
{ key: 'tickets', label: 'Ticket Panel senden', icon: <Send size={16} />, color: 'primary' as const },
|
{ key: 'tickets', label: 'Ticket Panel senden', icon: <Send size={16} />, color: 'primary' as const },
|
||||||
{ key: 'serverstats', label: 'Sync starten', icon: <RefreshCw size={16} />, color: 'success' as const },
|
{ key: 'serverstats', label: 'Sync starten', icon: <RefreshCw size={16} />, color: 'success' as const },
|
||||||
{ key: 'modules', label: 'Module aktualisieren', icon: <Zap size={16} />, color: 'warning' as const },
|
{ key: 'modules', label: 'Module aktualisieren', icon: <Zap size={16} />, color: 'warning' as const },
|
||||||
{ key: 'settings', label: 'Einstellungen öffnen', icon: <Settings size={16} />, color: 'default' as const },
|
{ key: 'settings', label: 'Einstellungen oeffnen', icon: <Settings size={16} />, color: 'default' as const },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="border border-default-100 bg-gradient-to-r from-primary-500/5 via-default-50/40 to-background overflow-hidden">
|
<Card>
|
||||||
<CardContent className="flex flex-col gap-6 p-6 xl:flex-row xl:items-center xl:justify-between">
|
<CardContent className="flex flex-col gap-6 xl:flex-row xl:items-center xl:justify-between">
|
||||||
<div className="flex min-w-0 items-center gap-5">
|
<div className="flex min-w-0 items-center gap-5">
|
||||||
<Avatar className="size-20 shrink-0" radius="lg" src={guildIconUrl(selectedGuild)} />
|
<Avatar className="size-20 shrink-0" radius="lg" src={guildIconUrl(selectedGuild)} />
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
@@ -65,49 +65,23 @@ export function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-5 xl:grid-cols-2 2xl:grid-cols-3">
|
<div className="grid gap-5 xl:grid-cols-2 2xl:grid-cols-3">
|
||||||
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
|
<Card>
|
||||||
<CardHeader className="flex items-center justify-between px-5 pt-5 pb-0">
|
<CardHeader>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-bold">Activity Bereich</h2>
|
<h2 className="text-lg font-bold">Activity Bereich</h2>
|
||||||
<p className="mt-0.5 text-tiny text-default-400">Live Statistiken</p>
|
<p className="mt-0.5 text-tiny text-default-400">Live Statistiken</p>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-3 p-5">
|
<CardContent className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<StatCard icon={<MessageSquare size={16} />} label="Nachrichten" value={activity?.messages24h ?? 0} />
|
||||||
<div className="rounded-xl border border-default-100 bg-default-50/20 p-4">
|
<StatCard icon={<Command size={16} />} label="Commands" value={activity?.commands24h ?? 0} color="success" />
|
||||||
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
|
<StatCard icon={<Shield size={16} />} label="Automod" value={activity?.automod24h ?? 0} color="warning" />
|
||||||
<MessageSquare size={14} className="text-primary-400" /> Nachrichten
|
<StatCard icon={<Users size={16} />} label="Neue User" value={activity?.newUsers24h ?? 0} />
|
||||||
</div>
|
|
||||||
<div className="mt-1 text-3xl font-black">{activity?.messages24h ?? 0}</div>
|
|
||||||
<div className="text-tiny text-default-400">in den letzten 24h</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-xl border border-default-100 bg-default-50/20 p-4">
|
|
||||||
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
|
|
||||||
<Command size={14} className="text-success-400" /> Commands
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 text-3xl font-black">{activity?.commands24h ?? 0}</div>
|
|
||||||
<div className="text-tiny text-default-400">in den letzten 24h</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-xl border border-default-100 bg-default-50/20 p-4">
|
|
||||||
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
|
|
||||||
<Shield size={14} className="text-warning-400" /> Automod
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 text-3xl font-black">{activity?.automod24h ?? 0}</div>
|
|
||||||
<div className="text-tiny text-default-400">Aktionen (24h)</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-xl border border-default-100 bg-default-50/20 p-4">
|
|
||||||
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
|
|
||||||
<Users size={14} className="text-secondary-400" /> Neue User
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 text-3xl font-black">{activity?.newUsers24h ?? 0}</div>
|
|
||||||
<div className="text-tiny text-default-400">Beitritte (24h)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
|
<Card>
|
||||||
<CardHeader className="flex items-center justify-between px-5 pt-5 pb-0">
|
<CardHeader className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-bold">Guild Logs</h2>
|
<h2 className="text-lg font-bold">Guild Logs</h2>
|
||||||
<p className="mt-0.5 text-tiny text-default-400">Letzte Ereignisse</p>
|
<p className="mt-0.5 text-tiny text-default-400">Letzte Ereignisse</p>
|
||||||
@@ -116,10 +90,11 @@ export function Dashboard() {
|
|||||||
Alle
|
Alle
|
||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-5">
|
<CardContent>
|
||||||
<ScrollShadow className="max-h-[320px] space-y-2 pr-1" hideScrollBar>
|
<ScrollShadow className="max-h-[320px] space-y-2 pr-1" hideScrollBar>
|
||||||
{logs.length ? logs.slice(0, 15).map((log, i) => (
|
{logs.length ? logs.slice(0, 15).map((log, i) => (
|
||||||
<div key={`${log.timestamp}-${i}`} className="flex items-start gap-3 rounded-xl border border-default-100 bg-default-50/20 p-3">
|
<Card key={`${log.timestamp}-${i}`}>
|
||||||
|
<CardContent className="flex items-start gap-3 p-3">
|
||||||
<div className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-lg ${
|
<div className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-lg ${
|
||||||
log.level === 'error' ? 'bg-danger-500/10 text-danger-400' :
|
log.level === 'error' ? 'bg-danger-500/10 text-danger-400' :
|
||||||
log.level === 'warn' ? 'bg-warning-500/10 text-warning-400' :
|
log.level === 'warn' ? 'bg-warning-500/10 text-warning-400' :
|
||||||
@@ -135,7 +110,6 @@ export function Dashboard() {
|
|||||||
color={log.level === 'error' ? 'danger' : log.level === 'warn' ? 'warning' : 'default'}
|
color={log.level === 'error' ? 'danger' : log.level === 'warn' ? 'warning' : 'default'}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
className="h-5"
|
|
||||||
>
|
>
|
||||||
{(log.level || 'info').toUpperCase()}
|
{(log.level || 'info').toUpperCase()}
|
||||||
</Chip>
|
</Chip>
|
||||||
@@ -145,7 +119,8 @@ export function Dashboard() {
|
|||||||
{log.category ? <span className="text-default-500">[{log.category}] </span> : ''}{log.message || '-'}
|
{log.category ? <span className="text-default-500">[{log.category}] </span> : ''}{log.message || '-'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)) : (
|
)) : (
|
||||||
<div className="flex flex-col items-center gap-2 py-6 text-center text-small text-default-400">
|
<div className="flex flex-col items-center gap-2 py-6 text-center text-small text-default-400">
|
||||||
<Activity size={20} />
|
<Activity size={20} />
|
||||||
@@ -156,12 +131,12 @@ export function Dashboard() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
|
<Card>
|
||||||
<CardHeader className="px-5 pt-5 pb-0">
|
<CardHeader>
|
||||||
<h2 className="text-lg font-bold">Quick Actions</h2>
|
<h2 className="text-lg font-bold">Quick Actions</h2>
|
||||||
<p className="mt-0.5 text-tiny text-default-400">Schnellzugriff</p>
|
<p className="mt-0.5 text-tiny text-default-400">Schnellzugriff</p>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-2 p-5">
|
<CardContent className="flex flex-col gap-2">
|
||||||
{quickActions.map((action) => (
|
{quickActions.map((action) => (
|
||||||
<Button
|
<Button
|
||||||
key={action.key}
|
key={action.key}
|
||||||
@@ -182,11 +157,11 @@ export function Dashboard() {
|
|||||||
{(['tickets', 'supportlogin', 'automod', 'welcome', 'birthday', 'reactionroles'] as const).map((key) => {
|
{(['tickets', 'supportlogin', 'automod', 'welcome', 'birthday', 'reactionroles'] as const).map((key) => {
|
||||||
const item = navItemMap[key];
|
const item = navItemMap[key];
|
||||||
return (
|
return (
|
||||||
<Card key={key} isPressable className="border border-default-100 bg-default-50/20 transition-all hover:border-primary-300 hover:shadow-md" onPress={() => setSection(key)}>
|
<Card key={key} isPressable onPress={() => setSection(key)}>
|
||||||
<CardContent className="flex flex-col items-start gap-2 p-4">
|
<CardContent className="flex flex-col items-start gap-2">
|
||||||
<div className="flex size-10 items-center justify-center rounded-xl bg-primary-500/10 text-primary-400">
|
<Chip color="primary" size="sm" variant="flat" startContent={item.icon}>
|
||||||
{item.icon}
|
{item.label}
|
||||||
</div>
|
</Chip>
|
||||||
<div className="font-semibold text-sm">{item.label}</div>
|
<div className="font-semibold text-sm">{item.label}</div>
|
||||||
<div className="text-tiny text-default-400">Modul verwalten</div>
|
<div className="text-tiny text-default-400">Modul verwalten</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
import { Avatar, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||||
import { LogIn, UserRound, Eye, Save, Send, RefreshCw } from 'lucide-react';
|
import { LogIn, UserRound, Save, Send } from 'lucide-react';
|
||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
|
|
||||||
@@ -7,13 +7,16 @@ export function SupportLogin() {
|
|||||||
const { supportLogin, setSupportLogin, saveSupportLogin } = useApp();
|
const { supportLogin, setSupportLogin, saveSupportLogin } = useApp();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionCard title="Support Login" subtitle="Login-Panel f<EFBFBD>r Supporter konfigurieren">
|
<SectionCard title="Support Login" subtitle="Login-Panel fuer Supporter konfigurieren">
|
||||||
<div className="grid gap-5 xl:grid-cols-[1fr_400px]">
|
<div className="grid gap-5 xl:grid-cols-[1fr_400px]">
|
||||||
<Card className="border border-default-100 bg-default-50/20">
|
<Card>
|
||||||
<CardHeader className="px-5 pt-5 pb-0">
|
<CardHeader>
|
||||||
<h3 className="text-base font-semibold">Panel-Konfiguration</h3>
|
<div>
|
||||||
|
<CardTitle>Panel-Konfiguration</CardTitle>
|
||||||
|
<CardDescription>Texte und Zielkanal mit normalen HeroUI-Feldern pflegen.</CardDescription>
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4 p-5">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<Switch
|
<Switch
|
||||||
isSelected={supportLogin?.config?.autoRefresh !== false}
|
isSelected={supportLogin?.config?.autoRefresh !== false}
|
||||||
onChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, autoRefresh: v } } : s)}
|
onChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, autoRefresh: v } } : s)}
|
||||||
@@ -76,41 +79,48 @@ export function SupportLogin() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Card className="border border-default-100 bg-default-50/20">
|
<Card>
|
||||||
<CardHeader className="px-5 pt-5 pb-0">
|
<CardHeader>
|
||||||
<h3 className="text-base font-semibold">Live Vorschau</h3>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="p-5">
|
|
||||||
<div className="rounded-xl border border-default-100 bg-gradient-to-b from-default-50/40 to-background p-4">
|
|
||||||
<div className="flex items-center gap-3 mb-3">
|
|
||||||
<div className="flex size-10 items-center justify-center rounded-xl bg-primary-500/10 text-primary-400">
|
|
||||||
<LogIn size={20} />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold">{supportLogin?.config?.title || 'Support Login'}</div>
|
<CardTitle>Live Vorschau</CardTitle>
|
||||||
<div className="text-tiny text-default-400">{supportLogin?.config?.description || 'Melde dich als Support an/ab.'}</div>
|
<CardDescription>Panel in einer normalen HeroUI-Karte.</CardDescription>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<LogIn size={16} />
|
||||||
|
<div>
|
||||||
|
<CardTitle>{supportLogin?.config?.title || 'Support Login'}</CardTitle>
|
||||||
|
<CardDescription>{supportLogin?.config?.description || 'Melde dich als Support an/ab.'}</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
</CardHeader>
|
||||||
|
<CardContent className="flex gap-2">
|
||||||
<Button size="sm" color="primary" variant="flat">{supportLogin?.config?.loginLabel || 'Login'}</Button>
|
<Button size="sm" color="primary" variant="flat">{supportLogin?.config?.loginLabel || 'Login'}</Button>
|
||||||
<Button size="sm" variant="flat">{supportLogin?.config?.logoutLabel || 'Logout'}</Button>
|
<Button size="sm" variant="flat">{supportLogin?.config?.logoutLabel || 'Logout'}</Button>
|
||||||
</div>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="border border-default-100 bg-default-50/20">
|
<Card>
|
||||||
<CardHeader className="px-5 pt-5 pb-0">
|
<CardHeader>
|
||||||
<h3 className="text-base font-semibold">Aktive Supporter</h3>
|
<div>
|
||||||
|
<CardTitle>Aktive Supporter</CardTitle>
|
||||||
|
<CardDescription>Aktueller Status aus der Guild.</CardDescription>
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-2 p-5">
|
<CardContent className="flex flex-col gap-2">
|
||||||
{supportLogin?.status?.active?.length ? supportLogin.status.active.map((s, i) => (
|
{supportLogin?.status?.active?.length ? supportLogin.status.active.map((s, i) => (
|
||||||
<div key={i} className="flex items-center gap-3 rounded-xl border border-default-100 bg-default-50/30 px-4 py-3 text-small">
|
<Card key={i}>
|
||||||
<div className="size-2 rounded-full bg-success-400" />
|
<CardContent className="flex items-center gap-3">
|
||||||
<Avatar name={s.username?.[0]} size="sm" className="size-6" />
|
<Avatar name={s.username?.[0]} size="sm" className="size-6" />
|
||||||
<span className="font-medium">{s.username || s.userId}</span>
|
<span className="font-medium">{s.username || s.userId}</span>
|
||||||
<Chip size="sm" variant="flat" color="success" className="ml-auto">Online</Chip>
|
<Chip size="sm" variant="flat" color="success" className="ml-auto">Online</Chip>
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)) : (
|
)) : (
|
||||||
<div className="flex flex-col items-center gap-2 py-4 text-center text-tiny text-default-400">
|
<div className="flex flex-col items-center gap-2 py-4 text-center text-tiny text-default-400">
|
||||||
<UserRound size={20} />
|
<UserRound size={20} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Input, TextArea, Button, Switch, Separator, TextField, Label } from '@heroui/react';
|
||||||
import { Sparkles, Save, Eye } from 'lucide-react';
|
import { Sparkles, Save } from 'lucide-react';
|
||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { SectionCard } from '../components/shared/SectionCard';
|
import { SectionCard } from '../components/shared/SectionCard';
|
||||||
|
|
||||||
@@ -9,11 +9,14 @@ export function Welcome() {
|
|||||||
return (
|
return (
|
||||||
<SectionCard title="Willkommen" subtitle="Welcome-Embeds und Join-Nachrichten">
|
<SectionCard title="Willkommen" subtitle="Welcome-Embeds und Join-Nachrichten">
|
||||||
<div className="grid gap-5 xl:grid-cols-2">
|
<div className="grid gap-5 xl:grid-cols-2">
|
||||||
<Card className="border border-default-100 bg-default-50/20">
|
<Card>
|
||||||
<CardHeader className="px-5 pt-5 pb-0">
|
<CardHeader>
|
||||||
<h3 className="text-base font-semibold">Welcome konfigurieren</h3>
|
<div>
|
||||||
|
<CardTitle>Welcome konfigurieren</CardTitle>
|
||||||
|
<CardDescription>Standardfelder fuer das Welcome-Embed.</CardDescription>
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4 p-5">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<Switch isSelected={settings.welcomeConfig?.enabled !== false} onChange={(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>
|
<div className="flex items-center gap-2"><Sparkles size={16} /> Welcome aktiv</div>
|
||||||
</Switch>
|
</Switch>
|
||||||
@@ -21,7 +24,7 @@ export function Welcome() {
|
|||||||
<TextField>
|
<TextField>
|
||||||
<Label>Channel ID</Label>
|
<Label>Channel ID</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Channel ID f<EFBFBD>r Willkommensnachrichten"
|
placeholder="Channel ID fuer Willkommensnachrichten"
|
||||||
value={settings.welcomeConfig?.channelId || settings.welcomeChannelId || ''}
|
value={settings.welcomeConfig?.channelId || settings.welcomeChannelId || ''}
|
||||||
onChange={(e) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), channelId: e.target.value } }))}
|
onChange={(e) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), channelId: e.target.value } }))}
|
||||||
/>
|
/>
|
||||||
@@ -62,33 +65,35 @@ export function Welcome() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<Card>
|
||||||
<Card className="border border-default-100 bg-default-50/20">
|
<CardHeader>
|
||||||
<CardHeader className="px-5 pt-5 pb-0">
|
<div>
|
||||||
<h3 className="text-base font-semibold">Live Vorschau</h3>
|
<CardTitle>Live Vorschau</CardTitle>
|
||||||
|
<CardDescription>Normale HeroUI-Karten ohne zusaetzliche Huelle.</CardDescription>
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-5">
|
<CardContent className="flex flex-col gap-4">
|
||||||
<div className="rounded-xl border border-default-100 bg-gradient-to-b from-default-50/40 to-background p-4">
|
<Card>
|
||||||
<div className="flex items-start gap-3">
|
<CardHeader>
|
||||||
<div className="flex size-10 items-center justify-center rounded-xl bg-primary-500/10 text-primary-400">
|
<div className="flex items-center gap-2">
|
||||||
<Sparkles size={20} />
|
<Sparkles size={16} />
|
||||||
|
<CardTitle>{settings.welcomeConfig?.embedTitle || 'Willkommen!'}</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
</CardHeader>
|
||||||
<div className="font-semibold text-lg">{settings.welcomeConfig?.embedTitle || 'Willkommen!'}</div>
|
<CardContent>
|
||||||
<p className="mt-1 text-small text-default-500">{settings.welcomeConfig?.embedDescription || 'Willkommen auf dem Server!'}</p>
|
<p>{settings.welcomeConfig?.embedDescription || 'Willkommen auf dem Server!'}</p>
|
||||||
{settings.welcomeConfig?.embedFooter && (
|
{settings.welcomeConfig?.embedFooter && (
|
||||||
<p className="mt-2 text-tiny text-default-400">{settings.welcomeConfig.embedFooter}</p>
|
<p className="text-small text-default-500">{settings.welcomeConfig.embedFooter}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
|
||||||
<p className="mt-3 text-tiny text-default-400">
|
<p className="text-small text-default-500">
|
||||||
Nutze {'{user}'} f<EFBFBD>r den Benutzernamen und {'{server}'} f<EFBFBD>r den Servernamen.
|
Nutze {'{user}'} fuer den Benutzernamen und {'{server}'} fuer den Servernamen.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</SectionCard>
|
</SectionCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user