diff --git a/app/client/modules/backups/routes/backup-details.tsx b/app/client/modules/backups/routes/backup-details.tsx index 28b7d90b..fbff2ffa 100644 --- a/app/client/modules/backups/routes/backup-details.tsx +++ b/app/client/modules/backups/routes/backup-details.tsx @@ -51,6 +51,7 @@ type Props = { repos: Repository[]; scheduleNotifs: ScheduleNotification[]; mirrors: ScheduleMirror[]; + snapshots?: Snapshot[]; }; scheduleId: string; initialSnapshotId?: string; @@ -78,6 +79,7 @@ export function ScheduleDetailsPage(props: Props) { failureReason, } = useQuery({ ...listSnapshotsOptions({ path: { shortId: schedule.repository.shortId }, query: { backupId: schedule.shortId } }), + initialData: loaderData.snapshots, }); const updateSchedule = useMutation({ diff --git a/app/client/modules/repositories/routes/repository-details.tsx b/app/client/modules/repositories/routes/repository-details.tsx index 2896ea57..8841c1e7 100644 --- a/app/client/modules/repositories/routes/repository-details.tsx +++ b/app/client/modules/repositories/routes/repository-details.tsx @@ -5,8 +5,17 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui import { RepositoryInfoTabContent } from "../tabs/info"; import { RepositorySnapshotsTabContent } from "../tabs/snapshots"; import { useNavigate, useSearch } from "@tanstack/react-router"; +import type { BackupSchedule, Snapshot } from "~/client/lib/types"; -export default function RepositoryDetailsPage({ repositoryId }: { repositoryId: string }) { +export default function RepositoryDetailsPage({ + repositoryId, + initialSnapshots, + initialBackupSchedules, +}: { + repositoryId: string; + initialSnapshots?: Snapshot[]; + initialBackupSchedules?: BackupSchedule[]; +}) { const navigate = useNavigate(); const { tab } = useSearch({ from: "/(dashboard)/repositories/$repositoryId/" }); const activeTab = tab || "info"; @@ -27,7 +36,11 @@ export default function RepositoryDetailsPage({ repositoryId }: { repositoryId: - + diff --git a/app/client/modules/repositories/tabs/snapshots.tsx b/app/client/modules/repositories/tabs/snapshots.tsx index 828a88a0..d039c03c 100644 --- a/app/client/modules/repositories/tabs/snapshots.tsx +++ b/app/client/modules/repositories/tabs/snapshots.tsx @@ -11,23 +11,26 @@ import { Button } from "~/client/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Input } from "~/client/components/ui/input"; import { Table, TableBody, TableCell, TableRow } from "~/client/components/ui/table"; -import type { Repository, Snapshot } from "~/client/lib/types"; +import type { BackupSchedule, Repository, Snapshot } from "~/client/lib/types"; import { toast } from "sonner"; type Props = { repository: Repository; + initialSnapshots?: Snapshot[]; + initialBackupSchedules?: BackupSchedule[]; }; -export const RepositorySnapshotsTabContent = ({ repository }: Props) => { +export const RepositorySnapshotsTabContent = ({ repository, initialSnapshots, initialBackupSchedules }: Props) => { const [searchQuery, setSearchQuery] = useState(""); - const { data, isFetching, failureReason } = useQuery({ + const { data, isPending, failureReason } = useQuery({ ...listSnapshotsOptions({ path: { shortId: repository.shortId } }), - initialData: [], + initialData: initialSnapshots, }); const schedules = useQuery({ ...listBackupSchedulesOptions(), + initialData: initialBackupSchedules, }); const refreshMutation = useMutation({ @@ -44,7 +47,9 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => { refreshMutation.mutate({ path: { shortId: repository.shortId } }); }; - const filteredSnapshots = data.filter((snapshot: Snapshot) => { + const snapshots = data ?? []; + + const filteredSnapshots = snapshots.filter((snapshot: Snapshot) => { if (!searchQuery) return true; const searchLower = searchQuery.toLowerCase(); @@ -58,7 +63,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => { ); }); - const hasNoFilteredSnapshots = !filteredSnapshots?.length; + const hasNoFilteredSnapshots = !filteredSnapshots.length; if (repository.status === "error") { return ( @@ -91,7 +96,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => { ); } - if (isFetching && !data.length) { + if (isPending) { return ( @@ -101,7 +106,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => { ); } - if (!data.length) { + if (!snapshots.length) { return ( @@ -131,7 +136,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => {
Snapshots - Backup snapshots stored in this repository. Total: {data.length} + Backup snapshots stored in this repository. Total: {snapshots.length}
@@ -180,7 +185,7 @@ export const RepositorySnapshotsTabContent = ({ repository }: Props) => { {hasNoFilteredSnapshots ? "No snapshots match filters." - : `Showing ${filteredSnapshots.length} of ${data.length}`} + : `Showing ${filteredSnapshots.length} of ${snapshots.length}`}
diff --git a/app/routes/(dashboard)/backups/$backupId/index.tsx b/app/routes/(dashboard)/backups/$backupId/index.tsx index 7d79d329..7a9215f7 100644 --- a/app/routes/(dashboard)/backups/$backupId/index.tsx +++ b/app/routes/(dashboard)/backups/$backupId/index.tsx @@ -9,6 +9,7 @@ import { listSnapshotsOptions, } from "~/client/api-client/@tanstack/react-query.gen"; import { ScheduleDetailsPage } from "~/client/modules/backups/routes/backup-details"; +import { prefetchOrSkip } from "~/utils/prefetch"; export const Route = createFileRoute("/(dashboard)/backups/$backupId/")({ component: RouteComponent, @@ -24,14 +25,21 @@ export const Route = createFileRoute("/(dashboard)/backups/$backupId/")({ context.queryClient.ensureQueryData({ ...getScheduleMirrorsOptions({ path: { shortId: backupId } }) }), ]); - void context.queryClient.prefetchQuery({ - ...listSnapshotsOptions({ - path: { shortId: schedule.repository.shortId }, - query: { backupId: schedule.shortId }, - }), + const snapshotOptions = listSnapshotsOptions({ + path: { shortId: schedule.repository.shortId }, + query: { backupId: schedule.shortId }, }); - return { schedule, notifs, repos, scheduleNotifs, mirrors }; + await prefetchOrSkip(context.queryClient, snapshotOptions); + + return { + schedule, + notifs, + repos, + scheduleNotifs, + mirrors, + snapshots: context.queryClient.getQueryData(snapshotOptions.queryKey), + }; }, staticData: { breadcrumb: (match) => [ diff --git a/app/routes/(dashboard)/repositories/$repositoryId/index.tsx b/app/routes/(dashboard)/repositories/$repositoryId/index.tsx index d32305a9..ef232965 100644 --- a/app/routes/(dashboard)/repositories/$repositoryId/index.tsx +++ b/app/routes/(dashboard)/repositories/$repositoryId/index.tsx @@ -6,23 +6,26 @@ import { listSnapshotsOptions, } from "~/client/api-client/@tanstack/react-query.gen"; import RepositoryDetailsPage from "~/client/modules/repositories/routes/repository-details"; +import { prefetchOrSkip } from "~/utils/prefetch"; export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/")({ component: RouteComponent, errorComponent: (e) =>
{e.error.message}
, loader: async ({ params, context }) => { - void context.queryClient.prefetchQuery({ - ...listSnapshotsOptions({ path: { shortId: params.repositoryId } }), - }); - void context.queryClient.prefetchQuery({ - ...listBackupSchedulesOptions(), - }); + const snapshotOptions = listSnapshotsOptions({ path: { shortId: params.repositoryId } }); + const schedulesOptions = listBackupSchedulesOptions(); - const res = await context.queryClient.ensureQueryData({ - ...getRepositoryOptions({ path: { shortId: params.repositoryId } }), - }); + const [res] = await Promise.all([ + context.queryClient.ensureQueryData(getRepositoryOptions({ path: { shortId: params.repositoryId } })), + prefetchOrSkip(context.queryClient, snapshotOptions), + prefetchOrSkip(context.queryClient, schedulesOptions), + ]); - return res; + return { + ...res, + snapshots: context.queryClient.getQueryData(snapshotOptions.queryKey), + backupSchedules: context.queryClient.getQueryData(schedulesOptions.queryKey), + }; }, validateSearch: type({ tab: "string?" }), staticData: { @@ -44,6 +47,13 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/") function RouteComponent() { const { repositoryId } = Route.useParams(); + const { snapshots, backupSchedules } = Route.useLoaderData(); - return ; + return ( + + ); } diff --git a/app/utils/prefetch.ts b/app/utils/prefetch.ts new file mode 100644 index 00000000..d9b1e0e7 --- /dev/null +++ b/app/utils/prefetch.ts @@ -0,0 +1,17 @@ +import type { FetchQueryOptions, QueryClient, QueryKey } from "@tanstack/react-query"; + +export async function prefetchOrSkip< + TQueryFnData = unknown, + TError = Error, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + queryClient: QueryClient, + options: FetchQueryOptions, + timeoutMs = 150, +): Promise { + await Promise.race([ + queryClient.prefetchQuery(options), + new Promise((resolve) => setTimeout(resolve, timeoutMs)), + ]); +}