diff --git a/tradein-mvp/frontend/src/app/sale-share/page.tsx b/tradein-mvp/frontend/src/app/sale-share/page.tsx index 032f0cd3..df6eae51 100644 --- a/tradein-mvp/frontend/src/app/sale-share/page.tsx +++ b/tradein-mvp/frontend/src/app/sale-share/page.tsx @@ -49,6 +49,10 @@ export default function SaleSharePage() { const [yearMax, setYearMax] = useState(""); const [houseType, setHouseType] = useState(""); const [sort, setSort] = useState("share_desc"); + // Окно метрики: «сейчас» (активные сейчас) или «за 45 дней». + const [window, setWindow] = useState<"now" | "45d">("now"); + // Клиентский фильтр «мин. объявлений в доме» (см. filteredBuildings ниже). + const [minCount, setMinCount] = useState(0); // Выбранный дом (drawer) + наведённый дом (sync со списком/картой). const [selected, setSelected] = useState(null); @@ -62,6 +66,15 @@ export default function SaleSharePage() { const debYearMin = useDebouncedValue(yearMin, 400); const debYearMax = useDebouncedValue(yearMax, 400); + // Сорт, уходящий в API, зависит от окна: при «45 дней» share/active мапятся + // на 45-дневные метрики (остальные — цена/экспозиция — без изменений). + const apiSort: SaleShareSort = useMemo(() => { + if (window !== "45d") return sort; + if (sort === "share_desc") return "share_45d_desc"; + if (sort === "active_desc") return "count_45d_desc"; + return sort; + }, [sort, window]); + const query: SaleShareQuery = useMemo( () => ({ min_pct: debMinPct, @@ -71,19 +84,30 @@ export default function SaleSharePage() { year_min: toNumOrNull(debYearMin), year_max: toNumOrNull(debYearMax), house_type: houseType || null, - sort, + sort: apiSort, limit: 200, }), - [debMinPct, debCity, debPriceMin, debPriceMax, debYearMin, debYearMax, houseType, sort], + [debMinPct, debCity, debPriceMin, debPriceMax, debYearMin, debYearMax, houseType, apiSort], ); const summaryQ = useSaleShareSummary(); const buildingsQ = useSaleShareBuildings(query); const buildings = useMemo(() => buildingsQ.data ?? [], [buildingsQ.data]); + // Клиентский фильтр «мин. объявлений в доме» — намеренно НЕ серверный: + // не трогаем query-key/бэкенд, фильтруем уже загруженный список по окну. + // Счётчик null трактуем как 0; minCount === 0 → фильтр выключен. + const filteredBuildings = useMemo(() => { + if (minCount <= 0) return buildings; + return buildings.filter((b) => { + const count = (window === "45d" ? b.listings_45d : b.active_secondary) ?? 0; + return count >= minCount; + }); + }, [buildings, minCount, window]); + // Стабильные колбэки (маркеры карты замыкают их при отрисовке). - const buildingsRef = useRef(buildings); - buildingsRef.current = buildings; + const buildingsRef = useRef(filteredBuildings); + buildingsRef.current = filteredBuildings; const handleSelect = useCallback((houseId: number) => { setSelected(buildingsRef.current.find((b) => b.house_id === houseId) ?? null); }, []); @@ -164,6 +188,10 @@ export default function SaleSharePage() { houseTypeOptions={houseTypeOptions} sort={sort} onSort={setSort} + window={window} + onWindow={setWindow} + minCount={minCount} + onMinCount={setMinCount} />
@@ -181,7 +209,7 @@ export default function SaleSharePage() { diff --git a/tradein-mvp/frontend/src/components/trade-in/SaleShareControls.tsx b/tradein-mvp/frontend/src/components/trade-in/SaleShareControls.tsx index bb1f0932..fe0a1f5b 100644 --- a/tradein-mvp/frontend/src/components/trade-in/SaleShareControls.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/SaleShareControls.tsx @@ -81,6 +81,10 @@ interface Props { houseTypeOptions: string[]; sort: SaleShareSort; onSort: (v: SaleShareSort) => void; + window: "now" | "45d"; + onWindow: (v: "now" | "45d") => void; + minCount: number; + onMinCount: (v: number) => void; } export function SaleShareControls({ @@ -102,6 +106,10 @@ export function SaleShareControls({ houseTypeOptions, sort, onSort, + window, + onWindow, + minCount, + onMinCount, }: Props) { const sliderMax = summary && summary.max_pct != null @@ -118,6 +126,30 @@ export function SaleShareControls({
+
+ + Окно метрики + +
+ + +
+
+
@@ -216,6 +248,22 @@ export function SaleShareControls({ ))}
+
+ + { + const n = Math.floor(Number(e.target.value)); + onMinCount(Number.isFinite(n) && n > 0 ? n : 0); + }} + placeholder="0" + /> +