feat: refactor Slider component and update budget filters for improved functionality and responsiveness
This commit is contained in:
parent
1e06fd5584
commit
9f02c49193
8 changed files with 134 additions and 140 deletions
|
|
@ -3,26 +3,23 @@ import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
interface SliderProps
|
|
||||||
extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {
|
|
||||||
rangeClassName?: string;
|
|
||||||
thumbClassName?: string;
|
|
||||||
}
|
|
||||||
function Slider({
|
function Slider({
|
||||||
className,
|
className,
|
||||||
thumbClassName,
|
|
||||||
rangeClassName,
|
|
||||||
defaultValue,
|
defaultValue,
|
||||||
value,
|
value,
|
||||||
min = 0,
|
min = 0,
|
||||||
max = 100,
|
max = 100,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SliderPrimitive.Root> & SliderProps) {
|
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
|
||||||
const _values = React.useMemo(() => {
|
const _values = React.useMemo(
|
||||||
if (Array.isArray(value)) return value;
|
() =>
|
||||||
if (Array.isArray(defaultValue)) return defaultValue;
|
Array.isArray(value)
|
||||||
return [min, max];
|
? value
|
||||||
}, [value, defaultValue, min, max]);
|
: Array.isArray(defaultValue)
|
||||||
|
? defaultValue
|
||||||
|
: [min, max],
|
||||||
|
[value, defaultValue, min, max],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SliderPrimitive.Root
|
<SliderPrimitive.Root
|
||||||
|
|
@ -39,26 +36,22 @@ function Slider({
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Track
|
<SliderPrimitive.Track
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative grow cursor-pointer overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-1.5",
|
"relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-1.5",
|
||||||
)}
|
)}
|
||||||
data-slot="slider-track"
|
data-slot="slider-track"
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Range
|
<SliderPrimitive.Range
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
|
"absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
|
||||||
rangeClassName,
|
|
||||||
)}
|
)}
|
||||||
data-slot="slider-range"
|
data-slot="slider-range"
|
||||||
/>
|
/>
|
||||||
</SliderPrimitive.Track>
|
</SliderPrimitive.Track>
|
||||||
{Array.from({ length: _values.length }, (_, index) => (
|
{Array.from({ length: _values.length }, (_, index) => (
|
||||||
<SliderPrimitive.Thumb
|
<SliderPrimitive.Thumb
|
||||||
className={cn(
|
className="block size-4 shrink-0 cursor-pointer rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:outline-hidden focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50"
|
||||||
"block size-4 shrink-0 cursor-pointer rounded-full border border-primary bg-background shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:outline-hidden focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50",
|
|
||||||
thumbClassName,
|
|
||||||
)}
|
|
||||||
data-slot="slider-thumb"
|
data-slot="slider-thumb"
|
||||||
// biome-ignore lint/suspicious/noArrayIndexKey: <index is safe here>
|
// biome-ignore lint/suspicious/noArrayIndexKey: <ok>
|
||||||
key={index}
|
key={index}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -208,9 +208,7 @@ export const FormNewServizio = ({
|
||||||
onValueChange={(vals) => {
|
onValueChange={(vals) => {
|
||||||
field.onChange(Number(vals[0]));
|
field.onChange(Number(vals[0]));
|
||||||
}}
|
}}
|
||||||
rangeClassName="bg-primary"
|
|
||||||
step={50}
|
step={50}
|
||||||
thumbClassName="border-primary"
|
|
||||||
value={[form.getValues("budget")]}
|
value={[form.getValues("budget")]}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
|
||||||
|
|
@ -582,9 +582,7 @@ export const FormNewServizioAcquisto = ({
|
||||||
onValueChange={(vals) => {
|
onValueChange={(vals) => {
|
||||||
field.onChange(Number(vals[0]));
|
field.onChange(Number(vals[0]));
|
||||||
}}
|
}}
|
||||||
rangeClassName="bg-neutral-700"
|
|
||||||
step={50}
|
step={50}
|
||||||
thumbClassName="border-neutral-700"
|
|
||||||
value={[budget]}
|
value={[budget]}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
|
||||||
|
|
@ -283,8 +283,7 @@ export const en: LangDict = {
|
||||||
],
|
],
|
||||||
comune: "City",
|
comune: "City",
|
||||||
consegna: "From",
|
consegna: "From",
|
||||||
budgetMin: "Minimum Budget",
|
budget: "Budget",
|
||||||
budgetMax: "Maximum Budget",
|
|
||||||
filtri_attivi: "Active filters",
|
filtri_attivi: "Active filters",
|
||||||
filtri_reset: "Reset filters",
|
filtri_reset: "Reset filters",
|
||||||
filtro_attivo: "Active filter",
|
filtro_attivo: "Active filter",
|
||||||
|
|
|
||||||
|
|
@ -288,8 +288,7 @@ export const it: LangDict = {
|
||||||
],
|
],
|
||||||
comune: "Comune",
|
comune: "Comune",
|
||||||
consegna: "Disponibile da",
|
consegna: "Disponibile da",
|
||||||
budgetMin: "Budget Minimo",
|
budget: "Budget",
|
||||||
budgetMax: "Budget Massimo",
|
|
||||||
filtri_attivi: "Filtri attivi",
|
filtri_attivi: "Filtri attivi",
|
||||||
filtri_reset: "Reset filtri",
|
filtri_reset: "Reset filtri",
|
||||||
filtro_attivo: "Filtro attivo",
|
filtro_attivo: "Filtro attivo",
|
||||||
|
|
|
||||||
|
|
@ -250,8 +250,7 @@ export type LangDict = {
|
||||||
|
|
||||||
comune: string;
|
comune: string;
|
||||||
consegna: string;
|
consegna: string;
|
||||||
budgetMin: string;
|
budget: string;
|
||||||
budgetMax: string;
|
|
||||||
accordion_minifaq: Faq[];
|
accordion_minifaq: Faq[];
|
||||||
find_similar: string;
|
find_similar: string;
|
||||||
codice: string;
|
codice: string;
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,11 @@ import {
|
||||||
import type { GetServerSideProps } from "next";
|
import type { GetServerSideProps } from "next";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
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 { LazyCardAnnuncio } from "~/components/annuncio_card";
|
import { LazyCardAnnuncio } from "~/components/annuncio_card";
|
||||||
import { CodiceBox } from "~/components/codiceRicerca";
|
import { CodiceBox } from "~/components/codiceRicerca";
|
||||||
import { NumberInput } from "~/components/custom_ui/InputNumber";
|
|
||||||
import { Layout } from "~/components/Layout";
|
import { Layout } from "~/components/Layout";
|
||||||
import { LoadingPage } from "~/components/loading";
|
import { LoadingPage } from "~/components/loading";
|
||||||
import ScrollToTop from "~/components/scroll_top";
|
import ScrollToTop from "~/components/scroll_top";
|
||||||
|
|
@ -35,11 +34,13 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "~/components/ui/select";
|
} from "~/components/ui/select";
|
||||||
|
import { Slider } from "~/components/ui/slider";
|
||||||
import { useMediaQuery } from "~/hooks/use-media-query";
|
import { useMediaQuery } from "~/hooks/use-media-query";
|
||||||
import { cn, debounce } from "~/lib/utils";
|
import { cn, debounce } from "~/lib/utils";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import {
|
import {
|
||||||
type AnnunciOptions,
|
type AnnunciOptions,
|
||||||
|
type FilterOption,
|
||||||
RicercaProvider,
|
RicercaProvider,
|
||||||
type SortTypes,
|
type SortTypes,
|
||||||
useRicerca,
|
useRicerca,
|
||||||
|
|
@ -72,7 +73,7 @@ const Annunci = ({ options }: AnnunciPageProps) => {
|
||||||
<meta content={t.heads.annunci_description} name="description" />
|
<meta content={t.heads.annunci_description} name="description" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<main className="relative mx-auto w-full max-w-10xl space-y-5 bg-background px-2 py-5 md:px-8">
|
<main className="relative mx-auto w-full max-w-500 space-y-5 bg-background px-2 py-5 md:px-8">
|
||||||
<Badge className="bg-muted py-1 text-foreground text-sm outline outline-muted-foreground">
|
<Badge className="bg-muted py-1 text-foreground text-sm outline outline-muted-foreground">
|
||||||
{t.annunci.titolo}
|
{t.annunci.titolo}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -121,6 +122,13 @@ const Ricerca = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sort_options: FilterOption<SortTypes>[] = [
|
||||||
|
{ value: "Recenti", disabled: false },
|
||||||
|
{ value: "Prezzo", disabled: false },
|
||||||
|
{ value: "Consegna", disabled: false },
|
||||||
|
{ value: "Mq", disabled: false },
|
||||||
|
];
|
||||||
|
|
||||||
const Filters = () => {
|
const Filters = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
|
|
@ -189,7 +197,7 @@ const Filters = () => {
|
||||||
sort: value,
|
sort: value,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
options={["Recenti", "Prezzo", "Consegna", "Mq"]}
|
options={sort_options}
|
||||||
placeholder={t.seleziona_placeholder}
|
placeholder={t.seleziona_placeholder}
|
||||||
value={sort}
|
value={sort}
|
||||||
/>
|
/>
|
||||||
|
|
@ -210,7 +218,7 @@ const RicercaSideFilters = () => {
|
||||||
|
|
||||||
if (!isBigScreen) return null;
|
if (!isBigScreen) return null;
|
||||||
return (
|
return (
|
||||||
<div className="flex h-fit w-46 max-w-46 shrink-0 grow-0 flex-col gap-3 overflow-clip rounded-md bg-secondary p-2 outline-neutral-300">
|
<div className="flex h-fit w-full max-w-72 shrink-0 grow-0 flex-col gap-3 overflow-clip rounded-md bg-secondary p-2 outline-neutral-300">
|
||||||
<Filters />
|
<Filters />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -240,7 +248,7 @@ const DrowdownFilters = () => {
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
align="start"
|
align="start"
|
||||||
className="flex w-60 max-w-3xl flex-col gap-2 p-4"
|
className="flex w-full min-w-80 max-w-3xl flex-col gap-2 p-4"
|
||||||
>
|
>
|
||||||
<Filters />
|
<Filters />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|
@ -253,7 +261,7 @@ const RicercaHeader = () => {
|
||||||
const { map, setAllParams } = useRicerca();
|
const { map, setAllParams } = useRicerca();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-10xl space-y-5">
|
<div className="mx-auto w-full max-w-500 space-y-5">
|
||||||
<div className="flex w-full flex-col-reverse gap-2 sm:flex-row">
|
<div className="flex w-full flex-col-reverse gap-2 sm:flex-row">
|
||||||
<div className="flex w-full items-center gap-2 sm:w-auto">
|
<div className="flex w-full items-center gap-2 sm:w-auto">
|
||||||
<DrowdownFilters />
|
<DrowdownFilters />
|
||||||
|
|
@ -348,7 +356,7 @@ const AnnunciList = () => {
|
||||||
interface SelectFilterProps<T> {
|
interface SelectFilterProps<T> {
|
||||||
label: string;
|
label: string;
|
||||||
id: string;
|
id: string;
|
||||||
options: string[];
|
options: FilterOption<string>[];
|
||||||
defaultValue?: T;
|
defaultValue?: T;
|
||||||
value: T | undefined;
|
value: T | undefined;
|
||||||
onValueChange: (value: T) => void;
|
onValueChange: (value: T) => void;
|
||||||
|
|
@ -402,8 +410,13 @@ export const SelectFilter = <T extends string>({
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<SelectItem className="text-left" key={option} value={option}>
|
<SelectItem
|
||||||
{option}
|
className="text-left"
|
||||||
|
disabled={option.disabled}
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
>
|
||||||
|
{option.value}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -420,7 +433,13 @@ const ConsegnaFilter = () => {
|
||||||
if (consegnaOptions.length === 0) return [];
|
if (consegnaOptions.length === 0) return [];
|
||||||
return consegnaOptions
|
return consegnaOptions
|
||||||
.map((c) =>
|
.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);
|
.filter((v) => v !== null);
|
||||||
}, [consegnaOptions, t.parametri.mesi]);
|
}, [consegnaOptions, t.parametri.mesi]);
|
||||||
|
|
@ -467,11 +486,12 @@ const ConsegnaFilter = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-1 rounded-md bg-background p-1">
|
<div className="flex flex-wrap gap-1 rounded-md bg-background p-1">
|
||||||
{mappedConsegnaOptions.map(({ value, label }) => (
|
{mappedConsegnaOptions.map(({ value, label, disabled }) => (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
consegna2?.includes(value) && "border border-primary",
|
consegna2?.includes(value) && "border border-primary",
|
||||||
)}
|
)}
|
||||||
|
disabled={disabled && !consegna2?.includes(value)}
|
||||||
key={value}
|
key={value}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await onValueChange(value);
|
await onValueChange(value);
|
||||||
|
|
@ -479,14 +499,14 @@ const ConsegnaFilter = () => {
|
||||||
size="sm"
|
size="sm"
|
||||||
variant={consegna2?.includes(value) ? "default" : "outline"}
|
variant={consegna2?.includes(value) ? "default" : "outline"}
|
||||||
>
|
>
|
||||||
<span className="text-sm">{label.substring(0, 3)}</span>
|
<span className="text-sm">{label?.substring(0, 3)}</span>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const MAX_SLIDER_VALUE = 1500;
|
||||||
const BudgetFilters = () => {
|
const BudgetFilters = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { minBudget, maxBudget, setAllParams } = useRicerca();
|
const { minBudget, maxBudget, setAllParams } = useRicerca();
|
||||||
|
|
@ -509,80 +529,39 @@ const BudgetFilters = () => {
|
||||||
[setAllParams],
|
[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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/*
|
<div className="flex w-full flex-col gap-2">
|
||||||
<div className="flex w-full max-w-md flex-col gap-2">
|
<div className="flex flex-row items-center gap-4">
|
||||||
<Label htmlFor="slider">Price Range</Label>
|
|
||||||
<Slider id="slider" max={1000} min={0} onValueChange={setValue} value={value} />
|
|
||||||
<div className="flex items-center justify-between text-muted-foreground text-sm">
|
|
||||||
<span>${value[0]}</span>
|
|
||||||
<span>${value[1]}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
*/}
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="flex h-4 flex-row gap-4 px-1">
|
|
||||||
<Label
|
<Label
|
||||||
className={cn(
|
className={cn("font-semibold text-muted-foreground")}
|
||||||
"font-semibold text-muted-foreground",
|
htmlFor="slider"
|
||||||
minBudget !== null && "text-primary",
|
|
||||||
)}
|
|
||||||
htmlFor="prezzoMIN"
|
|
||||||
>
|
>
|
||||||
{t.annunci.budgetMin}
|
{t.annunci.budget}
|
||||||
</Label>
|
</Label>
|
||||||
{minBudget !== null && (
|
{(minBudget !== null || maxBudget !== null) && (
|
||||||
<button
|
<button
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await setAllParams({
|
await setAllParams({
|
||||||
minBudget: null,
|
minBudget: null,
|
||||||
});
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<RotateCcwIcon className="size-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NumberInput
|
|
||||||
className="bg-background"
|
|
||||||
decimalScale={2}
|
|
||||||
decimalSeparator=","
|
|
||||||
fixedDecimalScale
|
|
||||||
id="prezzoMIN"
|
|
||||||
min={0}
|
|
||||||
onValueChange={(v) => {
|
|
||||||
if (v && v > 0) {
|
|
||||||
minDebounced(v * 100);
|
|
||||||
} else {
|
|
||||||
minDebounced(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
stepper={100}
|
|
||||||
suffix=" €"
|
|
||||||
value={Number(minBudget) / 100}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="flex h-4 flex-row gap-4 px-1">
|
|
||||||
<Label
|
|
||||||
className={cn(
|
|
||||||
"font-semibold text-muted-foreground",
|
|
||||||
maxBudget !== null && "text-primary",
|
|
||||||
)}
|
|
||||||
htmlFor="prezzoMAX"
|
|
||||||
>
|
|
||||||
{t.annunci.budgetMax}
|
|
||||||
</Label>
|
|
||||||
{maxBudget !== null && (
|
|
||||||
<button
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={async () => {
|
|
||||||
await setAllParams({
|
|
||||||
maxBudget: null,
|
maxBudget: null,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
@ -592,24 +571,22 @@ const BudgetFilters = () => {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<NumberInput
|
<Slider
|
||||||
className="bg-background"
|
className="mt-2"
|
||||||
decimalScale={2}
|
id="slider"
|
||||||
decimalSeparator=","
|
max={MAX_SLIDER_VALUE}
|
||||||
fixedDecimalScale
|
|
||||||
id="prezzoMAX"
|
|
||||||
min={0}
|
min={0}
|
||||||
onValueChange={(v) => {
|
onValueChange={(value) => setSliderValue(value as [number, number])}
|
||||||
if (v && v > 0) {
|
onValueCommit={onSliderChangeEnd}
|
||||||
maxDebounced(v * 100);
|
step={100}
|
||||||
} else {
|
value={sliderValue}
|
||||||
maxDebounced(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
stepper={100}
|
|
||||||
suffix=" €"
|
|
||||||
value={Number(maxBudget) / 100}
|
|
||||||
/>
|
/>
|
||||||
|
<div className="flex items-center justify-between text-muted-foreground text-sm">
|
||||||
|
<span>{sliderValue[0]} €</span>
|
||||||
|
<span>
|
||||||
|
{sliderValue[1] === MAX_SLIDER_VALUE ? "∞" : sliderValue[1]} €
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ export const buildRicercaUrl = (params: Partial<RicercaUrlParams>): string => {
|
||||||
return queryString ? `/annunci?${queryString}` : "/annunci";
|
return queryString ? `/annunci?${queryString}` : "/annunci";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FilterOption<T> = {
|
||||||
|
value: T;
|
||||||
|
disabled: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type RicercaContextType = {
|
type RicercaContextType = {
|
||||||
map: boolean;
|
map: boolean;
|
||||||
tipo: RicercaUrlParams["tipo"];
|
tipo: RicercaUrlParams["tipo"];
|
||||||
|
|
@ -74,9 +79,9 @@ type RicercaContextType = {
|
||||||
) => Promise<URLSearchParams>;
|
) => Promise<URLSearchParams>;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
|
|
||||||
comuniOptions: string[];
|
comuniOptions: FilterOption<string>[];
|
||||||
tipologieOptions: string[];
|
tipologieOptions: FilterOption<string>[];
|
||||||
consegnaOptions: number[];
|
consegnaOptions: FilterOption<number>[];
|
||||||
sortOptions: typeof sortOptions;
|
sortOptions: typeof sortOptions;
|
||||||
nFiltri: number;
|
nFiltri: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
|
@ -133,11 +138,24 @@ export const RicercaProvider: FC<{
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { tipologieOptions, comuniOptions, consegnaOptions } = useMemo(() => {
|
const { tipologieOptions, comuniOptions, consegnaOptions } = useMemo(() => {
|
||||||
const typeSet = new Set<string>();
|
// const typeSet = new Set<string>();
|
||||||
const comuneSet = new Set<string>();
|
// const comuneSet = new Set<string>();
|
||||||
const consegnaSet = new Set<number>();
|
// const consegnaSet = new Set<number>();
|
||||||
|
// 1. Sets for ALL distinct values present in the data (The Universe)
|
||||||
|
const allTypes = new Set<string>();
|
||||||
|
const allComuni = new Set<string>();
|
||||||
|
const allConsegna = new Set<number>();
|
||||||
|
|
||||||
|
// 2. Sets for values available with CURRENT OTHER filters (The Subset)
|
||||||
|
const availableTypes = new Set<string>();
|
||||||
|
const availableComuni = new Set<string>();
|
||||||
|
const availableConsegna = new Set<number>();
|
||||||
|
|
||||||
for (const item of options) {
|
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 matchesTipo = !tipo || item.tipo === tipo;
|
||||||
const matchesComune = !comune || item.comune === comune;
|
const matchesComune = !comune || item.comune === comune;
|
||||||
const matchesConsegna = !consegna || consegna.includes(item.consegna);
|
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)
|
// Add to Tipologie if it matches everything ELSE (Comune, Consegna, Budget)
|
||||||
if (matchesComune && matchesConsegna && matchesBudget) {
|
if (matchesComune && matchesConsegna && matchesBudget) {
|
||||||
typeSet.add(item.tipo);
|
availableTypes.add(item.tipo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to Comuni if it matches everything ELSE (Tipo, Consegna, Budget)
|
// Add to Comuni if it matches everything ELSE (Tipo, Consegna, Budget)
|
||||||
if (matchesTipo && matchesConsegna && matchesBudget) {
|
if (matchesTipo && matchesConsegna && matchesBudget) {
|
||||||
comuneSet.add(item.comune);
|
availableComuni.add(item.comune);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to Consegna if it matches everything ELSE (Tipo, Comune, Budget)
|
// Add to Consegna if it matches everything ELSE (Tipo, Comune, Budget)
|
||||||
if (matchesTipo && matchesComune && matchesBudget) {
|
if (matchesTipo && matchesComune && matchesBudget) {
|
||||||
consegnaSet.add(item.consegna);
|
availableConsegna.add(item.consegna);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentMonth = new Date().getMonth() + 1; // 1-12
|
const currentMonth = new Date().getMonth() + 1; // 1-12
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tipologieOptions: Array.from(typeSet),
|
tipologieOptions: Array.from(allTypes).map((t) => ({
|
||||||
comuniOptions: Array.from(comuneSet).sort((a, b) => a.localeCompare(b)),
|
value: t,
|
||||||
consegnaOptions: Array.from(consegnaSet).sort((a, b) => {
|
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 distA = (a - currentMonth + 12) % 12;
|
||||||
const distB = (b - currentMonth + 12) % 12;
|
const distB = (b - currentMonth + 12) % 12;
|
||||||
return distA - distB;
|
return distA - distB;
|
||||||
}),
|
})
|
||||||
|
.map((c) => ({
|
||||||
|
value: c,
|
||||||
|
disabled: !availableConsegna.has(c),
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
}, [options, tipo, comune, consegna, minBudget, maxBudget]);
|
}, [options, tipo, comune, consegna, minBudget, maxBudget]);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue