fix(ui): сохранять подтверждение восстановления при ошибке
This commit is contained in:
1 parent
221d6db10c
commit
1d1de12c58
12 files changed
+134
-35
No files matched your search
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user