backup-restore: Toast bei Erfolg, ausführliche Fehlermeldung im Dialog
Vorher: nach Klick auf "Ja, wiederherstellen" passierte UI-seitig
einfach … nichts Sichtbares außer dass der Dialog (irgendwann) zuging.
Bei einem 500er-Fehler blieb der Dialog offen ohne erkennbare
Begründung – der User dachte, die Aktion sei nicht durchgelaufen,
und klickte teils nochmal.
Jetzt:
- Erfolg → Dialog zu, grüne Toast-Meldung mit der Backend-Response
("X Datensätze und Y Dateien wiederhergestellt"), 6s sichtbar.
- Fehler → Dialog bleibt offen mit roter Detail-Box drinnen,
Backend-Felder error + details zusammengefügt, plus
Toast-Notification 8s. Button-Label wird zu "Erneut versuchen",
Sekundär-Button zu "Schließen".
- Beim Schließen wird mutation.reset() aufgerufen, damit beim
nächsten Öffnen keine alten Fehler dranhängen.
extractError-Helper ist allgemein – kann später für andere
Backup-Aktionen wiederverwendet werden.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,21 @@
|
|||||||
import { useState, useRef } from 'react';
|
import { useState, useRef } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
import { Database, Download, Upload, Trash2, RefreshCw, HardDrive, Clock, FileText, FolderOpen, Archive, AlertTriangle, Bomb } from 'lucide-react';
|
import { Database, Download, Upload, Trash2, RefreshCw, HardDrive, Clock, FileText, FolderOpen, Archive, AlertTriangle, Bomb } from 'lucide-react';
|
||||||
import { backupApi, BackupInfo, getAccessToken } from '../../services/api';
|
import { backupApi, BackupInfo, getAccessToken } from '../../services/api';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import Button from '../../components/ui/Button';
|
import Button from '../../components/ui/Button';
|
||||||
|
|
||||||
|
function extractError(err: any): string {
|
||||||
|
const data = err?.response?.data;
|
||||||
|
if (data) {
|
||||||
|
if (data.error && data.details) return `${data.error}: ${data.details}`;
|
||||||
|
if (data.error) return data.error;
|
||||||
|
if (typeof data === 'string') return data;
|
||||||
|
}
|
||||||
|
return err?.message || 'Unbekannter Fehler';
|
||||||
|
}
|
||||||
|
|
||||||
export default function DatabaseBackup() {
|
export default function DatabaseBackup() {
|
||||||
const [showRestoreConfirm, setShowRestoreConfirm] = useState<string | null>(null);
|
const [showRestoreConfirm, setShowRestoreConfirm] = useState<string | null>(null);
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState<string | null>(null);
|
||||||
@@ -34,9 +45,17 @@ export default function DatabaseBackup() {
|
|||||||
// Backup wiederherstellen
|
// Backup wiederherstellen
|
||||||
const restoreMutation = useMutation({
|
const restoreMutation = useMutation({
|
||||||
mutationFn: (name: string) => backupApi.restore(name),
|
mutationFn: (name: string) => backupApi.restore(name),
|
||||||
onSuccess: () => {
|
onSuccess: (response: any) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['backups'] });
|
queryClient.invalidateQueries({ queryKey: ['backups'] });
|
||||||
setShowRestoreConfirm(null);
|
setShowRestoreConfirm(null);
|
||||||
|
// Backend liefert message: "X Datensätze und Y Dateien wiederhergestellt"
|
||||||
|
const msg = response?.message || 'Backup erfolgreich wiederhergestellt.';
|
||||||
|
toast.success(msg, { duration: 6000 });
|
||||||
|
},
|
||||||
|
// Bei Fehler bleibt das Dialog absichtlich offen, damit der User
|
||||||
|
// die Detail-Message sehen + ggf. erneut versuchen kann.
|
||||||
|
onError: (err: any) => {
|
||||||
|
toast.error(extractError(err), { duration: 8000 });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -329,13 +348,24 @@ export default function DatabaseBackup() {
|
|||||||
<strong>Achtung:</strong> Bestehende Daten und Dokumente werden mit dem Backup-Stand überschrieben.
|
<strong>Achtung:</strong> Bestehende Daten und Dokumente werden mit dem Backup-Stand überschrieben.
|
||||||
Dies kann nicht rückgängig gemacht werden.
|
Dies kann nicht rückgängig gemacht werden.
|
||||||
</p>
|
</p>
|
||||||
|
{restoreMutation.isError && (
|
||||||
|
<div className="mb-4 bg-red-50 border border-red-200 text-red-800 text-sm p-3 rounded-lg">
|
||||||
|
<div className="font-semibold mb-1">Wiederherstellung fehlgeschlagen</div>
|
||||||
|
<div className="whitespace-pre-wrap break-words">
|
||||||
|
{extractError(restoreMutation.error)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex justify-end gap-3">
|
<div className="flex justify-end gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => setShowRestoreConfirm(null)}
|
onClick={() => {
|
||||||
|
setShowRestoreConfirm(null);
|
||||||
|
restoreMutation.reset();
|
||||||
|
}}
|
||||||
disabled={restoreMutation.isPending}
|
disabled={restoreMutation.isPending}
|
||||||
>
|
>
|
||||||
Abbrechen
|
{restoreMutation.isError ? 'Schließen' : 'Abbrechen'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -347,6 +377,8 @@ export default function DatabaseBackup() {
|
|||||||
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
||||||
Wird wiederhergestellt...
|
Wird wiederhergestellt...
|
||||||
</>
|
</>
|
||||||
|
) : restoreMutation.isError ? (
|
||||||
|
'Erneut versuchen'
|
||||||
) : (
|
) : (
|
||||||
'Ja, wiederherstellen'
|
'Ja, wiederherstellen'
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user