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
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled
This commit is contained in:
20
frontend/src/components/shared/EmptyState.tsx
Normal file
20
frontend/src/components/shared/EmptyState.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Card, CardContent } from '@heroui/react';
|
||||
import { Inbox } from 'lucide-react';
|
||||
|
||||
type Props = {
|
||||
message?: string;
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
|
||||
export function EmptyState({ message = 'Keine Daten vorhanden', icon }: Props) {
|
||||
return (
|
||||
<Card className="border border-default-100 bg-default-50/10">
|
||||
<CardContent className="flex flex-col items-center gap-3 py-8">
|
||||
<div className="flex size-12 items-center justify-center rounded-full bg-default-100/50 text-default-400">
|
||||
{icon || <Inbox size={24} />}
|
||||
</div>
|
||||
<p className="text-small text-default-400">{message}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
25
frontend/src/components/shared/ErrorState.tsx
Normal file
25
frontend/src/components/shared/ErrorState.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Card, CardContent } from '@heroui/react';
|
||||
import { AlertTriangle, RefreshCw } from 'lucide-react';
|
||||
|
||||
type Props = {
|
||||
message?: string;
|
||||
onRetry?: () => void;
|
||||
};
|
||||
|
||||
export function ErrorState({ message = 'Ein Fehler ist aufgetreten', onRetry }: Props) {
|
||||
return (
|
||||
<Card className="border border-danger-100/30 bg-danger-50/10">
|
||||
<CardContent className="flex flex-col items-center gap-3 py-8">
|
||||
<div className="flex size-12 items-center justify-center rounded-full bg-danger-500/10 text-danger-400">
|
||||
<AlertTriangle size={24} />
|
||||
</div>
|
||||
<p className="text-small text-default-500">{message}</p>
|
||||
{onRetry && (
|
||||
<button className="flex items-center gap-1 text-tiny text-primary-400 hover:text-primary-300 transition-colors" onClick={onRetry}>
|
||||
<RefreshCw size={12} /> Erneut versuchen
|
||||
</button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +1,10 @@
|
||||
import { Card, CardContent } from '@heroui/react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type Props = {
|
||||
lines?: number;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export function LoadingSkeleton({ lines = 3, children }: Props) {
|
||||
if (children) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
{Array.from({ length: lines }).map((_, i) => (
|
||||
<Card key={i} className="animate-pulse border border-default-100 bg-default-50/30">
|
||||
<CardContent className="p-4">{children}</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LoadingSkeleton({ lines = 3 }: Props) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
{Array.from({ length: lines }).map((_, i) => (
|
||||
|
||||
@@ -4,18 +4,27 @@ import type { ReactNode } from 'react';
|
||||
type Props = {
|
||||
icon: ReactNode;
|
||||
label: string;
|
||||
value: string;
|
||||
value: string | number;
|
||||
trend?: string;
|
||||
color?: 'primary' | 'success' | 'warning' | 'danger' | 'default';
|
||||
};
|
||||
|
||||
export function StatCard({ icon, label, value }: Props) {
|
||||
export function StatCard({ icon, label, value, trend, color = 'primary' }: Props) {
|
||||
return (
|
||||
<Card className="border border-default-100 bg-default-50/20">
|
||||
<Card className="border border-default-100 bg-gradient-to-br from-default-50/20 to-background hover:border-default-200 transition-all">
|
||||
<CardContent className="flex flex-col gap-2 p-4">
|
||||
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
|
||||
<span className="text-primary-400">{icon}</span>
|
||||
{label}
|
||||
<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`}>
|
||||
{icon}
|
||||
</div>
|
||||
{trend && (
|
||||
<span className={`text-tiny font-medium ${trend.startsWith('+') ? 'text-success-400' : trend.startsWith('-') ? 'text-danger-400' : 'text-default-400'}`}>
|
||||
{trend}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-2xl font-bold tracking-tight">{value}</div>
|
||||
<div className="text-tiny uppercase tracking-widest text-default-500">{label}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user