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>
|
`}</style>
|
||||||
|
|
||||||
<Toaster position="top-right" reverseOrder={false} />
|
<Toaster position="top-right" reverseOrder={false} />
|
||||||
<NextNProgress options={{ showSpinner: false }} />
|
{!router.pathname.startsWith("/annunci") && (
|
||||||
|
<NextNProgress options={{ showSpinner: false }} />
|
||||||
|
)}
|
||||||
|
|
||||||
{getLayout(<Component {...pageProps} />)}
|
{getLayout(<Component {...pageProps} />)}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,7 @@ import { Status500 } from "~/components/status-page";
|
||||||
import { CodiceBox } from "~/components/codiceRicerca";
|
import { CodiceBox } from "~/components/codiceRicerca";
|
||||||
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
||||||
import { MapSection } from "~/components/annunci_map";
|
import { MapSection } from "~/components/annunci_map";
|
||||||
|
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
@ -47,6 +44,7 @@ import {
|
||||||
} from "~/providers/RicercaProvider";
|
} from "~/providers/RicercaProvider";
|
||||||
import { AccordionComp } from "~/components/accordionComp";
|
import { AccordionComp } from "~/components/accordionComp";
|
||||||
import { CTA_TipologiaModal } from "~/components/annunci_tutorial";
|
import { CTA_TipologiaModal } from "~/components/annunci_tutorial";
|
||||||
|
import { keepPreviousData } from "@tanstack/react-query";
|
||||||
|
|
||||||
type AnnunciPageProps = {
|
type AnnunciPageProps = {
|
||||||
options: AnnunciOptions;
|
options: AnnunciOptions;
|
||||||
|
|
@ -265,7 +263,6 @@ const RicercaFilters = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const AnnunciList = () => {
|
const AnnunciList = () => {
|
||||||
const router = useRouter();
|
|
||||||
const {
|
const {
|
||||||
tipo,
|
tipo,
|
||||||
comune,
|
comune,
|
||||||
|
|
@ -274,68 +271,110 @@ const AnnunciList = () => {
|
||||||
map,
|
map,
|
||||||
page,
|
page,
|
||||||
setPage,
|
setPage,
|
||||||
lastPageCount,
|
|
||||||
setlastPageCount,
|
|
||||||
isLoading: ricercaLoading,
|
isLoading: ricercaLoading,
|
||||||
} = useRicerca();
|
} = useRicerca();
|
||||||
|
|
||||||
const { data, fetchNextPage, isLoading, hasNextPage } =
|
const { data, status, isPlaceholderData } =
|
||||||
api.annunci.getWithCursor.useInfiniteQuery(
|
api.annunci.getWithCursor.useQuery(
|
||||||
{
|
{
|
||||||
limit: 6,
|
page,
|
||||||
|
pageSize: 6,
|
||||||
tipologia: tipo || undefined,
|
tipologia: tipo || undefined,
|
||||||
comune: comune || undefined,
|
comune: comune || undefined,
|
||||||
consegna: consegna || undefined,
|
consegna: consegna || undefined,
|
||||||
sort: sort || undefined,
|
sort: sort || undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
placeholderData: keepPreviousData,
|
||||||
enabled: !map,
|
enabled: !map,
|
||||||
|
staleTime: 5000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const utils = api.useUtils();
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
const prefetchData = async () => {
|
||||||
hasNextPage !== undefined &&
|
if (!isPlaceholderData && data?.hasMore) {
|
||||||
hasNextPage === false &&
|
await utils.annunci.getWithCursor.prefetch({
|
||||||
lastPageCount === 100
|
page: page + 1,
|
||||||
) {
|
pageSize: 6,
|
||||||
setlastPageCount(page);
|
tipologia: tipo || undefined,
|
||||||
}
|
comune: comune || undefined,
|
||||||
}, [hasNextPage, page, lastPageCount]);
|
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 (ricercaLoading) return <LoadingPage />;
|
||||||
if ((!isLoading && !data) || !data) return <Status500 />;
|
if (status === "error") return <Status500 />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AnnunciGrid pagedata={data.pages[page]!.annunci} />
|
{status === "pending" ? (
|
||||||
|
<LoadingPage />
|
||||||
|
) : (
|
||||||
|
<AnnunciGrid pagedata={data.annunci} />
|
||||||
|
)}
|
||||||
<br />
|
<br />
|
||||||
<PaginationHandler
|
|
||||||
handleFetchNextPage={handleFetchNextPage}
|
<div className="mx-auto grid max-w-sm grid-cols-5 items-center justify-between gap-2">
|
||||||
handleFetchPreviousPage={handleFetchPreviousPage}
|
<div className="flex items-center justify-center">
|
||||||
handlePageReset={handlePageReset}
|
<button
|
||||||
page={page}
|
aria-label="Reset Page"
|
||||||
hasNextPage={data?.pages[page]?.nextCursor}
|
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>
|
</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,
|
createContext,
|
||||||
useContext,
|
useContext,
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
|
||||||
useTransition,
|
useTransition,
|
||||||
type Dispatch,
|
|
||||||
type FC,
|
type FC,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
type SetStateAction,
|
|
||||||
} from "react";
|
} from "react";
|
||||||
import {
|
import {
|
||||||
parseAsBoolean,
|
parseAsBoolean,
|
||||||
|
|
@ -45,9 +42,9 @@ type RicercaContextType = {
|
||||||
consegnaOptions: number[];
|
consegnaOptions: number[];
|
||||||
sortOptions: typeof sortOptions;
|
sortOptions: typeof sortOptions;
|
||||||
page: number;
|
page: number;
|
||||||
setPage: Dispatch<SetStateAction<number>>;
|
setPage: (
|
||||||
lastPageCount: number;
|
value: number | ((old: number | null) => number | null) | null,
|
||||||
setlastPageCount: Dispatch<SetStateAction<number>>;
|
) => Promise<URLSearchParams>;
|
||||||
ResetParams: () => Promise<void>;
|
ResetParams: () => Promise<void>;
|
||||||
nFiltri: number;
|
nFiltri: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
|
@ -103,8 +100,10 @@ export const RicercaProvider: FC<{
|
||||||
.withOptions({ startTransition }),
|
.withOptions({ startTransition }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useQueryState(
|
||||||
const [lastPageCount, setlastPageCount] = useState(100);
|
"p",
|
||||||
|
parseAsInteger.withDefault(0).withOptions({ clearOnDefault: true }),
|
||||||
|
);
|
||||||
|
|
||||||
const tipologieOptions = useMemo(() => {
|
const tipologieOptions = useMemo(() => {
|
||||||
const set = new Set<string>();
|
const set = new Set<string>();
|
||||||
|
|
@ -147,7 +146,7 @@ export const RicercaProvider: FC<{
|
||||||
await setComune("");
|
await setComune("");
|
||||||
await setConsegna(null);
|
await setConsegna(null);
|
||||||
await setSort("");
|
await setSort("");
|
||||||
setPage(0);
|
await setPage(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const nFiltri =
|
const nFiltri =
|
||||||
|
|
@ -176,8 +175,6 @@ export const RicercaProvider: FC<{
|
||||||
page,
|
page,
|
||||||
setPage,
|
setPage,
|
||||||
|
|
||||||
lastPageCount,
|
|
||||||
setlastPageCount,
|
|
||||||
ResetParams,
|
ResetParams,
|
||||||
nFiltri,
|
nFiltri,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ export const annunciRouter = createTRPCRouter({
|
||||||
getWithCursor: publicProcedure
|
getWithCursor: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
limit: z.number().min(1).max(100).optional(),
|
page: z.number().optional(),
|
||||||
cursor: z.string().optional(),
|
pageSize: z.number(),
|
||||||
tipologia: z.string().optional(),
|
tipologia: z.string().optional(),
|
||||||
comune: z.string().optional(),
|
comune: z.string().optional(),
|
||||||
consegna: z.number().optional(),
|
consegna: z.number().optional(),
|
||||||
|
|
@ -66,13 +66,9 @@ export const annunciRouter = createTRPCRouter({
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { annunci, nextCursor } = await getCursor_AnnunciHandler({
|
return await getCursor_AnnunciHandler({
|
||||||
input,
|
...input,
|
||||||
});
|
});
|
||||||
return {
|
|
||||||
annunci,
|
|
||||||
nextCursor,
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
getMapping: publicProcedure
|
getMapping: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|
|
||||||
|
|
@ -233,34 +233,20 @@ export type AnnuncioRicerca = Pick<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const getCursor_AnnunciHandler = async ({
|
export const getCursor_AnnunciHandler = async ({
|
||||||
input,
|
page = 0,
|
||||||
|
pageSize,
|
||||||
|
tipologia,
|
||||||
|
comune,
|
||||||
|
consegna,
|
||||||
|
sort,
|
||||||
}: {
|
}: {
|
||||||
input: {
|
page?: number;
|
||||||
limit?: number;
|
pageSize: number;
|
||||||
cursor?: string;
|
tipologia?: string;
|
||||||
tipologia?: string;
|
comune?: string;
|
||||||
comune?: string;
|
consegna?: number;
|
||||||
consegna?: number;
|
sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
|
||||||
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" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
let query = db
|
let query = db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
|
|
@ -283,35 +269,18 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
|
|
||||||
.where("web", "=", true)
|
.where("web", "=", true)
|
||||||
.where("stato", "!=", "Sospeso");
|
.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 !== "") {
|
if (tipologia && tipologia !== "") {
|
||||||
query = query.where("tipo", "=", input.tipologia);
|
query = query.where("tipo", "=", tipologia);
|
||||||
}
|
}
|
||||||
if (input.comune && input.comune !== "") {
|
if (comune && comune !== "") {
|
||||||
query = query.where("comune", "like", input.comune);
|
query = query.where("comune", "like", comune);
|
||||||
}
|
}
|
||||||
if (input.consegna) {
|
if (consegna) {
|
||||||
query = query.where("consegna", "=", input.consegna);
|
query = query.where("consegna", "=", consegna);
|
||||||
}
|
}
|
||||||
if (input.sort) {
|
if (sort) {
|
||||||
switch (input.sort) {
|
switch (sort) {
|
||||||
case "Recenti":
|
case "Recenti":
|
||||||
query = query.orderBy("modificato_il", "desc");
|
query = query.orderBy("modificato_il", "desc");
|
||||||
break;
|
break;
|
||||||
|
|
@ -328,32 +297,13 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
}
|
}
|
||||||
query = query.orderBy("id", "asc");
|
query = query.orderBy("id", "asc");
|
||||||
|
|
||||||
const itemsraw = await query.limit(limit + 1).execute();
|
const annunci = await query
|
||||||
const annunci = itemsraw;
|
.limit(pageSize + 1)
|
||||||
let nextCursor: string | undefined = undefined;
|
.offset(page * pageSize)
|
||||||
|
.execute();
|
||||||
if (annunci.length > limit) {
|
const hasMore = annunci.length > pageSize;
|
||||||
const nextItem = annunci.pop(); // return the last item from the array
|
if (annunci.length > pageSize) {
|
||||||
if (!nextItem) {
|
annunci.pop(); // Remove the last item if it exceeds the page size
|
||||||
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()}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const annuncio of annunci) {
|
for (const annuncio of annunci) {
|
||||||
|
|
@ -361,9 +311,10 @@ export const getCursor_AnnunciHandler = async ({
|
||||||
? createSrcset(annuncio.url_immagini)
|
? createSrcset(annuncio.url_immagini)
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
annunci,
|
annunci,
|
||||||
nextCursor,
|
hasMore,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue