From d46937300a6b930dce780b499292a112a3a771fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Sun, 22 Mar 2026 23:01:30 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20Activity=20Log=20=E2=80=94=20replace=20u?= =?UTF-8?q?seInfiniteQuery=20with=20simple=20useQuery=20+=20cursor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../components/admin/ActivityLogClient.tsx | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/apps/web/src/components/admin/ActivityLogClient.tsx b/apps/web/src/components/admin/ActivityLogClient.tsx index 4550593..a37c5bc 100644 --- a/apps/web/src/components/admin/ActivityLogClient.tsx +++ b/apps/web/src/components/admin/ActivityLogClient.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useMemo, useCallback } from "react"; +import { useState, useMemo, useCallback, useEffect } from "react"; import Link from "next/link"; import type { Route } from "next"; import { trpc } from "~/lib/trpc/client.js"; @@ -209,45 +209,39 @@ export function ActivityLogClient() { return input; }, [entityType, action, userId, search, startDate, endDate]); - type AuditListPage = { items: Array<{ - id: string; - entityType: string; - 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 [cursor, setCursor] = useState(undefined); + const [allEntries, setAllEntries] = useState([]); + const [hasNextPage, setHasNextPage] = useState(false); - const { - data, - isLoading, - fetchNextPage, - 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 { data, isLoading, isFetching } = (trpc.auditLog.list as any).useQuery( + { ...queryInput, ...(cursor ? { cursor } : {}) }, + { staleTime: 30_000, keepPreviousData: true }, + ) as { data: { items: any[]; nextCursor?: string } | undefined; isLoading: boolean; isFetching: boolean }; - const allEntries = useMemo(() => { - if (!data) return []; - return data.pages.flatMap((page) => page.items); - }, [data]); + // Append new page results + useEffect(() => { + if (!data) return; + 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) => { setExpandedId((prev) => (prev === id ? null : id)); @@ -462,11 +456,11 @@ export function ActivityLogClient() { {hasNextPage && (
)}