UI exposure for Stage 3 (IMV cache) + Stage 2c (houses data).
New components:
- IMVBenchmark: показывает Avito recommended price + range + market_count + сравнение с нашей оценкой (diff %)
- HouseInfoCard: показывает ближайший дом — rating + reviews + 9 characteristics (year/floors/elevators/concierge/etc)
New hooks:
- useEstimateImvBenchmark(id) → GET /api/v1/trade-in/estimate/{id}/imv-benchmark
- useEstimateHouses(id) → GET /api/v1/trade-in/estimate/{id}/houses
2 new TS types: HouseInfoForEstimate, IMVBenchmarkResponse (mirror Stage 4a backend Pydantic schemas).
Wired в page.tsx между HeroSummary и ListingsCard в result section.
Components conditionally render: возвращают null если data unavailable.
CSS: добавлены .imv-benchmark + .house-info-card стили (responsive grid).
TS strict, ESLint clean, next build pass.
Refs: AvitoScraper_v2_Implementation_Plan Stage 4b. Backend contract: PR Stage 4a (паралельный).
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
|
|
import { apiFetch } from "./api";
|
|
import type {
|
|
AggregatedEstimate,
|
|
HouseInfoForEstimate,
|
|
IMVBenchmarkResponse,
|
|
TradeInEstimateInput,
|
|
} from "@/types/trade-in";
|
|
|
|
const BASE = "/api/v1/trade-in";
|
|
|
|
/**
|
|
* POST /api/v1/trade-in/estimate
|
|
* Sends apartment parameters and returns AggregatedEstimate (mock data for now).
|
|
*/
|
|
export function useEstimateMutation() {
|
|
return useMutation<AggregatedEstimate, Error, TradeInEstimateInput>({
|
|
mutationFn: (input) =>
|
|
apiFetch<AggregatedEstimate>(`${BASE}/estimate`, {
|
|
method: "POST",
|
|
body: JSON.stringify(input),
|
|
}),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/trade-in/estimate/{id}
|
|
* Fetches a previously computed estimate by UUID (for shareable links / PDF).
|
|
*/
|
|
export function useEstimate(estimate_id: string | null) {
|
|
return useQuery<AggregatedEstimate>({
|
|
queryKey: ["trade-in", "estimate", estimate_id],
|
|
queryFn: () =>
|
|
apiFetch<AggregatedEstimate>(`${BASE}/estimate/${estimate_id}`),
|
|
enabled: estimate_id !== null && estimate_id.length > 0,
|
|
staleTime: 10 * 60_000, // 10 min — estimates expire at expires_at anyway
|
|
});
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/trade-in/estimate/{id}/houses
|
|
* Houses в радиусе 500m от target адреса estimate (Stage 2c data).
|
|
*/
|
|
export function useEstimateHouses(estimate_id: string | null) {
|
|
return useQuery<HouseInfoForEstimate[]>({
|
|
queryKey: ["trade-in", "estimate", estimate_id, "houses"],
|
|
queryFn: () =>
|
|
apiFetch<HouseInfoForEstimate[]>(`${BASE}/estimate/${estimate_id}/houses`),
|
|
enabled: estimate_id !== null && estimate_id.length > 0,
|
|
staleTime: 10 * 60_000,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/trade-in/estimate/{id}/imv-benchmark
|
|
* Avito IMV benchmark для UI badge (Stage 3 IMV cache lookup).
|
|
*/
|
|
export function useEstimateImvBenchmark(estimate_id: string | null) {
|
|
return useQuery<IMVBenchmarkResponse>({
|
|
queryKey: ["trade-in", "estimate", estimate_id, "imv-benchmark"],
|
|
queryFn: () =>
|
|
apiFetch<IMVBenchmarkResponse>(`${BASE}/estimate/${estimate_id}/imv-benchmark`),
|
|
enabled: estimate_id !== null && estimate_id.length > 0,
|
|
staleTime: 10 * 60_000,
|
|
});
|
|
}
|