Files
ripster/frontend/src/pages/DatabasePage.jsx
T
2026-04-10 18:44:41 +00:00

151 lines
4.7 KiB
React

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 { 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 [orphanRows, setOrphanRows] = useState([]);
const [orphanLoading, setOrphanLoading] = useState(false);
const [orphanImportBusyPath, setOrphanImportBusyPath] = useState(null);
const toastRef = useRef(null);
const loadOrphans = async () => {
setOrphanLoading(true);
try {
const response = await api.getOrphanRawFolders();
setOrphanRows(response.rows || []);
} catch (error) {
toastRef.current?.show({ severity: 'error', summary: 'RAW-Prüfung fehlgeschlagen', detail: error.message });
} finally {
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;
toastRef.current?.show({
severity: 'success',
summary: 'Job angelegt',
detail: newJobId ? `Historieneintrag #${newJobId} erstellt.` : 'Historieneintrag wurde erstellt.',
life: 3500
});
await loadOrphans();
} catch (error) {
toastRef.current?.show({
severity: 'error',
summary: 'Import fehlgeschlagen',
detail: error.message,
life: 4500
});
} finally {
setOrphanImportBusyPath(null);
}
};
const orphanTitleBody = (row) => (
<div>
<div><strong>{row.title || '-'}</strong></div>
<small>{row.year || '-'} | {row.imdbId || '-'}</small>
</div>
);
const orphanMediaBody = (row) => (
<Tag value={formatDetectedMediaType(row?.detectedMediaType)} />
);
const orphanPathBody = (row) => (
<div className="orphan-path-cell">
{row.rawPath}
</div>
);
const orphanActionBody = (row) => (
<Button
label="Job anlegen"
icon="pi pi-plus"
size="small"
onClick={() => handleImportOrphanRaw(row)}
loading={orphanImportBusyPath === row.rawPath}
disabled={Boolean(orphanImportBusyPath)}
/>
);
return (
<div className="page-grid">
<Toast ref={toastRef} />
<Card
title="Gefundene RAW-Einträge"
subTitle="Es werden RAW-Ordner ohne zugehörigen Historienjob aus Settings- und Default-RAW-Pfaden angezeigt."
>
<div className="table-filters">
<Button
label="RAW prüfen"
icon="pi pi-search"
onClick={loadOrphans}
loading={orphanLoading}
disabled={Boolean(orphanImportBusyPath)}
/>
<Tag value={`${orphanRows.length} gefunden`} severity={orphanRows.length > 0 ? 'warning' : 'success'} />
</div>
<div className="table-scroll-wrap table-scroll-wide">
<DataTable
value={orphanRows}
dataKey="rawPath"
paginator
rows={10}
loading={orphanLoading}
emptyMessage="Keine verwaisten RAW-Ordner gefunden"
responsiveLayout="scroll"
>
<Column field="folderName" header="RAW-Ordner" style={{ minWidth: '18rem' }} />
<Column header="Medium" body={orphanMediaBody} style={{ width: '10rem' }} />
<Column header="Titel" body={orphanTitleBody} style={{ minWidth: '14rem' }} />
<Column field="entryCount" header="Dateien" style={{ width: '8rem' }} />
<Column header="Pfad" body={orphanPathBody} style={{ minWidth: '22rem' }} />
<Column field="lastModifiedAt" header="Geändert" style={{ width: '16rem' }} />
<Column header="Aktion" body={orphanActionBody} style={{ width: '10rem' }} />
</DataTable>
</div>
</Card>
</div>
);
}