UI-Feinschliff: Settings-Tabs funktionieren, Details-Knopf tut etwas
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:
Hitonabi
2026-07-23 15:30:09 +02:00
parent 5528e0f652
commit 1aa51ca943
4 changed files with 123 additions and 136 deletions
+78 -2
View File
@@ -16,6 +16,9 @@ interface Device {
export default function DeviceDiscovery() {
const [devices, setDevices] = useState<Device[]>([])
const [loading, setLoading] = useState(true)
const [expandedId, setExpandedId] = useState<string | null>(null)
const [actionFeedback, setActionFeedback] = useState<string | null>(null)
const [actionBusy, setActionBusy] = useState(false)
const { theme } = useDarkMode()
const refreshDevices = async () => {
@@ -29,6 +32,33 @@ export default function DeviceDiscovery() {
}
}
const startRip = async (device: Device) => {
setActionBusy(true)
setActionFeedback(null)
try {
const response = await api.post('/jobs', { device_path: device.path })
setActionFeedback(`✓ Job angelegt (${response.data.id.slice(0, 8)}…) — Fortschritt im Dashboard`)
} catch (error: any) {
setActionFeedback(`${error?.response?.data?.detail || 'Job konnte nicht angelegt werden'}`)
} finally {
setActionBusy(false)
}
}
const ejectDisc = async (device: Device) => {
setActionBusy(true)
setActionFeedback(null)
try {
await api.post(`/devices/${device.id}/eject`)
setActionFeedback('✓ Disc ausgeworfen')
refreshDevices()
} catch (error: any) {
setActionFeedback(`${error?.response?.data?.detail || 'Auswurf fehlgeschlagen'}`)
} finally {
setActionBusy(false)
}
}
useEffect(() => {
refreshDevices()
const interval = setInterval(refreshDevices, 5000)
@@ -146,10 +176,56 @@ export default function DeviceDiscovery() {
</div>
</div>
</div>
<button className={`text-sm font-medium transition-colors ${theme === 'dark' ? 'text-indigo-400 hover:text-indigo-300' : 'text-indigo-600 hover:text-indigo-700'}`}>
Details
<button
onClick={() => {
setExpandedId(expandedId === device.id ? null : device.id)
setActionFeedback(null)
}}
className={`text-sm font-medium transition-colors ${theme === 'dark' ? 'text-indigo-400 hover:text-indigo-300' : 'text-indigo-600 hover:text-indigo-700'}`}
>
{expandedId === device.id ? 'Schließen' : 'Details'}
</button>
</div>
{expandedId === device.id && (
<div className={`mt-4 pt-4 border-t space-y-3 ${theme === 'dark' ? 'border-slate-700' : 'border-slate-200'}`}>
<div className="grid grid-cols-2 gap-x-6 gap-y-1 text-sm">
<span className={theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}>Gerätepfad</span>
<span className={`font-mono ${theme === 'dark' ? 'text-slate-300' : 'text-slate-700'}`}>{device.path}</span>
<span className={theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}>Modell</span>
<span className={theme === 'dark' ? 'text-slate-300' : 'text-slate-700'}>{device.model || 'unbekannt'}</span>
<span className={theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}>Seriennummer</span>
<span className={`font-mono text-xs ${theme === 'dark' ? 'text-slate-300' : 'text-slate-700'}`}>{device.serial || '—'}</span>
<span className={theme === 'dark' ? 'text-slate-500' : 'text-slate-400'}>Disc</span>
<span className={theme === 'dark' ? 'text-slate-300' : 'text-slate-700'}>
{device.status === 'ready' ? `eingelegt (${device.type.toUpperCase()})` : 'keine'}
</span>
</div>
<div className="flex items-center gap-3 pt-1">
<button
onClick={() => startRip(device)}
disabled={actionBusy || device.status !== 'ready'}
className="px-4 py-2 rounded-lg bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
Rippen starten
</button>
<button
onClick={() => ejectDisc(device)}
disabled={actionBusy || device.status !== 'ready'}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${theme === 'dark' ? 'bg-slate-700 text-slate-200 hover:bg-slate-600' : 'bg-slate-200 text-slate-700 hover:bg-slate-300'}`}
>
Auswerfen
</button>
</div>
{actionFeedback && (
<p className={`text-sm ${actionFeedback.startsWith('✓') ? 'text-emerald-500' : 'text-rose-500'}`}>
{actionFeedback}
</p>
)}
</div>
)}
</div>
))}
</div>