diff --git a/app/(main)/backups/page.tsx b/app/(main)/backups/page.tsx index ab324af5..d49454c0 100644 --- a/app/(main)/backups/page.tsx +++ b/app/(main)/backups/page.tsx @@ -74,6 +74,24 @@ function BackupsContent() { const canWrite = isLive && can("mm:backups:write") const canReadServers = can("mm:servers:read") const [resources, setResources] = useState>(() => ({ backups: unknownResource(), servers: unknownResource(), schedule: unknownResource(), storage: unknownResource() })) + const dialogTrigger = useRef<{ element: HTMLElement; rowId: string | null; label: string | null } | null>(null) + const refreshTrigger = useRef(null) + const captureDialogFocus = () => { + const element = document.activeElement + dialogTrigger.current = element instanceof HTMLElement + ? { element, rowId: element.closest("[data-row-id]")?.getAttribute("data-row-id") ?? null, label: element.getAttribute("aria-label") } + : null + } + const returnDialogFocus = () => { + const trigger = dialogTrigger.current + if (trigger?.element.isConnected) return trigger.element + // Cell renderers can remount when operation state changes; resolve the same row/action. + if (trigger?.rowId && trigger.label) { + const replacement = document.querySelector(`[data-row-id="${CSS.escape(trigger.rowId)}"] button[aria-label="${CSS.escape(trigger.label)}"]`) + if (replacement) return replacement + } + return refreshTrigger.current + } const reader = useRef(null) const revision = useRef(0) const alive = useRef(true) @@ -403,7 +421,7 @@ function BackupsContent() { {!canRead &&

Нет права mm:backups:read

} {isLive && !canReadServers &&

Каталог устройств недоступен без mm:servers:read.

} {isLive && canRead &&
- + {loading ? "Обновление данных…" : resources.backups.receivedAt ? "Получено: " + new Date(resources.backups.receivedAt).toLocaleString("ru-RU") : "Резервные копии пока неизвестны"}
} {isLive && (Object.keys(resources) as Resource[]).filter(resource => resources[resource].error).map(resource => @@ -493,8 +511,8 @@ function BackupsContent() { filters={advancedFilters} onFiltersChange={setAdvancedFilters} onDownload={handleDownload} - onRestore={backup => { setOpError(null); setRestoreTarget(backup) }} - onDelete={backup => { setOpError(null); setDeleteTarget(backup) }} + onRestore={backup => { captureDialogFocus(); setOpError(null); setRestoreTarget(backup) }} + onDelete={backup => { captureDialogFocus(); setOpError(null); setDeleteTarget(backup) }} onCreate={() => openCreate(false)} canCreate={canCreate} canWrite={canWrite && !opBusy && !scheduleSaveBusy && !backupJobId} @@ -578,6 +596,7 @@ function BackupsContent() { /> setDeleteTarget(null)} /> void onCancel: () => void + returnFocus?: () => HTMLElement | null }) { return ( { if (!v && !busy) onCancel() }}> - + @@ -61,6 +63,7 @@ export function BackupRestoreDialog({ error, onConfirm, onCancel, + returnFocus, }: { backup: Backup | null busy: boolean @@ -68,10 +71,11 @@ export function BackupRestoreDialog({ error?: string | null onConfirm: () => void onCancel: () => void + returnFocus?: () => HTMLElement | null }) { return ( { if (!v && !busy) onCancel() }}> - + diff --git a/components/data-grids/backups-data-grid.tsx b/components/data-grids/backups-data-grid.tsx index ce58128f..b5d38680 100644 --- a/components/data-grids/backups-data-grid.tsx +++ b/components/data-grids/backups-data-grid.tsx @@ -1,8 +1,8 @@ "use client" -import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" +import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" -import { useMemo, type ReactNode } from "react" +import { useEffect, useMemo, type ReactNode } from "react" import { type ColumnDef, useTable, @@ -10,9 +10,9 @@ import { import type { Backup } from "@/lib/data" import { Button } from "@/components/ui/button" import { Badge } from "@/components/reui/badge" -import { IconTile } from "@/components/reui/icon-tile" import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell" import { + DEFAULT_DATA_GRID_LAYOUT, DATA_GRID_CELL_PAD, DATA_GRID_CELL_PAD_FIRST, DATA_GRID_CELL_PAD_LAST, @@ -20,7 +20,6 @@ import { import { DataGridSortHeader } from "@/components/data-grids/shared/data-grid-sort-header" import { EmptyState } from "@/components/empty-state" import { - CloudIcon, DownloadIcon, HardDriveIcon, RefreshCwIcon, @@ -63,20 +62,12 @@ function BackupsDataGrid({ { id: "filename", accessorKey: "filename", - size: 280, + size: 110, header: ({ column }) => , cell: ({ row }) => { const b = row.original - const inS3 = b.storage === "s3" || b.storage === "both" return (
-
{b.filename} {b.uploadError ? ( @@ -190,12 +181,12 @@ function BackupsDataGrid({ cell: ({ row }) => { const b = row.original return ( -
+