fix: Activity Log — replace useInfiniteQuery with simple useQuery + cursor
The useInfiniteQuery with tRPC as-any cast wasn't passing cursors correctly, causing no data to load on initial page visit. Replaced with simple useQuery + manual cursor state: - First 50 entries load immediately on mount - "Load more" button appends next page via cursor - Filter changes reset cursor and entries - keepPreviousData prevents flash during pagination Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useMemo, useCallback } from "react";
|
import { useState, useMemo, useCallback, useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { Route } from "next";
|
import type { Route } from "next";
|
||||||
import { trpc } from "~/lib/trpc/client.js";
|
import { trpc } from "~/lib/trpc/client.js";
|
||||||
@@ -209,45 +209,39 @@ export function ActivityLogClient() {
|
|||||||
return input;
|
return input;
|
||||||
}, [entityType, action, userId, search, startDate, endDate]);
|
}, [entityType, action, userId, search, startDate, endDate]);
|
||||||
|
|
||||||
type AuditListPage = { items: Array<{
|
const [cursor, setCursor] = useState<string | undefined>(undefined);
|
||||||
id: string;
|
const [allEntries, setAllEntries] = useState<any[]>([]);
|
||||||
entityType: string;
|
const [hasNextPage, setHasNextPage] = useState(false);
|
||||||
entityId: string;
|
|
||||||
action: string;
|
|
||||||
changes: unknown;
|
|
||||||
createdAt: Date;
|
|
||||||
source: string | null;
|
|
||||||
entityName: string | null;
|
|
||||||
summary: string | null;
|
|
||||||
user: { id: string; name: string | null; email: string } | null;
|
|
||||||
}>; nextCursor?: string };
|
|
||||||
|
|
||||||
const {
|
const { data, isLoading, isFetching } = (trpc.auditLog.list as any).useQuery(
|
||||||
data,
|
{ ...queryInput, ...(cursor ? { cursor } : {}) },
|
||||||
isLoading,
|
{ staleTime: 30_000, keepPreviousData: true },
|
||||||
fetchNextPage,
|
) as { data: { items: any[]; nextCursor?: string } | undefined; isLoading: boolean; isFetching: boolean };
|
||||||
hasNextPage,
|
|
||||||
isFetchingNextPage,
|
|
||||||
// Keep as any to avoid tRPC TS depth limits with useInfiniteQuery
|
|
||||||
} = (trpc.auditLog.list.useInfiniteQuery as any)(
|
|
||||||
queryInput,
|
|
||||||
{
|
|
||||||
getNextPageParam: (lastPage: AuditListPage) => lastPage.nextCursor ?? undefined,
|
|
||||||
initialCursor: undefined,
|
|
||||||
staleTime: 30_000,
|
|
||||||
},
|
|
||||||
) as {
|
|
||||||
data: { pages: AuditListPage[] } | undefined;
|
|
||||||
isLoading: boolean;
|
|
||||||
fetchNextPage: () => void;
|
|
||||||
hasNextPage: boolean;
|
|
||||||
isFetchingNextPage: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const allEntries = useMemo(() => {
|
// Append new page results
|
||||||
if (!data) return [];
|
useEffect(() => {
|
||||||
return data.pages.flatMap((page) => page.items);
|
if (!data) return;
|
||||||
}, [data]);
|
if (!cursor) {
|
||||||
|
// First page or filter change — replace
|
||||||
|
setAllEntries(data.items);
|
||||||
|
} else {
|
||||||
|
// Subsequent page — append
|
||||||
|
setAllEntries((prev) => [...prev, ...data.items]);
|
||||||
|
}
|
||||||
|
setHasNextPage(!!data.nextCursor);
|
||||||
|
}, [data, cursor]);
|
||||||
|
|
||||||
|
// Reset cursor when filters change
|
||||||
|
useEffect(() => {
|
||||||
|
setCursor(undefined);
|
||||||
|
setAllEntries([]);
|
||||||
|
}, [entityType, action, userId, search, startDate, endDate]);
|
||||||
|
|
||||||
|
function loadMore() {
|
||||||
|
if (data?.nextCursor) {
|
||||||
|
setCursor(data.nextCursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toggleExpand = useCallback((id: string) => {
|
const toggleExpand = useCallback((id: string) => {
|
||||||
setExpandedId((prev) => (prev === id ? null : id));
|
setExpandedId((prev) => (prev === id ? null : id));
|
||||||
@@ -462,11 +456,11 @@ export function ActivityLogClient() {
|
|||||||
{hasNextPage && (
|
{hasNextPage && (
|
||||||
<div className="flex justify-center pt-4">
|
<div className="flex justify-center pt-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => fetchNextPage()}
|
onClick={loadMore}
|
||||||
disabled={isFetchingNextPage}
|
disabled={isFetching}
|
||||||
className="rounded-lg border border-gray-300 bg-white px-6 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 dark:border-slate-600 dark:bg-slate-800 dark:text-gray-300 dark:hover:bg-slate-700"
|
className="rounded-lg border border-gray-300 bg-white px-6 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50 dark:border-slate-600 dark:bg-slate-800 dark:text-gray-300 dark:hover:bg-slate-700"
|
||||||
>
|
>
|
||||||
{isFetchingNextPage ? "Loading..." : "Load more"}
|
{isFetching ? "Loading..." : "Load more"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user