prefetch list if a name change

This commit is contained in:
Nate Kelley 2025-08-05 09:27:44 -06:00
parent 87b0f55157
commit e70d68d9ea
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 119 additions and 12 deletions

View File

@ -11,7 +11,7 @@ import { queryKeys } from '@/api/query_keys';
import type { RustApiError } from '../errors';
import type {
GetReportIndividualResponse,
UpdateReportRequest,
GetReportsListResponse,
UpdateReportResponse
} from '@buster/server-shared/reports';
import {
@ -21,6 +21,7 @@ import {
getReportById_server,
updateReport
} from './requests';
import { useDebounceFn } from '@/hooks/useDebounce';
/**
* Hook to get a list of reports
@ -61,6 +62,20 @@ export const prefetchGetReportsList = async (
return queryClient;
};
export const prefetchGetReportsListClient = async (
params?: Parameters<typeof getReportsList>[0],
queryClientProp?: QueryClient
) => {
const queryClient = queryClientProp || new QueryClient();
await queryClient.prefetchQuery({
...queryKeys.reportsGetList(params),
queryFn: () => getReportsList(params)
});
return queryClient;
};
/**
* Hook to get an individual report by ID
*/
@ -107,11 +122,11 @@ export const useUpdateReport = () => {
return useMutation<
UpdateReportResponse,
RustApiError,
{ reportId: string; data: UpdateReportRequest },
Parameters<typeof updateReport>[0],
{ previousReport?: GetReportIndividualResponse }
>({
mutationFn: ({ reportId, data }) => updateReport(reportId, data),
onMutate: async ({ reportId, data }) => {
mutationFn: updateReport,
onMutate: async ({ reportId, ...data }) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({
queryKey: queryKeys.reportsGetReport(reportId).queryKey
@ -145,18 +160,32 @@ export const useUpdateReport = () => {
);
}
},
onSuccess: (data, { reportId, data: updateData }) => {
onSuccess: (data, { reportId, ...updateData }, ctx) => {
// Update the individual report cache with server response
queryClient.setQueryData(queryKeys.reportsGetReport(reportId).queryKey, data);
const nameChanged = updateData.name !== undefined && updateData.name !== data.name;
const nameChanged =
updateData.name !== undefined &&
ctx?.previousReport?.name !== undefined &&
updateData.name !== ctx?.previousReport?.name;
// Invalidate the list cache to ensure it's fresh
if (nameChanged) {
queryClient.invalidateQueries({
queryKey: queryKeys.reportsGetList().queryKey,
refetchType: 'all'
const listQueryKey = queryKeys.reportsGetList().queryKey;
const hasActiveQuery = queryClient.getQueryCache().find({
queryKey: listQueryKey,
exact: true,
type: 'active'
});
if (hasActiveQuery) {
queryClient.invalidateQueries({
queryKey: listQueryKey,
refetchType: 'all'
});
} else {
prefetchGetReportsListClient();
}
}
}
});

View File

@ -50,6 +50,9 @@ export const getReportById_server = async (reportId: string) => {
/**
* Update a report
*/
export const updateReport = async (reportId: string, data: UpdateReportRequest) => {
export const updateReport = async ({
reportId,
...data
}: UpdateReportRequest & { reportId: string }) => {
return mainApiV2.put<UpdateReportResponse>(`/reports/${reportId}`, data).then((res) => res.data);
};

View File

@ -1,9 +1,9 @@
//import { ReportsListController } from '@/controllers/ReportsListController';
import { ReportPageController } from '@/controllers/ReportPageControllers/ReportPageController';
export default async function Page(props: { params: Promise<{ reportId: string }> }) {
const params = await props.params;
const { reportId } = params;
return <div>Report with an id of {reportId}</div>;
return <ReportPageController reportId={reportId} />;
}

View File

@ -0,0 +1,39 @@
'use client';
import {
prefetchGetReportsListClient,
useGetReport,
useGetReportsList,
useUpdateReport
} from '@/api/buster_rest/reports';
import { cn } from '@/lib/utils';
import React from 'react';
import { ReportPageHeader } from './ReportPageHeader';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { useDebounceFn } from '@/hooks/useDebounce';
export const ReportPageController: React.FC<{
reportId: string;
readOnly?: boolean;
className?: string;
}> = ({ reportId, readOnly = false, className = '' }) => {
const { data: report } = useGetReport({ reportId, versionNumber: undefined });
const { mutate: updateReport } = useUpdateReport();
const onChangeName = useMemoizedFn((name: string) => {
updateReport({ reportId, name });
});
const { run: debouncedUpdateReport } = useDebounceFn(updateReport, { wait: 300 });
return (
<div className={cn('space-y-1.5 pt-9 pb-6 sm:px-[max(64px,calc(50%-350px))]', className)}>
<ReportPageHeader
name={report?.name}
updatedAt={report?.updated_at}
onChangeName={onChangeName}
/>
</div>
);
};

View File

@ -0,0 +1,35 @@
import React, { useMemo } from 'react';
import { formatDate } from '@/lib/date';
import { EditableTitle } from '@/components/ui/typography/EditableTitle';
import { Paragraph } from '@/components/ui/typography/Paragraph';
import { cn } from '@/lib/utils';
const DEFAULT_CREATED_BY = 'Created by Buster';
export const ReportPageHeader = React.forwardRef<
HTMLInputElement,
{
className?: string;
name?: string;
updatedAt?: string;
onChangeName: (name: string) => void;
}
>(({ name = '', updatedAt = '', className = '', onChangeName }, ref) => {
const updatedAtFormatted = useMemo(() => {
if (!updatedAt) return '';
return formatDate({ date: updatedAt, format: 'll' });
}, [updatedAt]);
return (
<div className={cn('flex flex-col space-y-1.5', className)}>
<EditableTitle className="h-9" level={1} ref={ref} onChange={onChangeName}>
{name}
</EditableTitle>
<Paragraph size={'base'} variant={'tertiary'}>
{updatedAtFormatted} {DEFAULT_CREATED_BY}
</Paragraph>
</div>
);
});
ReportPageHeader.displayName = 'ReportPageHeader';

View File

@ -0,0 +1 @@
export * from './ReportPageController';