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:
2026-03-22 23:01:30 +01:00
parent eacbdb5d47
commit d46937300a
@@ -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>
)} )}