2025-08-04 17:45:44 +02:00
|
|
|
"use client";
|
|
|
|
|
import {
|
2025-12-29 18:33:00 +01:00
|
|
|
parseAsArrayOf,
|
2025-08-28 18:27:07 +02:00
|
|
|
parseAsBoolean,
|
|
|
|
|
parseAsInteger,
|
|
|
|
|
parseAsString,
|
|
|
|
|
parseAsStringLiteral,
|
2026-01-20 18:38:17 +01:00
|
|
|
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,
|
2026-01-20 18:38:17 +01:00
|
|
|
useCallback,
|
2025-08-28 18:27:07 +02:00
|
|
|
useContext,
|
|
|
|
|
useMemo,
|
|
|
|
|
useTransition,
|
|
|
|
|
} from "react";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2026-01-20 18:38:17 +01: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;
|
2026-01-20 18:38:17 +01:00
|
|
|
consegna: number[];
|
|
|
|
|
minBudget: number;
|
|
|
|
|
maxBudget: number;
|
|
|
|
|
sort: SortTypes;
|
|
|
|
|
};
|
2025-12-29 19:10:46 +01:00
|
|
|
|
2026-01-20 18:38:17 +01:00
|
|
|
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";
|
|
|
|
|
};
|
2025-12-29 19:10:46 +01:00
|
|
|
|
2026-01-20 18:38:17 +01:00
|
|
|
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;
|
2026-01-20 18:38:17 +01:00
|
|
|
|
|
|
|
|
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>;
|
2026-01-20 18:38:17 +01:00
|
|
|
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;
|
2025-12-29 19:10:46 +01:00
|
|
|
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 }) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const [isLoading, startTransition] = useTransition();
|
|
|
|
|
|
2026-01-20 18:38:17 +01:00
|
|
|
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
|
|
|
);
|
|
|
|
|
|
2026-01-20 18:38:17 +01:00
|
|
|
const reset = useCallback(() => {
|
|
|
|
|
setAllParams({
|
|
|
|
|
tipo: "",
|
|
|
|
|
comune: "",
|
|
|
|
|
consegna: null,
|
|
|
|
|
minBudget: null,
|
|
|
|
|
maxBudget: null,
|
|
|
|
|
sort: "Recenti",
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-01-20 19:05:15 +01:00
|
|
|
const { tipologieOptions, comuniOptions, consegnaOptions } = useMemo(() => {
|
|
|
|
|
const typeSet = new Set<string>();
|
|
|
|
|
const comuneSet = new Set<string>();
|
|
|
|
|
const consegnaSet = new Set<number>();
|
|
|
|
|
|
|
|
|
|
for (const item of options) {
|
|
|
|
|
const matchesTipo = !tipo || item.tipo === tipo;
|
|
|
|
|
const matchesComune = !comune || item.comune === comune;
|
|
|
|
|
const matchesConsegna = !consegna || consegna.includes(item.consegna);
|
|
|
|
|
const matchesBudget =
|
|
|
|
|
(!minBudget || item.prezzo >= minBudget) &&
|
|
|
|
|
(!maxBudget || item.prezzo <= maxBudget);
|
|
|
|
|
|
|
|
|
|
// Add to Tipologie if it matches everything ELSE (Comune, Consegna, Budget)
|
|
|
|
|
if (matchesComune && matchesConsegna && matchesBudget) {
|
|
|
|
|
typeSet.add(item.tipo);
|
2025-08-28 18:27:07 +02:00
|
|
|
}
|
2026-01-20 19:05:15 +01:00
|
|
|
|
|
|
|
|
// Add to Comuni if it matches everything ELSE (Tipo, Consegna, Budget)
|
|
|
|
|
if (matchesTipo && matchesConsegna && matchesBudget) {
|
|
|
|
|
comuneSet.add(item.comune);
|
2025-08-28 18:27:07 +02:00
|
|
|
}
|
2026-01-20 19:05:15 +01:00
|
|
|
|
|
|
|
|
// Add to Consegna if it matches everything ELSE (Tipo, Comune, Budget)
|
|
|
|
|
if (matchesTipo && matchesComune && matchesBudget) {
|
|
|
|
|
consegnaSet.add(item.consegna);
|
2025-08-28 18:27:07 +02:00
|
|
|
}
|
2026-01-20 19:05:15 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 09:42:21 +01:00
|
|
|
const currentMonth = new Date().getMonth() + 1; // 1-12
|
|
|
|
|
|
2026-01-20 19:05:15 +01:00
|
|
|
return {
|
|
|
|
|
tipologieOptions: Array.from(typeSet),
|
|
|
|
|
comuniOptions: Array.from(comuneSet).sort((a, b) => a.localeCompare(b)),
|
|
|
|
|
consegnaOptions: Array.from(consegnaSet).sort((a, b) => {
|
|
|
|
|
const distA = (a - currentMonth + 12) % 12;
|
|
|
|
|
const distB = (b - currentMonth + 12) % 12;
|
|
|
|
|
return distA - distB;
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
}, [options, tipo, comune, consegna, minBudget, maxBudget]);
|
|
|
|
|
|
|
|
|
|
const nFiltri =
|
|
|
|
|
(tipo ? 1 : 0) +
|
|
|
|
|
(comune ? 1 : 0) +
|
|
|
|
|
(consegna !== null && consegna.length > 0 ? 1 : 0) +
|
|
|
|
|
(sort !== "Recenti" ? 1 : 0) +
|
|
|
|
|
(minBudget !== null ? 1 : 0) +
|
|
|
|
|
(maxBudget !== null ? 1 : 0);
|
2025-08-28 18:27:07 +02:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<RicercaContext.Provider
|
|
|
|
|
value={{
|
2026-01-20 18:38:17 +01:00
|
|
|
map,
|
|
|
|
|
tipo,
|
2025-08-28 18:27:07 +02:00
|
|
|
comune,
|
2025-12-29 18:33:00 +01:00
|
|
|
consegna,
|
2025-12-29 19:10:46 +01:00
|
|
|
minBudget,
|
|
|
|
|
maxBudget,
|
2025-08-29 16:18:32 +02:00
|
|
|
sort,
|
2025-12-29 18:33:00 +01:00
|
|
|
|
2026-01-20 18:38:17 +01:00
|
|
|
setAllParams,
|
|
|
|
|
reset,
|
2025-12-29 18:33:00 +01:00
|
|
|
|
2026-01-20 18:38:17 +01:00
|
|
|
comuniOptions,
|
|
|
|
|
consegnaOptions,
|
|
|
|
|
tipologieOptions,
|
|
|
|
|
sortOptions,
|
2025-12-29 18:33:00 +01:00
|
|
|
nFiltri,
|
|
|
|
|
isLoading,
|
2025-08-28 18:27:07 +02:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</RicercaContext.Provider>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|