"use client"; import { parseAsArrayOf, parseAsBoolean, parseAsInteger, parseAsString, parseAsStringLiteral, useQueryStates, } from "nuqs"; import { createContext, type FC, type ReactNode, useCallback, useContext, useMemo, useTransition, } from "react"; const sortOptions = ["Recenti", "Prezzo", "Consegna", "Mq"] as const; export type SortTypes = (typeof sortOptions)[number]; type RicercaUrlParams = { map: boolean; tipo: string; comune: string; consegna: number[]; minBudget: number; maxBudget: number; sort: SortTypes; }; export const paramsMapping = { map: "map", tipo: "tipo", comune: "comune", consegna: "consegna", minBudget: "budget_min", maxBudget: "budget_max", sort: "sort", } as const; export const buildRicercaUrl = (params: Partial): 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"; }; export type FilterOption = { value: T; disabled: boolean; }; type RicercaContextType = { map: boolean; tipo: RicercaUrlParams["tipo"]; comune: RicercaUrlParams["comune"] | null; consegna: RicercaUrlParams["consegna"] | null; minBudget: RicercaUrlParams["minBudget"] | null; maxBudget: RicercaUrlParams["maxBudget"] | null; 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"]; }>, ) => Promise; reset: () => void; comuniOptions: FilterOption[]; tipologieOptions: FilterOption[]; consegnaOptions: FilterOption[]; sortOptions: typeof sortOptions; nFiltri: number; isLoading: boolean; }; export type AnnunciOptions = { comune: string; tipo: string; consegna: number; prezzo: number; }[]; const RicercaContext = createContext(undefined); export const useRicerca = () => { const context = useContext(RicercaContext); if (!context) { throw new Error("useRicerca must be used within a RicercaProvider"); } return context; }; export const RicercaProvider: FC<{ children: ReactNode; options: AnnunciOptions; }> = ({ children, options }) => { 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 }, ); const reset = useCallback(() => { setAllParams({ tipo: "", comune: "", consegna: null, minBudget: null, maxBudget: null, sort: "Recenti", }); }, []); const { tipologieOptions, comuniOptions, consegnaOptions } = useMemo(() => { // const typeSet = new Set(); // const comuneSet = new Set(); // const consegnaSet = new Set(); // 1. Sets for ALL distinct values present in the data (The Universe) const allTypes = new Set(); const allComuni = new Set(); const allConsegna = new Set(); // 2. Sets for values available with CURRENT OTHER filters (The Subset) const availableTypes = new Set(); const availableComuni = new Set(); const availableConsegna = new Set(); for (const item of options) { // Populate the Universe sets allTypes.add(item.tipo); allComuni.add(item.comune); allConsegna.add(item.consegna); 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) { availableTypes.add(item.tipo); } // Add to Comuni if it matches everything ELSE (Tipo, Consegna, Budget) if (matchesTipo && matchesConsegna && matchesBudget) { availableComuni.add(item.comune); } // Add to Consegna if it matches everything ELSE (Tipo, Comune, Budget) if (matchesTipo && matchesComune && matchesBudget) { availableConsegna.add(item.consegna); } } const currentMonth = new Date().getMonth() + 1; // 1-12 return { tipologieOptions: Array.from(allTypes).map((t) => ({ value: t, disabled: !availableTypes.has(t), })), comuniOptions: Array.from(allComuni) .sort((a, b) => a.localeCompare(b)) .map((c) => ({ value: c, disabled: !availableComuni.has(c), })), consegnaOptions: Array.from(allConsegna) .sort((a, b) => { const distA = (a - currentMonth + 12) % 12; const distB = (b - currentMonth + 12) % 12; return distA - distB; }) .map((c) => ({ value: c, disabled: !availableConsegna.has(c), })), }; }, [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); return ( {children} ); };