import { useEffect, useRef, useState } from 'react'; import { Card } from 'primereact/card'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Tag } from 'primereact/tag'; import { Button } from 'primereact/button'; import { Toast } from 'primereact/toast'; import { useNavigate } from 'react-router-dom'; import { api } from '../api/client'; import { confirmModal } from '../utils/confirmModal'; function formatDetectedMediaType(value) { const mediaType = String(value || '').trim().toLowerCase(); if (mediaType === 'bluray') { return 'Blu-ray'; } if (mediaType === 'dvd') { return 'DVD'; } if (mediaType === 'cd') { return 'CD'; } if (mediaType === 'audiobook') { return 'Audiobook'; } return 'Sonstiges'; } export default function DatabasePage() { const navigate = useNavigate(); const [orphanRows, setOrphanRows] = useState([]); const [orphanLoading, setOrphanLoading] = useState(false); const [orphanImportBusyPath, setOrphanImportBusyPath] = useState(null); const [orphanDeleteBusyPath, setOrphanDeleteBusyPath] = useState(null); const toastRef = useRef(null); const orphanLoadRequestRef = useRef(0); const loadOrphans = async (options = {}) => { const requestId = orphanLoadRequestRef.current + 1; orphanLoadRequestRef.current = requestId; const silent = Boolean(options?.silent); if (!silent) { setOrphanLoading(true); } try { const response = await api.getOrphanRawFolders(); if (orphanLoadRequestRef.current !== requestId) { return; } setOrphanRows(Array.isArray(response?.rows) ? response.rows : []); } catch (error) { if (orphanLoadRequestRef.current !== requestId) { return; } toastRef.current?.show({ severity: 'error', summary: 'RAW-Prüfung fehlgeschlagen', detail: error.message }); } finally { if (orphanLoadRequestRef.current === requestId && !silent) { setOrphanLoading(false); } } }; useEffect(() => { void loadOrphans(); }, []); const handleImportOrphanRaw = async (row) => { const target = row?.rawPath || row?.folderName || '-'; const confirmed = await confirmModal({ header: 'Historienjob anlegen', message: `Für RAW-Ordner "${target}" einen neuen Historienjob anlegen?`, acceptLabel: 'Job anlegen', rejectLabel: 'Abbrechen' }); if (!confirmed) { return; } setOrphanImportBusyPath(row.rawPath); try { const response = await api.importOrphanRawFolder(row.rawPath); const newJobId = response?.job?.id; const activationStarted = Boolean(response?.activation?.started); const activationError = String(response?.activationError || '').trim(); const importedRawPath = String(row?.rawPath || '').trim(); if (importedRawPath) { // Immediate UX feedback: imported orphan should disappear without a full page reload. setOrphanRows((previousRows) => ( (Array.isArray(previousRows) ? previousRows : []).filter((entry) => String(entry?.rawPath || '').trim() !== importedRawPath) )); } toastRef.current?.show({ severity: 'success', summary: activationStarted ? 'Analyse gestartet' : 'Job angelegt', detail: activationStarted ? (newJobId ? `Job #${newJobId} läuft jetzt im Ripper-View.` : 'Job läuft jetzt im Ripper-View.') : (newJobId ? `Historieneintrag #${newJobId} erstellt.` : 'Historieneintrag wurde erstellt.'), life: 3500 }); if (activationError) { toastRef.current?.show({ severity: 'warn', summary: 'Analyse-Start fehlgeschlagen', detail: activationError, life: 5200 }); } // Some workflows update job linkage milliseconds later (e.g. follow-up analyze start). // Recheck a few times in background to converge UI without manual refresh. const refreshDelaysMs = [0, 600, 1800]; for (const delayMs of refreshDelaysMs) { if (delayMs > 0) { await new Promise((resolve) => window.setTimeout(resolve, delayMs)); } await loadOrphans({ silent: true }); } // Jump to the Ripper view immediately after creating a job from /database. navigate('/ripper'); } catch (error) { toastRef.current?.show({ severity: 'error', summary: 'Import fehlgeschlagen', detail: error.message, life: 4500 }); } finally { setOrphanImportBusyPath(null); } }; const handleDeleteOrphanRaw = async (row) => { const target = String(row?.rawPath || row?.folderName || '-').trim() || '-'; const confirmed = await confirmModal({ header: 'RAW löschen', message: `RAW-Ordner "${target}" wirklich löschen?\n` + 'Der Ordner wird dauerhaft aus dem Dateisystem entfernt und verschwindet aus der /database-Liste.', acceptLabel: 'RAW löschen', rejectLabel: 'Abbrechen', danger: true }); if (!confirmed) { return; } setOrphanDeleteBusyPath(row.rawPath); try { const response = await api.deleteOrphanRawFolder(row.rawPath); const deletedRawPath = String(response?.rawPath || row?.rawPath || '').trim(); const deletedFiles = Number(response?.filesDeleted || 0); const deletedDirs = Number(response?.dirsRemoved || 0); if (deletedRawPath) { setOrphanRows((previousRows) => ( (Array.isArray(previousRows) ? previousRows : []).filter((entry) => String(entry?.rawPath || '').trim() !== deletedRawPath) )); } toastRef.current?.show({ severity: 'success', summary: 'RAW gelöscht', detail: `Ordner entfernt | Dateien: ${deletedFiles}, Ordner: ${deletedDirs}`, life: 3500 }); await loadOrphans({ silent: true }); } catch (error) { toastRef.current?.show({ severity: 'error', summary: 'Löschen fehlgeschlagen', detail: error.message, life: 4500 }); } finally { setOrphanDeleteBusyPath(null); } }; const orphanTitleBody = (row) => (
{row.title || '-'}
{row.year || '-'} | {row.imdbId || '-'}
); const orphanMediaBody = (row) => ( ); const orphanPathBody = (row) => (
{row.rawPath}
); const orphanActionBody = (row) => (
); return (
); }