refactor: optimistic ui when deleting a snapshot (#515)
This commit is contained in:
parent
bad944a232
commit
8e5eb68935
4 changed files with 56 additions and 12 deletions
|
|
@ -27,19 +27,26 @@ import {
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||
import { formatDuration } from "~/utils/utils";
|
||||
import { formatDateTime } from "~/client/lib/datetime";
|
||||
import { deleteSnapshotsMutation, tagSnapshotsMutation } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
import {
|
||||
deleteSnapshotsMutation,
|
||||
listSnapshotsQueryKey,
|
||||
tagSnapshotsMutation,
|
||||
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||
import { parseError } from "~/client/lib/errors";
|
||||
import type { BackupSchedule, Snapshot } from "../lib/types";
|
||||
import { cn } from "../lib/utils";
|
||||
import { Link, useNavigate } from "@tanstack/react-router";
|
||||
import type { ListSnapshotsData } from "~/client/api-client/types.gen";
|
||||
import type { Options } from "~/client/api-client/client/types.gen";
|
||||
|
||||
type Props = {
|
||||
snapshots: Snapshot[];
|
||||
backups: BackupSchedule[];
|
||||
repositoryId: string;
|
||||
listSnapshotsQueryOptions: Options<ListSnapshotsData>;
|
||||
};
|
||||
|
||||
export const SnapshotsTable = ({ snapshots, repositoryId, backups }: Props) => {
|
||||
export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshotsQueryOptions }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
|
@ -50,8 +57,16 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups }: Props) => {
|
|||
|
||||
const deleteSnapshots = useMutation({
|
||||
...deleteSnapshotsMutation(),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["listSnapshots"] });
|
||||
onSuccess: (_data, variables) => {
|
||||
const snapshotIds = variables.body?.snapshotIds ?? [];
|
||||
const queryKey = listSnapshotsQueryKey(listSnapshotsQueryOptions);
|
||||
|
||||
queryClient.setQueryData<Snapshot[]>(queryKey, (old) => {
|
||||
if (!old) return old;
|
||||
return old.filter((snapshot) => !snapshotIds.includes(snapshot.short_id));
|
||||
});
|
||||
|
||||
void queryClient.invalidateQueries({ queryKey });
|
||||
setShowBulkDeleteConfirm(false);
|
||||
setSelectedIds(new Set());
|
||||
},
|
||||
|
|
@ -63,7 +78,6 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups }: Props) => {
|
|||
setShowReTagDialog(false);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["listSnapshots"] });
|
||||
setShowReTagDialog(false);
|
||||
setSelectedIds(new Set());
|
||||
setTargetScheduleId("");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useId, useState } from "react";
|
||||
import { useQuery, useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useQuery, useMutation, useSuspenseQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
import { toast } from "sonner";
|
||||
import { Save, X } from "lucide-react";
|
||||
import { Button } from "~/client/components/ui/button";
|
||||
|
|
@ -18,6 +19,7 @@ import {
|
|||
runBackupNowMutation,
|
||||
deleteBackupScheduleMutation,
|
||||
listSnapshotsOptions,
|
||||
listSnapshotsQueryKey,
|
||||
updateBackupScheduleMutation,
|
||||
stopBackupMutation,
|
||||
deleteSnapshotMutation,
|
||||
|
|
@ -38,6 +40,7 @@ import type {
|
|||
Repository,
|
||||
ScheduleMirror,
|
||||
ScheduleNotification,
|
||||
Snapshot,
|
||||
} from "~/client/lib/types";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
|
|
@ -50,15 +53,18 @@ type Props = {
|
|||
mirrors: ScheduleMirror[];
|
||||
};
|
||||
scheduleId: string;
|
||||
initialSnapshotId?: string;
|
||||
};
|
||||
|
||||
export function ScheduleDetailsPage(props: Props) {
|
||||
const { loaderData, scheduleId } = props;
|
||||
const { loaderData, scheduleId, initialSnapshotId } = props;
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const searchParams = useSearch({ from: "/(dashboard)/backups/$backupId/" });
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const formId = useId();
|
||||
const [selectedSnapshotId, setSelectedSnapshotId] = useState<string>();
|
||||
const [selectedSnapshotId, setSelectedSnapshotId] = useState<string | undefined>(initialSnapshotId);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [snapshotToDelete, setSnapshotToDelete] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -118,13 +124,25 @@ export function ScheduleDetailsPage(props: Props) {
|
|||
},
|
||||
});
|
||||
|
||||
const listSnapshotsQueryOptions = { path: { id: schedule.repository.id }, query: { backupId: schedule.shortId } };
|
||||
|
||||
const deleteSnapshot = useMutation({
|
||||
...deleteSnapshotMutation(),
|
||||
onSuccess: () => {
|
||||
onSuccess: (_data, variables) => {
|
||||
const snapshotId = variables.path.snapshotId;
|
||||
const queryKey = listSnapshotsQueryKey(listSnapshotsQueryOptions);
|
||||
|
||||
queryClient.setQueryData<Snapshot[]>(queryKey, (old) => {
|
||||
if (!old) return old;
|
||||
return old.filter((snapshot) => snapshot.short_id !== snapshotId);
|
||||
});
|
||||
|
||||
void queryClient.invalidateQueries({ queryKey });
|
||||
setShowDeleteConfirm(false);
|
||||
setSnapshotToDelete(null);
|
||||
if (selectedSnapshotId === snapshotToDelete) {
|
||||
if (selectedSnapshotId === snapshotId) {
|
||||
setSelectedSnapshotId(undefined);
|
||||
void navigate({ to: ".", search: () => ({ snapshot: undefined }) });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -200,6 +218,14 @@ export function ScheduleDetailsPage(props: Props) {
|
|||
}
|
||||
};
|
||||
|
||||
const handleSnapshotSelect = (snapshotId: string) => {
|
||||
setSelectedSnapshotId(snapshotId);
|
||||
void navigate({
|
||||
to: ".",
|
||||
search: () => ({ ...searchParams, snapshot: snapshotId }),
|
||||
});
|
||||
};
|
||||
|
||||
if (isEditMode) {
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -250,7 +276,7 @@ export function ScheduleDetailsPage(props: Props) {
|
|||
snapshots={snapshots ?? []}
|
||||
snapshotId={selectedSnapshot?.short_id}
|
||||
error={failureReason?.message}
|
||||
onSnapshotSelect={setSelectedSnapshotId}
|
||||
onSnapshotSelect={handleSnapshotSelect}
|
||||
/>
|
||||
<BackupSummaryCard summary={selectedSnapshot?.summary} />
|
||||
{selectedSnapshot && (
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
|
|||
snapshots={filteredSnapshots}
|
||||
repositoryId={repository.shortId}
|
||||
backups={schedules.data ?? []}
|
||||
listSnapshotsQueryOptions={{ path: { id: repository.id } }}
|
||||
/>
|
||||
)}
|
||||
<div className="px-4 py-2 text-sm text-muted-foreground bg-card-header flex justify-between border-t">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { type } from "arktype";
|
||||
import {
|
||||
getBackupScheduleOptions,
|
||||
getScheduleMirrorsOptions,
|
||||
|
|
@ -11,6 +12,7 @@ import { ScheduleDetailsPage } from "~/client/modules/backups/routes/backup-deta
|
|||
|
||||
export const Route = createFileRoute("/(dashboard)/backups/$backupId/")({
|
||||
component: RouteComponent,
|
||||
validateSearch: type({ snapshot: "string?" }),
|
||||
loader: async ({ params, context }) => {
|
||||
const { backupId } = params;
|
||||
|
||||
|
|
@ -48,6 +50,7 @@ export const Route = createFileRoute("/(dashboard)/backups/$backupId/")({
|
|||
function RouteComponent() {
|
||||
const loaderData = Route.useLoaderData();
|
||||
const { backupId } = Route.useParams();
|
||||
const search = Route.useSearch();
|
||||
|
||||
return <ScheduleDetailsPage loaderData={loaderData} scheduleId={backupId} />;
|
||||
return <ScheduleDetailsPage loaderData={loaderData} scheduleId={backupId} initialSnapshotId={search.snapshot} />;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue