diff --git a/apps/infoalloggi/src/components/ui/slider.tsx b/apps/infoalloggi/src/components/ui/slider.tsx index fbf2fd5..11ddf2d 100644 --- a/apps/infoalloggi/src/components/ui/slider.tsx +++ b/apps/infoalloggi/src/components/ui/slider.tsx @@ -3,26 +3,23 @@ import * as React from "react"; import { cn } from "~/lib/utils"; -interface SliderProps - extends React.ComponentPropsWithoutRef { - rangeClassName?: string; - thumbClassName?: string; -} function Slider({ className, - thumbClassName, - rangeClassName, defaultValue, value, min = 0, max = 100, ...props -}: React.ComponentProps & SliderProps) { - const _values = React.useMemo(() => { - if (Array.isArray(value)) return value; - if (Array.isArray(defaultValue)) return defaultValue; - return [min, max]; - }, [value, defaultValue, min, max]); +}: React.ComponentProps) { + const _values = React.useMemo( + () => + Array.isArray(value) + ? value + : Array.isArray(defaultValue) + ? defaultValue + : [min, max], + [value, defaultValue, min, max], + ); return ( {Array.from({ length: _values.length }, (_, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: key={index} /> ))} diff --git a/apps/infoalloggi/src/forms/FormNewServizio.tsx b/apps/infoalloggi/src/forms/FormNewServizio.tsx index 8ed323c..7415c86 100644 --- a/apps/infoalloggi/src/forms/FormNewServizio.tsx +++ b/apps/infoalloggi/src/forms/FormNewServizio.tsx @@ -208,9 +208,7 @@ export const FormNewServizio = ({ onValueChange={(vals) => { field.onChange(Number(vals[0])); }} - rangeClassName="bg-primary" step={50} - thumbClassName="border-primary" value={[form.getValues("budget")]} /> diff --git a/apps/infoalloggi/src/forms/FormNewServizioAcquisto.tsx b/apps/infoalloggi/src/forms/FormNewServizioAcquisto.tsx index e7b1922..63a4390 100644 --- a/apps/infoalloggi/src/forms/FormNewServizioAcquisto.tsx +++ b/apps/infoalloggi/src/forms/FormNewServizioAcquisto.tsx @@ -582,9 +582,7 @@ export const FormNewServizioAcquisto = ({ onValueChange={(vals) => { field.onChange(Number(vals[0])); }} - rangeClassName="bg-neutral-700" step={50} - thumbClassName="border-neutral-700" value={[budget]} /> diff --git a/apps/infoalloggi/src/i18n/en.ts b/apps/infoalloggi/src/i18n/en.ts index 9fad542..e80b1bb 100644 --- a/apps/infoalloggi/src/i18n/en.ts +++ b/apps/infoalloggi/src/i18n/en.ts @@ -283,8 +283,7 @@ export const en: LangDict = { ], comune: "City", consegna: "From", - budgetMin: "Minimum Budget", - budgetMax: "Maximum Budget", + budget: "Budget", filtri_attivi: "Active filters", filtri_reset: "Reset filters", filtro_attivo: "Active filter", diff --git a/apps/infoalloggi/src/i18n/it.ts b/apps/infoalloggi/src/i18n/it.ts index 1e4cc0d..38bc20d 100644 --- a/apps/infoalloggi/src/i18n/it.ts +++ b/apps/infoalloggi/src/i18n/it.ts @@ -288,8 +288,7 @@ export const it: LangDict = { ], comune: "Comune", consegna: "Disponibile da", - budgetMin: "Budget Minimo", - budgetMax: "Budget Massimo", + budget: "Budget", filtri_attivi: "Filtri attivi", filtri_reset: "Reset filtri", filtro_attivo: "Filtro attivo", diff --git a/apps/infoalloggi/src/i18n/locales.ts b/apps/infoalloggi/src/i18n/locales.ts index c25b794..602b500 100644 --- a/apps/infoalloggi/src/i18n/locales.ts +++ b/apps/infoalloggi/src/i18n/locales.ts @@ -250,8 +250,7 @@ export type LangDict = { comune: string; consegna: string; - budgetMin: string; - budgetMax: string; + budget: string; accordion_minifaq: Faq[]; find_similar: string; codice: string; diff --git a/apps/infoalloggi/src/pages/annunci.tsx b/apps/infoalloggi/src/pages/annunci.tsx index 9f8abf3..85e7c96 100644 --- a/apps/infoalloggi/src/pages/annunci.tsx +++ b/apps/infoalloggi/src/pages/annunci.tsx @@ -10,12 +10,11 @@ import { import type { GetServerSideProps } from "next"; import dynamic from "next/dynamic"; import Head from "next/head"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { AccordionComp } from "~/components/accordionComp"; import { CTA_TipologiaModal } from "~/components/annunci_tutorial"; import { LazyCardAnnuncio } from "~/components/annuncio_card"; import { CodiceBox } from "~/components/codiceRicerca"; -import { NumberInput } from "~/components/custom_ui/InputNumber"; import { Layout } from "~/components/Layout"; import { LoadingPage } from "~/components/loading"; import ScrollToTop from "~/components/scroll_top"; @@ -35,11 +34,13 @@ import { SelectTrigger, SelectValue, } from "~/components/ui/select"; +import { Slider } from "~/components/ui/slider"; import { useMediaQuery } from "~/hooks/use-media-query"; import { cn, debounce } from "~/lib/utils"; import { useTranslation } from "~/providers/I18nProvider"; import { type AnnunciOptions, + type FilterOption, RicercaProvider, type SortTypes, useRicerca, @@ -72,7 +73,7 @@ const Annunci = ({ options }: AnnunciPageProps) => { -
+
{t.annunci.titolo} @@ -121,6 +122,13 @@ const Ricerca = () => { ); }; +const sort_options: FilterOption[] = [ + { value: "Recenti", disabled: false }, + { value: "Prezzo", disabled: false }, + { value: "Consegna", disabled: false }, + { value: "Mq", disabled: false }, +]; + const Filters = () => { const { t } = useTranslation(); const { @@ -189,7 +197,7 @@ const Filters = () => { sort: value, }); }} - options={["Recenti", "Prezzo", "Consegna", "Mq"]} + options={sort_options} placeholder={t.seleziona_placeholder} value={sort} /> @@ -210,7 +218,7 @@ const RicercaSideFilters = () => { if (!isBigScreen) return null; return ( -
+
); @@ -240,7 +248,7 @@ const DrowdownFilters = () => { @@ -253,7 +261,7 @@ const RicercaHeader = () => { const { map, setAllParams } = useRicerca(); return ( -
+
@@ -348,7 +356,7 @@ const AnnunciList = () => { interface SelectFilterProps { label: string; id: string; - options: string[]; + options: FilterOption[]; defaultValue?: T; value: T | undefined; onValueChange: (value: T) => void; @@ -402,8 +410,13 @@ export const SelectFilter = ({ {options.map((option) => ( - - {option} + + {option.value} ))} @@ -420,7 +433,13 @@ const ConsegnaFilter = () => { if (consegnaOptions.length === 0) return []; return consegnaOptions .map((c) => - t.parametri.mesi[c] ? { value: c, label: t.parametri.mesi[c] } : null, + t.parametri.mesi[c.value] + ? { + value: c.value, + label: t.parametri.mesi[c.value], + disabled: c.disabled, + } + : null, ) .filter((v) => v !== null); }, [consegnaOptions, t.parametri.mesi]); @@ -467,11 +486,12 @@ const ConsegnaFilter = () => {
- {mappedConsegnaOptions.map(({ value, label }) => ( + {mappedConsegnaOptions.map(({ value, label, disabled }) => ( ))}
); }; - +const MAX_SLIDER_VALUE = 1500; const BudgetFilters = () => { const { t } = useTranslation(); const { minBudget, maxBudget, setAllParams } = useRicerca(); @@ -509,80 +529,39 @@ const BudgetFilters = () => { [setAllParams], ); + const [sliderValue, setSliderValue] = useState<[number, number]>([ + Number(minBudget) / 100 || 0, + Number(maxBudget) / 100 || MAX_SLIDER_VALUE, + ]); + + const onSliderChangeEnd = () => { + const [min, max] = sliderValue; + minDebounced(min === 0 ? null : min * 100); + maxDebounced(max === MAX_SLIDER_VALUE ? null : max * 100); + }; + useEffect(() => { + setSliderValue([ + Number(minBudget) / 100 || 0, + Number(maxBudget) / 100 || MAX_SLIDER_VALUE, + ]); + }, [minBudget, maxBudget]); + return ( <> - {/* -
- - -
- ${value[0]} - ${value[1]} -
-
- */} -
-
+
+
- {minBudget !== null && ( + {(minBudget !== null || maxBudget !== null) && ( - )} -
- - { - if (v && v > 0) { - minDebounced(v * 100); - } else { - minDebounced(null); - } - }} - stepper={100} - suffix=" €" - value={Number(minBudget) / 100} - /> -
- -
-
- - {maxBudget !== null && ( - )}
- { - if (v && v > 0) { - maxDebounced(v * 100); - } else { - maxDebounced(null); - } - }} - stepper={100} - suffix=" €" - value={Number(maxBudget) / 100} + onValueChange={(value) => setSliderValue(value as [number, number])} + onValueCommit={onSliderChangeEnd} + step={100} + value={sliderValue} /> +
+ {sliderValue[0]} € + + {sliderValue[1] === MAX_SLIDER_VALUE ? "∞" : sliderValue[1]} € + +
); diff --git a/apps/infoalloggi/src/providers/RicercaProvider.tsx b/apps/infoalloggi/src/providers/RicercaProvider.tsx index 0a8364a..a4242c2 100644 --- a/apps/infoalloggi/src/providers/RicercaProvider.tsx +++ b/apps/infoalloggi/src/providers/RicercaProvider.tsx @@ -52,6 +52,11 @@ export const buildRicercaUrl = (params: Partial): string => { return queryString ? `/annunci?${queryString}` : "/annunci"; }; +export type FilterOption = { + value: T; + disabled: boolean; +}; + type RicercaContextType = { map: boolean; tipo: RicercaUrlParams["tipo"]; @@ -74,9 +79,9 @@ type RicercaContextType = { ) => Promise; reset: () => void; - comuniOptions: string[]; - tipologieOptions: string[]; - consegnaOptions: number[]; + comuniOptions: FilterOption[]; + tipologieOptions: FilterOption[]; + consegnaOptions: FilterOption[]; sortOptions: typeof sortOptions; nFiltri: number; isLoading: boolean; @@ -133,11 +138,24 @@ export const RicercaProvider: FC<{ }, []); const { tipologieOptions, comuniOptions, consegnaOptions } = useMemo(() => { - const typeSet = new Set(); - const comuneSet = new Set(); - const consegnaSet = new Set(); + // 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); @@ -147,30 +165,43 @@ export const RicercaProvider: FC<{ // Add to Tipologie if it matches everything ELSE (Comune, Consegna, Budget) if (matchesComune && matchesConsegna && matchesBudget) { - typeSet.add(item.tipo); + availableTypes.add(item.tipo); } // Add to Comuni if it matches everything ELSE (Tipo, Consegna, Budget) if (matchesTipo && matchesConsegna && matchesBudget) { - comuneSet.add(item.comune); + availableComuni.add(item.comune); } // Add to Consegna if it matches everything ELSE (Tipo, Comune, Budget) if (matchesTipo && matchesComune && matchesBudget) { - consegnaSet.add(item.consegna); + availableConsegna.add(item.consegna); } } const currentMonth = new Date().getMonth() + 1; // 1-12 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; - }), + 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]);