gendesign/tradein-mvp/frontend/src/lib/trade-in-api.ts
lekss361 ab57e5756c feat(tradein): Stage 4b — IMV benchmark badge + house info card on estimate result
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 (паралельный).
2026-05-23 16:20:42 +03:00

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,
});
}