Redesign filter panel with range sliders, separated visualization card, and backend filter support

Simplify the filter UI to show only essential filters (type toggle, price/bedroom
range sliders, min size) by default, with advanced filters collapsed. Extract
visualization controls (color-by metric, POI travel mode) into a separate
VisualizationCard component. Wire up previously ignored backend filters: max_sqm,
min/max_price_per_sqm, and district_names now work end-to-end.
This commit is contained in:
Viktor Barzin 2026-02-08 18:50:06 +00:00
parent 1f4a3f858c
commit 743e018668
No known key found for this signature in database
GPG key ID: 0EB088298288D958
11 changed files with 422 additions and 588 deletions

View file

@ -0,0 +1,70 @@
import { Slider } from "./slider";
import { Input } from "./input";
interface RangeSliderFieldProps {
label: string;
min: number;
max: number;
step: number;
value: [number, number];
onValueChange: (value: [number, number]) => void;
formatValue?: (v: number) => string;
}
export function RangeSliderField({
label,
min,
max,
step,
value,
onValueChange,
formatValue = (v) => String(v),
}: RangeSliderFieldProps) {
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-medium">{label}</span>
<span className="text-xs text-muted-foreground">
{formatValue(value[0])} {formatValue(value[1])}
</span>
</div>
<Slider
min={min}
max={max}
step={step}
value={value}
onValueChange={(v) => onValueChange(v as [number, number])}
/>
<div className="grid grid-cols-2 gap-2">
<Input
type="number"
className="h-7 text-xs"
min={min}
max={value[1]}
step={step}
value={value[0]}
onChange={(e) => {
const v = Number(e.target.value);
if (!isNaN(v)) {
onValueChange([Math.min(v, value[1]), value[1]]);
}
}}
/>
<Input
type="number"
className="h-7 text-xs"
min={value[0]}
max={max}
step={step}
value={value[1]}
onChange={(e) => {
const v = Number(e.target.value);
if (!isNaN(v)) {
onValueChange([value[0], Math.max(v, value[0])]);
}
}}
/>
</div>
</div>
);
}

View file

@ -19,7 +19,7 @@ const Slider = React.forwardRef<
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
{props.defaultValue?.map((_, index) => (
{(props.defaultValue ?? props.value)?.map((_, index) => (
<SliderPrimitive.Thumb
key={index}
className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"