feat(ui): Toast-Feedback ueberall, Job-Detail-Popup, System-Tab, Wizard-Media-Server, Favicon, Footer

- ToastContext: jede Aktion quittiert sichtbar ('Erfolgreich aktualisiert'
  ploppt oben rechts rein) — Refresh-Knoepfe drehen jetzt wirklich
  (Laufwerke, Logs, Worker, Speicherziele).
- JobDetailModal: Klick auf Titel in 'Neueste Jobs' -> Poster, Jahr,
  Beschreibung, Genres, Ablagepfad, Fehler.
- Logs: Filter-Pills um Warnung + Fehler ergaenzt (vorher nur
  Alle/Info/Erfolg).
- Speicherziele: Benutzer/Passwort VOR dem 'Freigaben auflisten'-Knopf
  (Windows/NAS lehnen Gast-Abfragen ab — genau daher kam das
  NT_STATUS_ACCESS_DENIED), Hinweistext, Toasts beim Einhaengen/Entfernen.
- Datei-Browser (Speicherziele + Rip-Ziel-Dialog) zeigt Dateien grau mit
  Groesse — Ordner wirkten faelschlich leer.
- Einstellungen: neuer System-Tab (Werkzeug-Versionen je Worker, Key-Status,
  Plattenplatz, MakeMKV-Beta-Key-Feld), Media-Server-Wahl im Ripping-Tab,
  Arbeitsverzeichnis im Verarbeitung-Tab, Benachrichtigungs-Anleitung
  (Discord/ntfy/Slack) + 'Test senden'.
- First-Run-Wizard: Media-Server-Schritt (Jellyfin/Emby/Kodi/Plex/Keins).
- Favicon (Disc im Logo-Verlauf, public/favicon.svg — /vite.svg war 404)
  + Footer 'Created with (Herz) by LucyAI, Claude and KrBrZ'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-24 08:55:42 +02:00
co-authored by Claude Fable 5
parent e032a9df1c
commit 13c1ae6064
16 changed files with 722 additions and 76 deletions
+29 -13
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { Folder, FolderOpen, ArrowUp, CheckCircle } from 'lucide-react'
import { Folder, FolderOpen, File, ArrowUp, CheckCircle } from 'lucide-react'
import { api } from '../lib/api'
import { useDarkMode } from '../context/ThemeContext'
@@ -34,6 +34,7 @@ export default function RipTargetModal({ isOpen, onClose, onSave }: RipTargetMod
const [browsePath, setBrowsePath] = useState<string>('/app/media')
const [browseParent, setBrowseParent] = useState<string | null>(null)
const [browseDirs, setBrowseDirs] = useState<BrowseDir[]>([])
const [browseFiles, setBrowseFiles] = useState<{ name: string, size_mb: number | null }[]>([])
const [customPath, setCustomPath] = useState('')
const { theme } = useDarkMode()
@@ -58,8 +59,10 @@ export default function RipTargetModal({ isOpen, onClose, onSave }: RipTargetMod
setBrowsePath(r.data.path)
setBrowseParent(r.data.parent)
setBrowseDirs(r.data.dirs)
setBrowseFiles(r.data.files || [])
} catch {
setBrowseDirs([])
setBrowseFiles([])
}
}
@@ -134,19 +137,32 @@ export default function RipTargetModal({ isOpen, onClose, onSave }: RipTargetMod
</button>
</div>
<div className="max-h-40 overflow-y-auto">
{browseDirs.length === 0 ? (
<p className={`text-xs px-3 py-3 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>Keine Unterordner</p>
{browseDirs.length === 0 && browseFiles.length === 0 ? (
<p className={`text-xs px-3 py-3 ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>Ordner ist leer</p>
) : (
browseDirs.map(d => (
<button
key={d.path}
onClick={() => laden(d.path)}
className={`w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition-colors ${theme === 'dark' ? 'text-slate-300 hover:bg-slate-700' : 'text-slate-700 hover:bg-slate-100'}`}
>
<Folder size={15} className={theme === 'dark' ? 'text-slate-500' : 'text-slate-400'} />
{d.name}
</button>
))
<>
{browseDirs.map(d => (
<button
key={d.path}
onClick={() => laden(d.path)}
className={`w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition-colors ${theme === 'dark' ? 'text-slate-300 hover:bg-slate-700' : 'text-slate-700 hover:bg-slate-100'}`}
>
<Folder size={15} className={theme === 'dark' ? 'text-slate-500' : 'text-slate-400'} />
{d.name}
</button>
))}
{browseFiles.map(f => (
<div key={f.name} className={`flex items-center gap-2 px-3 py-1.5 text-sm ${theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}`}>
<File size={14} className="flex-shrink-0" />
<span className="truncate">{f.name}</span>
{f.size_mb != null && (
<span className="ml-auto text-xs flex-shrink-0">
{f.size_mb >= 1024 ? `${(f.size_mb / 1024).toFixed(1)} GB` : `${f.size_mb} MB`}
</span>
)}
</div>
))}
</>
)}
</div>
</div>