infoalloggi-monorepo/apps/infoalloggi/src/providers/RicercaProvider.tsx

236 lines
5.5 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
"use client";
import {
parseAsArrayOf,
2025-08-28 18:27:07 +02:00
parseAsBoolean,
parseAsInteger,
parseAsString,
parseAsStringLiteral,
useQueryStates,
2025-08-04 17:45:44 +02:00
} from "nuqs";
2025-08-28 18:27:07 +02:00
import {
createContext,
type FC,
type ReactNode,
useCallback,
2025-08-28 18:27:07 +02:00
useContext,
useEffect,
2025-08-28 18:27:07 +02:00
useMemo,
useState,
2025-08-28 18:27:07 +02:00
useTransition,
} from "react";
2025-08-04 17:45:44 +02:00
const sortOptions = ["Recenti", "Prezzo", "Consegna", "Mq"] as const;
export type SortTypes = (typeof sortOptions)[number];
type RicercaUrlParams = {
2025-08-28 18:27:07 +02:00
map: boolean;
tipo: string;
comune: string;
consegna: number[];
minBudget: number;
maxBudget: number;
sort: SortTypes;
};
export const paramsMapping = {
map: "m",
tipo: "t",
comune: "c",
consegna: "f",
minBudget: "bmin",
maxBudget: "bmax",
sort: "s",
} as const;
export const buildRicercaUrl = (params: Partial<RicercaUrlParams>): string => {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
const mappedKey = paramsMapping[key as keyof typeof paramsMapping];
if (value !== undefined && value !== null) {
searchParams.set(mappedKey, value.toString());
}
});
const queryString = searchParams.toString();
return queryString ? `/annunci?${queryString}` : "/annunci";
};
type RicercaContextType = {
map: boolean;
tipo: RicercaUrlParams["tipo"];
comune: RicercaUrlParams["comune"] | null;
consegna: RicercaUrlParams["consegna"] | null;
minBudget: RicercaUrlParams["minBudget"] | null;
maxBudget: RicercaUrlParams["maxBudget"] | null;
2025-08-28 18:27:07 +02:00
sort: SortTypes;
setAllParams: (
params: Partial<{
tipo: RicercaUrlParams["tipo"];
comune: RicercaUrlParams["comune"] | null;
consegna: RicercaUrlParams["consegna"] | null;
minBudget: RicercaUrlParams["minBudget"] | null;
maxBudget: RicercaUrlParams["maxBudget"] | null;
sort: RicercaUrlParams["sort"];
map: RicercaUrlParams["map"];
}>,
2025-08-28 18:27:07 +02:00
) => Promise<URLSearchParams>;
reset: () => void;
2025-08-28 18:27:07 +02:00
comuniOptions: string[];
tipologieOptions: string[];
consegnaOptions: number[];
sortOptions: typeof sortOptions;
nFiltri: number;
isLoading: boolean;
2025-08-04 17:45:44 +02:00
};
export type AnnunciOptions = {
2025-08-28 18:27:07 +02:00
comune: string;
tipo: string;
consegna: number;
prezzo: number;
2025-08-04 17:45:44 +02:00
}[];
const RicercaContext = createContext<RicercaContextType | undefined>(undefined);
export const useRicerca = () => {
2025-08-28 18:27:07 +02:00
const context = useContext(RicercaContext);
if (!context) {
throw new Error("useRicerca must be used within a RicercaProvider");
}
return context;
2025-08-04 17:45:44 +02:00
};
export const RicercaProvider: FC<{
2025-08-28 18:27:07 +02:00
children: ReactNode;
options: AnnunciOptions;
2025-08-04 17:45:44 +02:00
}> = ({ children, options }) => {
const [nFiltri, setNFiltri] = useState(0);
2025-08-28 18:27:07 +02:00
const [isLoading, startTransition] = useTransition();
const [
{ map, tipo, comune, consegna, minBudget, maxBudget, sort },
setAllParams,
] = useQueryStates(
{
map: parseAsBoolean.withDefault(false),
tipo: parseAsString.withDefault(""),
comune: parseAsString.withDefault(""),
consegna: parseAsArrayOf(parseAsInteger),
minBudget: parseAsInteger,
maxBudget: parseAsInteger,
sort: parseAsStringLiteral(sortOptions).withDefault("Recenti"),
},
{ startTransition, shallow: true, urlKeys: paramsMapping },
2025-08-28 18:27:07 +02:00
);
const reset = useCallback(() => {
setAllParams({
tipo: "",
comune: "",
consegna: null,
minBudget: null,
maxBudget: null,
sort: "Recenti",
});
}, []);
2025-08-28 18:27:07 +02:00
const tipologieOptions = useMemo(() => {
const set = new Set<string>();
options.forEach((item) => {
if (!comune || item.comune === comune) {
if (!consegna || consegna.includes(item.consegna)) {
if (!minBudget || item.prezzo >= minBudget) {
if (!maxBudget || item.prezzo <= maxBudget) {
set.add(item.tipo);
}
}
2025-08-28 18:27:07 +02:00
}
}
});
return Array.from(set);
}, [options, comune, consegna, minBudget, maxBudget]);
2025-08-28 18:27:07 +02:00
const comuniOptions = useMemo(() => {
const set = new Set<string>();
options.forEach((item) => {
if (!tipo || item.tipo === tipo) {
if (!consegna || consegna.includes(item.consegna)) {
if (!minBudget || item.prezzo >= minBudget) {
if (!maxBudget || item.prezzo <= maxBudget) {
set.add(item.comune);
}
}
2025-08-28 18:27:07 +02:00
}
}
});
return Array.from(set).sort((a, b) => a.localeCompare(b));
}, [options, tipo, consegna, minBudget, maxBudget]);
2025-08-28 18:27:07 +02:00
const consegnaOptions = useMemo(() => {
const set = new Set<number>();
options.forEach((item) => {
if (!tipo || item.tipo === tipo) {
if (!comune || item.comune === comune) {
if (!minBudget || item.prezzo >= minBudget) {
if (!maxBudget || item.prezzo <= maxBudget) {
set.add(item.consegna);
}
}
2025-08-28 18:27:07 +02:00
}
}
});
const currentMonth = new Date().getMonth() + 1; // 1-12
return Array.from(set).sort((a, b) => {
// Calculate distance from current month (wrapping around)
const distA = (a - currentMonth + 12) % 12;
const distB = (b - currentMonth + 12) % 12;
return distA - distB;
});
}, [options, tipo, comune, minBudget, maxBudget]);
2025-08-28 18:27:07 +02:00
const updateNFiltri = () => {
setNFiltri(
[tipo, comune].reduce(
(count, item) => (item !== "" ? count + 1 : count),
0,
) +
(consegna !== null ? 1 : 0) +
(sort !== "Recenti" ? 1 : 0) +
(minBudget !== null ? 1 : 0) +
(maxBudget !== null ? 1 : 0),
);
2025-08-28 18:27:07 +02:00
};
useEffect(() => {
updateNFiltri();
}, [tipo, comune, consegna, minBudget, maxBudget, sort]);
2025-08-28 18:27:07 +02:00
return (
<RicercaContext.Provider
value={{
map,
tipo,
2025-08-28 18:27:07 +02:00
comune,
consegna,
minBudget,
maxBudget,
2025-08-29 16:18:32 +02:00
sort,
setAllParams,
reset,
comuniOptions,
consegnaOptions,
tipologieOptions,
sortOptions,
nFiltri,
isLoading,
2025-08-28 18:27:07 +02:00
}}
>
{children}
</RicercaContext.Provider>
);
2025-08-04 17:45:44 +02:00
};