Refactor pagination logic in AnnunciList and update API to use page and pageSize parameters for improved data fetching
This commit is contained in:
parent
c574af9096
commit
9b44cad8eb
5 changed files with 132 additions and 216 deletions
|
|
@ -73,7 +73,10 @@ const MyApp = ({
|
|||
`}</style>
|
||||
|
||||
<Toaster position="top-right" reverseOrder={false} />
|
||||
<NextNProgress options={{ showSpinner: false }} />
|
||||
{!router.pathname.startsWith("/annunci") && (
|
||||
<NextNProgress options={{ showSpinner: false }} />
|
||||
)}
|
||||
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</ThemeProvider>
|
||||
</SessionProvider>
|
||||
|
|
|
|||
|
|
@ -29,10 +29,7 @@ import { Status500 } from "~/components/status-page";
|
|||
import { CodiceBox } from "~/components/codiceRicerca";
|
||||
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
||||
import { MapSection } from "~/components/annunci_map";
|
||||
|
||||
import { api } from "~/utils/api";
|
||||
import { useRouter } from "next/router";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
|
@ -47,6 +44,7 @@ import {
|
|||
} from "~/providers/RicercaProvider";
|
||||
import { AccordionComp } from "~/components/accordionComp";
|
||||
import { CTA_TipologiaModal } from "~/components/annunci_tutorial";
|
||||
import { keepPreviousData } from "@tanstack/react-query";
|
||||
|
||||
type AnnunciPageProps = {
|
||||
options: AnnunciOptions;
|
||||
|
|
@ -265,7 +263,6 @@ const RicercaFilters = () => {
|
|||
};
|
||||
|
||||
const AnnunciList = () => {
|
||||
const router = useRouter();
|
||||
const {
|
||||
tipo,
|
||||
comune,
|
||||
|
|
@ -274,68 +271,110 @@ const AnnunciList = () => {
|
|||
map,
|
||||
page,
|
||||
setPage,
|
||||
lastPageCount,
|
||||
setlastPageCount,
|
||||
isLoading: ricercaLoading,
|
||||
} = useRicerca();
|
||||
|
||||
const { data, fetchNextPage, isLoading, hasNextPage } =
|
||||
api.annunci.getWithCursor.useInfiniteQuery(
|
||||
const { data, status, isPlaceholderData } =
|
||||
api.annunci.getWithCursor.useQuery(
|
||||
{
|
||||
limit: 6,
|
||||
page,
|
||||
pageSize: 6,
|
||||
tipologia: tipo || undefined,
|
||||
comune: comune || undefined,
|
||||
consegna: consegna || undefined,
|
||||
sort: sort || undefined,
|
||||
},
|
||||
{
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
placeholderData: keepPreviousData,
|
||||
enabled: !map,
|
||||
staleTime: 5000,
|
||||
},
|
||||
);
|
||||
|
||||
const handlePageReset = () => {
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const handleFetchNextPage = async () => {
|
||||
try {
|
||||
await fetchNextPage();
|
||||
setPage((prev) => prev + 1);
|
||||
} catch {
|
||||
await router.push("/annunci");
|
||||
toast.error("Errore caricamento annunci");
|
||||
}
|
||||
};
|
||||
|
||||
const handleFetchPreviousPage = () => {
|
||||
setPage((prev) => prev - 1);
|
||||
};
|
||||
|
||||
const utils = api.useUtils();
|
||||
useEffect(() => {
|
||||
if (
|
||||
hasNextPage !== undefined &&
|
||||
hasNextPage === false &&
|
||||
lastPageCount === 100
|
||||
) {
|
||||
setlastPageCount(page);
|
||||
}
|
||||
}, [hasNextPage, page, lastPageCount]);
|
||||
const prefetchData = async () => {
|
||||
if (!isPlaceholderData && data?.hasMore) {
|
||||
await utils.annunci.getWithCursor.prefetch({
|
||||
page: page + 1,
|
||||
pageSize: 6,
|
||||
tipologia: tipo || undefined,
|
||||
comune: comune || undefined,
|
||||
consegna: consegna || undefined,
|
||||
sort: sort || undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
prefetchData().catch((err) => {
|
||||
console.error("Error prefetching next page:", err);
|
||||
});
|
||||
}, [data, isPlaceholderData, page]);
|
||||
|
||||
if (isLoading || ricercaLoading) return <LoadingPage />;
|
||||
if ((!isLoading && !data) || !data) return <Status500 />;
|
||||
if (ricercaLoading) return <LoadingPage />;
|
||||
if (status === "error") return <Status500 />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnnunciGrid pagedata={data.pages[page]!.annunci} />
|
||||
{status === "pending" ? (
|
||||
<LoadingPage />
|
||||
) : (
|
||||
<AnnunciGrid pagedata={data.annunci} />
|
||||
)}
|
||||
<br />
|
||||
<PaginationHandler
|
||||
handleFetchNextPage={handleFetchNextPage}
|
||||
handleFetchPreviousPage={handleFetchPreviousPage}
|
||||
handlePageReset={handlePageReset}
|
||||
page={page}
|
||||
hasNextPage={data?.pages[page]?.nextCursor}
|
||||
/>
|
||||
|
||||
<div className="mx-auto grid max-w-sm grid-cols-5 items-center justify-between gap-2">
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Reset Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
page < 2 ? "invisible" : "visible",
|
||||
)}
|
||||
onClick={async () => {
|
||||
await setPage(0);
|
||||
}}
|
||||
disabled={page < 2}
|
||||
>
|
||||
<ChevronsRight className="rotate-180" height={30} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Previous Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
page < 1 ? "invisible" : "visible",
|
||||
)}
|
||||
onClick={async () => {
|
||||
await setPage((old) => (old ? Math.max(old - 1, 0) : 0));
|
||||
}}
|
||||
disabled={page === 0}
|
||||
>
|
||||
<ChevronRight height={30} className="rotate-180" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<span className="text-lg font-semibold text-neutral-500">
|
||||
{page + 1}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Next Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
data?.hasMore ? "visible" : "invisible",
|
||||
)}
|
||||
onClick={async () => {
|
||||
await setPage((old) =>
|
||||
old ? (data?.hasMore ? old + 1 : old) : 1,
|
||||
);
|
||||
}}
|
||||
disabled={isPlaceholderData || !data?.hasMore}
|
||||
>
|
||||
<ChevronRight height={30} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -396,73 +435,3 @@ export const SelectFilter = <T extends string>({
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PaginationHandler = ({
|
||||
handleFetchNextPage,
|
||||
handleFetchPreviousPage,
|
||||
handlePageReset,
|
||||
page,
|
||||
hasNextPage,
|
||||
}: {
|
||||
handleFetchNextPage: () => Promise<void>;
|
||||
handleFetchPreviousPage: () => void;
|
||||
handlePageReset: () => void;
|
||||
page: number;
|
||||
hasNextPage: string | undefined;
|
||||
}) => {
|
||||
const [attivo, setAttivo] = useState(true);
|
||||
const fetchNextPage = async () => {
|
||||
setAttivo(false);
|
||||
await handleFetchNextPage();
|
||||
setAttivo(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto grid max-w-sm grid-cols-5 items-center justify-between gap-2">
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Reset Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
page < 2 ? "invisible" : "visible",
|
||||
)}
|
||||
onClick={handlePageReset}
|
||||
disabled={page < 2}
|
||||
>
|
||||
<ChevronsRight className="rotate-180" height={30} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Previous Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
page < 1 ? "invisible" : "visible",
|
||||
)}
|
||||
onClick={handleFetchPreviousPage}
|
||||
disabled={page < 1}
|
||||
>
|
||||
<ChevronRight height={30} className="rotate-180" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<span className="text-lg font-semibold text-neutral-500">
|
||||
{page + 1}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Next Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
hasNextPage === undefined ? "invisible" : "visible",
|
||||
)}
|
||||
onClick={fetchNextPage}
|
||||
disabled={!hasNextPage || attivo === false}
|
||||
>
|
||||
<ChevronRight height={30} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,12 +3,9 @@ import {
|
|||
createContext,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
type Dispatch,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
import {
|
||||
parseAsBoolean,
|
||||
|
|
@ -45,9 +42,9 @@ type RicercaContextType = {
|
|||
consegnaOptions: number[];
|
||||
sortOptions: typeof sortOptions;
|
||||
page: number;
|
||||
setPage: Dispatch<SetStateAction<number>>;
|
||||
lastPageCount: number;
|
||||
setlastPageCount: Dispatch<SetStateAction<number>>;
|
||||
setPage: (
|
||||
value: number | ((old: number | null) => number | null) | null,
|
||||
) => Promise<URLSearchParams>;
|
||||
ResetParams: () => Promise<void>;
|
||||
nFiltri: number;
|
||||
isLoading: boolean;
|
||||
|
|
@ -103,8 +100,10 @@ export const RicercaProvider: FC<{
|
|||
.withOptions({ startTransition }),
|
||||
);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [lastPageCount, setlastPageCount] = useState(100);
|
||||
const [page, setPage] = useQueryState(
|
||||
"p",
|
||||
parseAsInteger.withDefault(0).withOptions({ clearOnDefault: true }),
|
||||
);
|
||||
|
||||
const tipologieOptions = useMemo(() => {
|
||||
const set = new Set<string>();
|
||||
|
|
@ -147,7 +146,7 @@ export const RicercaProvider: FC<{
|
|||
await setComune("");
|
||||
await setConsegna(null);
|
||||
await setSort("");
|
||||
setPage(0);
|
||||
await setPage(0);
|
||||
};
|
||||
|
||||
const nFiltri =
|
||||
|
|
@ -176,8 +175,6 @@ export const RicercaProvider: FC<{
|
|||
page,
|
||||
setPage,
|
||||
|
||||
lastPageCount,
|
||||
setlastPageCount,
|
||||
ResetParams,
|
||||
nFiltri,
|
||||
isLoading,
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ export const annunciRouter = createTRPCRouter({
|
|||
getWithCursor: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
limit: z.number().min(1).max(100).optional(),
|
||||
cursor: z.string().optional(),
|
||||
page: z.number().optional(),
|
||||
pageSize: z.number(),
|
||||
tipologia: z.string().optional(),
|
||||
comune: z.string().optional(),
|
||||
consegna: z.number().optional(),
|
||||
|
|
@ -66,13 +66,9 @@ export const annunciRouter = createTRPCRouter({
|
|||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const { annunci, nextCursor } = await getCursor_AnnunciHandler({
|
||||
input,
|
||||
return await getCursor_AnnunciHandler({
|
||||
...input,
|
||||
});
|
||||
return {
|
||||
annunci,
|
||||
nextCursor,
|
||||
};
|
||||
}),
|
||||
getMapping: publicProcedure
|
||||
.input(
|
||||
|
|
|
|||
|
|
@ -233,34 +233,20 @@ export type AnnuncioRicerca = Pick<
|
|||
>;
|
||||
|
||||
export const getCursor_AnnunciHandler = async ({
|
||||
input,
|
||||
page = 0,
|
||||
pageSize,
|
||||
tipologia,
|
||||
comune,
|
||||
consegna,
|
||||
sort,
|
||||
}: {
|
||||
input: {
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
tipologia?: string;
|
||||
comune?: string;
|
||||
consegna?: number;
|
||||
sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
|
||||
};
|
||||
}): Promise<{
|
||||
annunci: AnnuncioRicerca[];
|
||||
nextCursor: string | undefined;
|
||||
}> => {
|
||||
const limit = input.limit ?? 50;
|
||||
const [cursor, sortCursor] = input.cursor?.split("_") ?? [0, undefined];
|
||||
function sortType(sort: "Recenti" | "Prezzo" | "Consegna" | "Mq") {
|
||||
switch (sort) {
|
||||
case "Recenti":
|
||||
return { col: "modificato_il", opr: "desc" };
|
||||
case "Prezzo":
|
||||
return { col: "prezzo", opr: "asc" };
|
||||
case "Consegna":
|
||||
return { col: "consegna", opr: "asc" };
|
||||
case "Mq":
|
||||
return { col: "mq", opr: "asc" };
|
||||
}
|
||||
}
|
||||
page?: number;
|
||||
pageSize: number;
|
||||
tipologia?: string;
|
||||
comune?: string;
|
||||
consegna?: number;
|
||||
sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
|
||||
}) => {
|
||||
try {
|
||||
let query = db
|
||||
.selectFrom("annunci")
|
||||
|
|
@ -283,35 +269,18 @@ export const getCursor_AnnunciHandler = async ({
|
|||
|
||||
.where("web", "=", true)
|
||||
.where("stato", "!=", "Sospeso");
|
||||
if (sortCursor && input.sort !== undefined) {
|
||||
const { col: sortColumn, opr } = sortType(input.sort) as {
|
||||
col: keyof Annunci;
|
||||
opr: string;
|
||||
};
|
||||
query = query.where((eb) =>
|
||||
eb.or([
|
||||
eb.and([
|
||||
eb("id", ">=", (cursor as AnnunciId) ?? 0),
|
||||
eb(sortColumn, "=", sortCursor.toString()),
|
||||
]),
|
||||
eb(sortColumn, opr === "desc" ? "<" : ">", sortCursor.toString()),
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
query = query.where("id", ">=", (cursor as AnnunciId) ?? 0);
|
||||
}
|
||||
|
||||
if (input.tipologia && input.tipologia !== "") {
|
||||
query = query.where("tipo", "=", input.tipologia);
|
||||
if (tipologia && tipologia !== "") {
|
||||
query = query.where("tipo", "=", tipologia);
|
||||
}
|
||||
if (input.comune && input.comune !== "") {
|
||||
query = query.where("comune", "like", input.comune);
|
||||
if (comune && comune !== "") {
|
||||
query = query.where("comune", "like", comune);
|
||||
}
|
||||
if (input.consegna) {
|
||||
query = query.where("consegna", "=", input.consegna);
|
||||
if (consegna) {
|
||||
query = query.where("consegna", "=", consegna);
|
||||
}
|
||||
if (input.sort) {
|
||||
switch (input.sort) {
|
||||
if (sort) {
|
||||
switch (sort) {
|
||||
case "Recenti":
|
||||
query = query.orderBy("modificato_il", "desc");
|
||||
break;
|
||||
|
|
@ -328,32 +297,13 @@ export const getCursor_AnnunciHandler = async ({
|
|||
}
|
||||
query = query.orderBy("id", "asc");
|
||||
|
||||
const itemsraw = await query.limit(limit + 1).execute();
|
||||
const annunci = itemsraw;
|
||||
let nextCursor: string | undefined = undefined;
|
||||
|
||||
if (annunci.length > limit) {
|
||||
const nextItem = annunci.pop(); // return the last item from the array
|
||||
if (!nextItem) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Errore interno",
|
||||
});
|
||||
}
|
||||
if (!input.sort) {
|
||||
nextCursor = nextItem.id.toString();
|
||||
} else {
|
||||
const { col: sortColumn } = sortType(input.sort);
|
||||
if (sortColumn === "modificato_il") {
|
||||
if (!nextItem.modificato_il) {
|
||||
nextCursor = `${nextItem.id.toString()}_1990-01-01`;
|
||||
} else {
|
||||
nextCursor = `${nextItem.id.toString()}_${nextItem.modificato_il.toISOString()}`;
|
||||
}
|
||||
} else {
|
||||
nextCursor = `${nextItem.id.toString()}_${nextItem[sortColumn as keyof typeof nextItem]?.toString()}`;
|
||||
}
|
||||
}
|
||||
const annunci = await query
|
||||
.limit(pageSize + 1)
|
||||
.offset(page * pageSize)
|
||||
.execute();
|
||||
const hasMore = annunci.length > pageSize;
|
||||
if (annunci.length > pageSize) {
|
||||
annunci.pop(); // Remove the last item if it exceeds the page size
|
||||
}
|
||||
|
||||
for (const annuncio of annunci) {
|
||||
|
|
@ -361,9 +311,10 @@ export const getCursor_AnnunciHandler = async ({
|
|||
? createSrcset(annuncio.url_immagini)
|
||||
: [];
|
||||
}
|
||||
|
||||
return {
|
||||
annunci,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
};
|
||||
} catch {
|
||||
throw new TRPCError({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue