UI-Feinschliff: Settings-Tabs funktionieren, Details-Knopf tut etwas
Ampel / ampel (push) Successful in 33s
Ampel / ampel (push) Successful in 33s
- Settings: Tabs waren reine Dekoration (kein onClick, isActive fest) — jetzt echte Tab-Navigation (Ripping/Verzeichnisse/APIs/Benachrichtigungen) + OMDb-Key-Feld im APIs-Tab - Laufwerk-Details: aufklappbares Panel (Pfad/Modell/Seriennummer/Disc) mit "Rippen starten" (POST /jobs) und "Auswerfen" (Eject-Endpoint) - Placebos raus: 4 tote Schnellaktionen, doppelte Geraete-Sektion mit zweitem totem Details-Knopf, toter Abmelden-Knopf Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Settings, Save, Disc, Cpu, Database, Globe, AlertCircle, CheckCircle } from 'lucide-react'
|
||||
import { Save, Disc, Database, Globe, AlertCircle, CheckCircle } from 'lucide-react'
|
||||
import { api } from '../lib/api'
|
||||
import { useDarkMode } from '../context/ThemeContext'
|
||||
|
||||
interface SettingsState {
|
||||
tmdbApiKey: string
|
||||
tvdbApiKey: string
|
||||
omdbApiKey: string
|
||||
outputDir: string
|
||||
movieDir: string
|
||||
seriesDir: string
|
||||
@@ -19,6 +20,7 @@ interface SettingsState {
|
||||
const defaultSettings: SettingsState = {
|
||||
tmdbApiKey: '',
|
||||
tvdbApiKey: '',
|
||||
omdbApiKey: '',
|
||||
outputDir: '/app/media',
|
||||
movieDir: 'movies',
|
||||
seriesDir: 'series',
|
||||
@@ -29,8 +31,11 @@ const defaultSettings: SettingsState = {
|
||||
notificationWebhook: '',
|
||||
}
|
||||
|
||||
type SettingsTab = 'ripping' | 'verzeichnisse' | 'apis' | 'benachrichtigungen'
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [settings, setSettings] = useState<SettingsState>(defaultSettings)
|
||||
const [activeTab, setActiveTab] = useState<SettingsTab>('ripping')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [saved, setSaved] = useState(false)
|
||||
@@ -90,19 +95,18 @@ export default function SettingsPage() {
|
||||
)}
|
||||
|
||||
<div className={`rounded-xl shadow-sm overflow-hidden transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-800 border border-slate-700' : 'bg-white border border-slate-200'}`}>
|
||||
{/* Tabs */}
|
||||
{/* Tabs — echte Navigation (vorher reine Dekoration ohne onClick) */}
|
||||
<div className={`flex transition-colors duration-300 ${theme === 'dark' ? 'border-slate-700' : 'border-slate-200'} border-b`}>
|
||||
<TabButton icon={Disc} label="Ripping" isActive={true} darkMode={theme === 'dark'} />
|
||||
<TabButton icon={Cpu} label="Verarbeitung" isActive={false} darkMode={theme === 'dark'} />
|
||||
<TabButton icon={Database} label="Datenbank" isActive={false} darkMode={theme === 'dark'} />
|
||||
<TabButton icon={Globe} label="APIs" isActive={false} darkMode={theme === 'dark'} />
|
||||
<TabButton icon={AlertCircle} label="Benachrichtigungen" isActive={false} darkMode={theme === 'dark'} />
|
||||
<TabButton icon={Disc} label="Ripping" isActive={activeTab === 'ripping'} onClick={() => setActiveTab('ripping')} darkMode={theme === 'dark'} />
|
||||
<TabButton icon={Database} label="Verzeichnisse" isActive={activeTab === 'verzeichnisse'} onClick={() => setActiveTab('verzeichnisse')} darkMode={theme === 'dark'} />
|
||||
<TabButton icon={Globe} label="APIs" isActive={activeTab === 'apis'} onClick={() => setActiveTab('apis')} darkMode={theme === 'dark'} />
|
||||
<TabButton icon={AlertCircle} label="Benachrichtigungen" isActive={activeTab === 'benachrichtigungen'} onClick={() => setActiveTab('benachrichtigungen')} darkMode={theme === 'dark'} />
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-8 transition-colors duration-300">
|
||||
{/* Ripping Settings */}
|
||||
<section>
|
||||
{activeTab === 'ripping' && <section>
|
||||
<h2 className={`text-lg font-semibold mb-4 flex items-center gap-2 transition-colors duration-300 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>
|
||||
<Disc size={20} className={theme === 'dark' ? 'text-indigo-400' : 'text-indigo-600'} />
|
||||
Ripping-Einstellungen
|
||||
@@ -160,10 +164,10 @@ export default function SettingsPage() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>}
|
||||
|
||||
{/* Output Directories */}
|
||||
<section>
|
||||
{activeTab === 'verzeichnisse' && <section>
|
||||
<h2 className={`text-lg font-semibold mb-4 flex items-center gap-2 transition-colors duration-300 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>
|
||||
<Database size={20} className={theme === 'dark' ? 'text-indigo-400' : 'text-indigo-600'} />
|
||||
Ausgabeverzeichnisse
|
||||
@@ -223,10 +227,10 @@ export default function SettingsPage() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>}
|
||||
|
||||
{/* API Keys */}
|
||||
<section>
|
||||
{activeTab === 'apis' && <section>
|
||||
<h2 className={`text-lg font-semibold mb-4 flex items-center gap-2 transition-colors duration-300 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>
|
||||
<Globe size={20} className={theme === 'dark' ? 'text-indigo-400' : 'text-indigo-600'} />
|
||||
API-Schlüssel
|
||||
@@ -261,11 +265,27 @@ export default function SettingsPage() {
|
||||
placeholder="Dein TVDb API Key"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 transition-colors duration-300">
|
||||
<label className={`block text-sm font-medium ${theme === 'dark' ? 'text-slate-300' : 'text-slate-700'}`}>
|
||||
OMDb API Key (Fallback)
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={settings.omdbApiKey}
|
||||
onChange={(e) => handleChange('omdbApiKey', e.target.value)}
|
||||
className={`w-full px-3 py-2 ${theme === 'dark' ? 'bg-slate-800 border-slate-600 text-slate-200' : 'bg-white border-slate-300 text-slate-900'} border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all`}
|
||||
placeholder="Dein OMDb API Key"
|
||||
/>
|
||||
<p className={`text-xs ${theme === 'dark' ? 'text-slate-400' : 'text-slate-500'}`}>
|
||||
Zweite Quelle, wenn TMDB nichts findet — kostenloser Key auf <a href="https://www.omdbapi.com/apikey.aspx" target="_blank" rel="noopener noreferrer" className={`${theme === 'dark' ? 'text-indigo-400 hover:underline' : 'text-indigo-600 hover:underline'}`}>omdbapi.com</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>}
|
||||
|
||||
{/* Webhook */}
|
||||
<section>
|
||||
{activeTab === 'benachrichtigungen' && <section>
|
||||
<h2 className={`text-lg font-semibold mb-4 flex items-center gap-2 transition-colors duration-300 ${theme === 'dark' ? 'text-slate-100' : 'text-slate-900'}`}>
|
||||
<AlertCircle size={20} className={theme === 'dark' ? 'text-indigo-400' : 'text-indigo-600'} />
|
||||
Webhook-Benachrichtigungen
|
||||
@@ -286,7 +306,7 @@ export default function SettingsPage() {
|
||||
Optional: URL für Benachrichtigungen bei Job-Ende (Discord, Slack, etc.)
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>}
|
||||
|
||||
{/* Save Button */}
|
||||
<div className={`flex items-center justify-end gap-4 pt-6 transition-colors duration-300 ${theme === 'dark' ? 'border-slate-700' : 'border-slate-200'} border-t`}>
|
||||
@@ -314,10 +334,11 @@ export default function SettingsPage() {
|
||||
)
|
||||
}
|
||||
|
||||
function TabButton({ icon: Icon, label, isActive, darkMode }: { icon: any, label: string, isActive: boolean, darkMode: boolean }) {
|
||||
function TabButton({ icon: Icon, label, isActive, onClick, darkMode }: { icon: any, label: string, isActive: boolean, onClick: () => void, darkMode: boolean }) {
|
||||
const { theme } = useDarkMode()
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`px-6 py-3 text-sm font-medium transition-colors border-b-2 ${
|
||||
isActive
|
||||
? `${darkMode ? 'border-indigo-500 text-indigo-400' : 'border-indigo-600 text-indigo-600'}`
|
||||
|
||||
Reference in New Issue
Block a user