fix(ui): сохранять подтверждение восстановления при ошибке

This commit is contained in:
Denozordec committed 2026-09-29 13:37:32 +07:00
1 parent 221d6db10c
commit 1d1de12c58
12 files changed
+134 -35

No files matched your search

+47 -21
View File
@@ -1,6 +1,6 @@
"use client"
import { useMemo, useState, type ComponentProps } from "react"
import { useMemo, useRef, useState, type ComponentProps } from "react"
import { HistoryIcon, LoaderCircleIcon, RotateCcwIcon } from "lucide-react"
import {
Sheet,
@@ -78,12 +78,38 @@ export function ConfigHistorySheet({
onRestore: (id: string) => Promise<void> | void
}) {
const [pending, setPending] = useState<ConfigRevisionDto | null>(null)
const restoreLock = useRef(false)
const [submitting, setSubmitting] = useState(false)
const [restoreError, setRestoreError] = useState<string | null>(null)
const busy = restoring || submitting
async function confirmRestore() {
if (!pending || restoreLock.current || restoring) return
restoreLock.current = true
setSubmitting(true)
setRestoreError(null)
try {
await onRestore(pending.id)
setPending(null)
} catch (error) {
setRestoreError(error instanceof Error ? error.message : "Не удалось восстановить версию")
} finally {
restoreLock.current = false
setSubmitting(false)
}
}
const newestFirst = useMemo(() => revisions, [revisions])
return (
<>
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="right" className="w-full sm:max-w-md flex flex-col gap-0 p-0">
<Sheet open={open} onOpenChange={v => {
if (restoreLock.current || restoring) return
if (!v) {
setPending(null)
setRestoreError(null)
}
onOpenChange(v)
}}>
<SheetContent showCloseButton={!busy} side="right" className="w-full sm:max-w-md flex flex-col gap-0 p-0">
<SheetHeader className="px-6 pt-6 pb-4 border-b shrink-0">
<SheetTitle>{title}</SheetTitle>
<SheetDescription>
@@ -92,7 +118,7 @@ export function ConfigHistorySheet({
</SheetHeader>
<div className="flex-1 min-h-0 overflow-y-auto px-6 py-5">
{loading ? (
<div className="flex items-center justify-center gap-2 py-16 text-sm text-muted-foreground">
<div role="status" className="flex items-center justify-center gap-2 py-16 text-sm text-muted-foreground">
<LoaderCircleIcon className="size-4 animate-spin" />
Загрузка истории…
</div>
@@ -127,8 +153,8 @@ export function ConfigHistorySheet({
type="button"
variant="outline"
size="sm"
disabled={restoring}
onClick={() => setPending(rev)}
disabled={busy}
onClick={() => { setRestoreError(null); setPending(rev) }}
>
<RotateCcwIcon className="size-3.5" />
Откатить
@@ -144,7 +170,7 @@ export function ConfigHistorySheet({
</SheetContent>
</Sheet>
<AlertDialog open={Boolean(pending)} onOpenChange={(v) => { if (!v && !restoring) setPending(null) }}>
<AlertDialog open={Boolean(pending)} onOpenChange={(v) => { if (!v && !restoreLock.current && !restoring) { setPending(null); setRestoreError(null) } }}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia className="bg-warning/10 text-warning">
@@ -156,25 +182,25 @@ export function ConfigHistorySheet({
На CHR будут применены {pending?.itemCount ?? 0} {itemLabel} от{" "}
{pending ? formatWhen(pending.createdAt) : ""}.
</span>
<Alert variant="warning">
<TriangleAlertIcon />
<AlertTitle>Изменятся только объекты MikrotikManager</AlertTitle>
<AlertDescription>
Чужие правила и маршруты на роутере не удаляются.
</AlertDescription>
</Alert>
</AlertDialogDescription>
<Alert variant="warning">
<TriangleAlertIcon />
<AlertTitle>Изменятся только объекты MikrotikManager</AlertTitle>
<AlertDescription>
Чужие правила и маршруты на роутере не удаляются.
</AlertDescription>
</Alert>
</AlertDialogHeader>
{busy && <p role="status" className="text-sm text-muted-foreground">Ожидание результата восстановления…</p>}
{restoreError && <p role="alert" className="text-sm text-destructive">{restoreError}</p>}
<AlertDialogFooter>
<AlertDialogCancel disabled={restoring} onClick={() => setPending(null)}>Отмена</AlertDialogCancel>
<AlertDialogCancel disabled={busy} onClick={() => setPending(null)}>Отмена</AlertDialogCancel>
<AlertDialogAction
disabled={restoring || !pending}
onClick={() => {
if (!pending) return
void Promise.resolve(onRestore(pending.id)).finally(() => setPending(null))
}}
disabled={busy || !pending}
type="button"
onClick={() => void confirmRestore()}
>
{restoring ? <LoaderCircleIcon className="size-4 animate-spin" /> : null}
{busy ? <LoaderCircleIcon className="size-4 animate-spin" /> : null}
Откатить
</AlertDialogAction>
</AlertDialogFooter>