refactor(ui): Modals & Unterseiten auf Primitives & dark: umgestellt

This commit is contained in:
Hitonabi
2026-07-24 16:04:46 +02:00
parent 7004d798d6
commit 90b708302e
10 changed files with 808 additions and 869 deletions
+19 -23
View File
@@ -1,37 +1,33 @@
import { ReactNode } from 'react'
import { BookOpen, Disc, Tv, HardDrive, Bell, Wrench, Cpu, Download, HelpCircle } from 'lucide-react'
import { useDarkMode } from '../context/ThemeContext'
// Anleitungs-Tab (Commander-Wunsch 24.07.): Rippy erklärt sich selbst —
// keine externen Wikis, keine README-Suche. Reiner Text, keine API-Aufrufe.
import { Disc, Tv, HardDrive, Bell, Wrench, Cpu, Download, HelpCircle } from 'lucide-react'
import { PageHeader } from '../components/ui/PageHeader'
import { Card, CardHeader, CardTitle, CardContent } from '../components/ui/Card'
function Abschnitt({ icon: Icon, titel, children }: { icon: any, titel: string, children: ReactNode }) {
const { theme } = useDarkMode()
return (
<section className={`rounded-xl border p-6 ${theme === 'dark' ? 'bg-slate-800 border-slate-700' : 'bg-white border-slate-200'}`}>
<h2 className={`text-lg font-semibold mb-3 flex items-center gap-2 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>
<Icon size={20} className={theme === 'dark' ? 'text-indigo-400' : 'text-indigo-600'} />
{titel}
</h2>
<div className={`space-y-2 text-sm leading-relaxed ${theme === 'dark' ? 'text-slate-300' : 'text-slate-600'}`}>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2.5">
<Icon size={20} className="text-amber-500" />
<span>{titel}</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm leading-relaxed text-slate-600 dark:text-slate-300">
{children}
</div>
</section>
</CardContent>
</Card>
)
}
export default function AnleitungPage() {
const { theme } = useDarkMode()
const fett = theme === 'dark' ? 'text-slate-100 font-medium' : 'text-slate-900 font-medium'
const fett = "font-semibold text-slate-900 dark:text-slate-100"
return (
<div className="space-y-6 max-w-4xl transition-colors duration-300">
<div className="flex flex-col gap-2">
<h1 className={`text-3xl font-bold ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>Anleitung</h1>
<p className={`text-sm ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
So funktioniert Rippy vom Einlegen bis zum fertigen Film in deinem Media-Server.
</p>
</div>
<div className="space-y-6 max-w-4xl">
<PageHeader
title="Anleitung & Hilfe"
subtitle="So funktioniert Rippy — vom Einlegen der Disc bis zum fertigen Film in deinem Media-Server"
/>
<Abschnitt icon={Disc} titel="Der normale Weg: Disc rein, fertig">
<p>
+93 -126
View File
@@ -1,8 +1,10 @@
import { useState, useEffect } from 'react'
import { Terminal, AlertCircle, CheckCircle, RefreshCw } from 'lucide-react'
import { api } from '../lib/api'
import { useDarkMode } from '../context/ThemeContext'
import { useToast } from '../context/ToastContext'
import { PageHeader } from '../components/ui/PageHeader'
import { Card, CardContent } from '../components/ui/Card'
import { Button } from '../components/ui/Button'
interface LogEntry {
id: string
@@ -18,12 +20,9 @@ export default function LogsPage() {
const [filter, setFilter] = useState<string>('all')
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const { theme } = useDarkMode()
const { toast } = useToast()
const refreshLogs = async (manuell = false) => {
// Echte Ereignisse aus der Datenbank — die Mock-Einträge („The Matrix
// gerippt") sind raus. Was hier steht, ist passiert; sonst ist es leer.
if (manuell) setRefreshing(true)
try {
const response = await api.get('/logs', { params: { limit: 200 } })
@@ -46,19 +45,19 @@ export default function LogsPage() {
const getLevelColor = (level: string) => {
switch (level) {
case 'error': return theme === 'dark' ? 'bg-rose-900/30 text-rose-400 border-rose-700/20' : 'bg-rose-100 text-rose-700 border-rose-600/20'
case 'warning': return theme === 'dark' ? 'bg-amber-900/30 text-amber-400 border-amber-700/20' : 'bg-amber-100 text-amber-700 border-amber-600/20'
case 'success': return theme === 'dark' ? 'bg-emerald-900/30 text-emerald-400 border-emerald-700/20' : 'bg-emerald-100 text-emerald-700 border-emerald-600/20'
default: return theme === 'dark' ? 'bg-blue-900/30 text-blue-400 border-blue-700/20' : 'bg-blue-100 text-blue-700 border-blue-600/20'
case 'error': return 'bg-rose-500/15 text-rose-600 dark:text-rose-400 border-rose-500/30'
case 'warning': return 'bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/30'
case 'success': return 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border-emerald-500/30'
default: return 'bg-sky-500/15 text-sky-600 dark:text-sky-400 border-sky-500/30'
}
}
const getLevelIcon = (level: string) => {
switch (level) {
case 'error': return <AlertCircle size={16} />
case 'warning': return <AlertCircle size={16} />
case 'success': return <CheckCircle size={16} />
default: return <Terminal size={16} />
case 'error': return <AlertCircle size={15} />
case 'warning': return <AlertCircle size={15} />
case 'success': return <CheckCircle size={15} />
default: return <Terminal size={15} />
}
}
@@ -83,153 +82,121 @@ export default function LogsPage() {
}
return (
<div className="space-y-6 transition-colors duration-300">
{/* Header */}
<div className="flex flex-col gap-2 transition-colors duration-300">
<h1 className="text-3xl font-bold dark:text-slate-100 text-slate-900">Logs</h1>
<p className={`text-sm ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Alle relevanten Log-Informationen des Ripping-Systems</p>
</div>
<div className="space-y-6">
<PageHeader
title="System-Logs"
subtitle="Alle relevanten Ereignisse und Protokolle des Ripping-Systems auf einen Blick"
action={
<Button variant="secondary" onClick={() => refreshLogs(true)}>
<RefreshCw size={15} className={loading || refreshing ? 'animate-spin' : ''} />
Neu laden
</Button>
}
/>
{/* Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 transition-colors duration-300">
<div className={`rounded-lg p-4 border transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-800 border-slate-700' : 'bg-white border-slate-200'}`}>
<div className="flex items-center gap-3 mb-2">
<div className="p-2 rounded-lg bg-blue-500/10 text-blue-500">
{/* Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4 flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-sky-500/15 text-sky-500 border border-sky-500/20">
<Terminal size={18} />
</div>
<span className={`text-sm font-medium ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Info</span>
</div>
<p className={`text-2xl font-bold ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>{stats.info}</p>
</div>
<div className={`rounded-lg p-4 border transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-800 border-slate-700' : 'bg-white border-slate-200'}`}>
<div className="flex items-center gap-3 mb-2">
<div className="p-2 rounded-lg bg-amber-500/10 text-amber-500">
<div>
<span className="text-xs text-slate-500 dark:text-slate-400 font-medium">Info</span>
<p className="text-2xl font-bold text-slate-900 dark:text-slate-100">{stats.info}</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4 flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-amber-500/15 text-amber-500 border border-amber-500/20">
<AlertCircle size={18} />
</div>
<span className={`text-sm font-medium ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Warnung</span>
</div>
<p className={`text-2xl font-bold ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>{stats.warning}</p>
</div>
<div className={`rounded-lg p-4 border transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-800 border-slate-700' : 'bg-white border-slate-200'}`}>
<div className="flex items-center gap-3 mb-2">
<div className="p-2 rounded-lg bg-emerald-500/10 text-emerald-500">
<div>
<span className="text-xs text-slate-500 dark:text-slate-400 font-medium">Warnungen</span>
<p className="text-2xl font-bold text-slate-900 dark:text-slate-100">{stats.warning}</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4 flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-emerald-500/15 text-emerald-500 border border-emerald-500/20">
<CheckCircle size={18} />
</div>
<span className={`text-sm font-medium ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Erfolg</span>
</div>
<p className={`text-2xl font-bold ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>{stats.success}</p>
</div>
<div className={`rounded-lg p-4 border transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-800 border-slate-700' : 'bg-white border-slate-200'}`}>
<div className="flex items-center gap-3 mb-2">
<div className="p-2 rounded-lg bg-rose-500/10 text-rose-500">
<div>
<span className="text-xs text-slate-500 dark:text-slate-400 font-medium">Erfolge</span>
<p className="text-2xl font-bold text-slate-900 dark:text-slate-100">{stats.success}</p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4 flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-rose-500/15 text-rose-500 border border-rose-500/20">
<AlertCircle size={18} />
</div>
<span className={`text-sm font-medium ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Fehler</span>
</div>
<p className={`text-2xl font-bold ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>{stats.error}</p>
</div>
<div>
<span className="text-xs text-slate-500 dark:text-slate-400 font-medium">Fehler</span>
<p className="text-2xl font-bold text-slate-900 dark:text-slate-100">{stats.error}</p>
</div>
</CardContent>
</Card>
</div>
{/* Filters */}
<div className={`rounded-lg p-4 border transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-800 border-slate-700' : 'bg-white border-slate-200'}`}>
<div className="flex flex-wrap items-center gap-3">
<span className={`text-sm font-medium ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Filter:</span>
<button
onClick={() => setFilter('all')}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${
filter === 'all'
? 'bg-indigo-600 text-white'
: theme === 'dark' ? 'bg-slate-700 text-slate-300 hover:bg-slate-600' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Alle
</button>
<button
onClick={() => setFilter('info')}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${
filter === 'info'
? 'bg-blue-600 text-white'
: theme === 'dark' ? 'bg-slate-700 text-slate-300 hover:bg-slate-600' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Info
</button>
<button
onClick={() => setFilter('success')}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${
filter === 'success'
? 'bg-emerald-600 text-white'
: theme === 'dark' ? 'bg-slate-700 text-slate-300 hover:bg-slate-600' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Erfolg
</button>
<button
onClick={() => setFilter('warning')}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${
filter === 'warning'
? 'bg-amber-600 text-white'
: theme === 'dark' ? 'bg-slate-700 text-slate-300 hover:bg-slate-600' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Warnung
</button>
<button
onClick={() => setFilter('error')}
className={`px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${
filter === 'error'
? 'bg-rose-600 text-white'
: theme === 'dark' ? 'bg-slate-700 text-slate-300 hover:bg-slate-600' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Fehler
</button>
<div className="flex-1" />
<button
onClick={() => refreshLogs(true)}
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${theme === 'dark' ? 'bg-slate-700 text-slate-300 hover:bg-slate-600' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'}`}
>
<RefreshCw size={14} className={loading || refreshing ? 'animate-spin' : ''} />
Neu laden
</button>
{/* Filters & Log List */}
<Card className="overflow-hidden">
<div className="px-6 py-4 border-b border-slate-200/80 dark:border-slate-800 flex items-center gap-2 flex-wrap bg-slate-50/50 dark:bg-slate-900/50">
<span className="text-xs font-medium text-slate-500 dark:text-slate-400 mr-2">Filter:</span>
{['all', 'info', 'success', 'warning', 'error'].map(f => (
<button
key={f}
onClick={() => setFilter(f)}
className={`px-3 py-1 rounded-full text-xs font-medium transition-all ${
filter === f
? 'bg-amber-500 text-slate-950 font-bold shadow-md shadow-amber-500/20'
: 'bg-slate-200/70 dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-700'
}`}
>
{f === 'all' ? 'Alle' : getLevelLabel(f)}
</button>
))}
</div>
</div>
{/* Logs List */}
<div className={`rounded-lg border overflow-hidden transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-800 border-slate-700' : 'bg-white border-slate-200'}`}>
<div className="max-h-[600px] overflow-y-auto">
{filteredLogs.length === 0 ? (
<div className="text-center py-12 transition-colors duration-300">
<div className={`mx-auto w-16 h-16 rounded-full flex items-center justify-center mb-4 ${theme === 'dark' ? 'bg-slate-700' : 'bg-slate-100'}`}>
<Terminal className={theme === 'dark' ? 'text-slate-400' : 'text-slate-400'} size={32} />
<div className="text-center py-16">
<div className="mx-auto w-16 h-16 rounded-2xl flex items-center justify-center mb-3 bg-slate-100 dark:bg-slate-800 text-slate-400 border border-slate-200 dark:border-slate-700">
<Terminal size={32} />
</div>
<p className={`text-sm ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>Keine Logs gefunden</p>
<p className="text-sm text-slate-500 dark:text-slate-400">Keine Logs für diesen Filter vorhanden</p>
</div>
) : (
<div className="divide-y divide-slate-200 dark:divide-slate-700">
<div className="divide-y divide-slate-200/80 dark:divide-slate-800">
{filteredLogs.map(log => (
<div key={log.id} className={`p-4 transition-colors duration-300 hover:${theme === 'dark' ? 'bg-slate-700/50' : 'bg-slate-50'}`}>
<div key={log.id} className="p-4 transition-colors hover:bg-slate-50/80 dark:hover:bg-slate-800/40">
<div className="flex items-start gap-3">
<div className={`mt-1 p-1.5 rounded ${getLevelColor(log.level)}`}>
<div className={`mt-0.5 p-1.5 rounded-lg border ${getLevelColor(log.level)}`}>
{getLevelIcon(log.level)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<span className={`text-xs font-mono px-1.5 py-0.5 rounded ${theme === 'dark' ? 'bg-slate-700 text-slate-400' : 'bg-slate-100 text-slate-600'}`}>
<span className="text-xs font-mono px-2 py-0.5 rounded-md bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border border-slate-200 dark:border-slate-700">
{log.source}
</span>
<span className={`text-xs font-medium ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
<span className="text-xs text-slate-400 font-mono">
{log.timestamp}
</span>
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${getLevelColor(log.level)}`}>
<span className={`text-xs font-medium px-2 py-0.5 rounded-full border ${getLevelColor(log.level)}`}>
{getLevelLabel(log.level)}
</span>
</div>
<p className={`text-sm font-medium ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">
{log.message}
</p>
{log.details && (
<p className={`text-xs mt-1 font-mono ${theme === 'dark' ? 'text-slate-500' : 'text-slate-500'}`}>
<p className="text-xs mt-1 font-mono text-slate-500 dark:text-slate-400">
{log.details}
</p>
)}
@@ -240,7 +207,7 @@ export default function LogsPage() {
</div>
)}
</div>
</div>
</Card>
</div>
)
}