"use client"; import { useMutation, useQuery } from "@tanstack/react-query"; import { apiFetch } from "./api"; import type { AggregatedEstimate, CianPriceChangeStats, HouseAnalyticsResponse, HouseInfoForEstimate, IMVBenchmarkResponse, PlacementHistoryItem, SalesVsListingsResponse, SellTimeSensitivityResponse, StreetDealsResponse, 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({ mutationFn: (input) => apiFetch(`${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({ queryKey: ["trade-in", "estimate", estimate_id], queryFn: () => apiFetch(`${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({ queryKey: ["trade-in", "estimate", estimate_id, "houses"], queryFn: () => apiFetch(`${BASE}/estimate/${estimate_id}/houses`), enabled: estimate_id !== null && estimate_id.length > 0, staleTime: 10 * 60_000, }); } /** * GET /api/v1/trade-in/estimate/{id}/placement-history * Historical listings from house_placement_history for the target house. */ export function useEstimatePlacementHistory(estimate_id: string | null) { return useQuery({ queryKey: ["trade-in", "estimate", estimate_id, "placement-history"], queryFn: () => apiFetch( `${BASE}/estimate/${estimate_id}/placement-history`, ), 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({ queryKey: ["trade-in", "estimate", estimate_id, "imv-benchmark"], queryFn: () => apiFetch(`${BASE}/estimate/${estimate_id}/imv-benchmark`), enabled: estimate_id !== null && estimate_id.length > 0, staleTime: 10 * 60_000, }); } /** * GET /api/v1/trade-in/estimate/{id}/cian-price-changes * Price change history for Cian analog listings (only those with >0 changes). */ export function useEstimateCianPriceChanges(estimate_id: string | null) { return useQuery({ queryKey: ["trade-in", "estimate", estimate_id, "cian-price-changes"], queryFn: () => apiFetch( `${BASE}/estimate/${estimate_id}/cian-price-changes`, ), enabled: estimate_id !== null && estimate_id.length > 0, staleTime: 10 * 60_000, }); } /** * GET /api/v1/trade-in/estimate/{id}/house-analytics * Price history, KPI and recent sold lots for the target house. */ export function useEstimateHouseAnalytics(estimate_id: string | null) { return useQuery({ queryKey: ["trade-in", "estimate", estimate_id, "house-analytics"], queryFn: () => apiFetch( `${BASE}/estimate/${estimate_id}/house-analytics`, ), enabled: estimate_id !== null && estimate_id.length > 0, staleTime: 10 * 60_000, }); } /** * GET /api/v1/trade-in/street-deals * ДКП-сделки Росреестра по улице target адреса. Per-street (open dataset * без номера дома). Empty response если street не извлёкся. */ export function useStreetDeals( address: string | null, area_m2: number | null, rooms: number | null, ) { const params = new URLSearchParams(); if (address) params.set("address", address); if (area_m2 !== null) params.set("area_m2", String(area_m2)); if (rooms !== null) params.set("rooms", String(rooms)); return useQuery({ queryKey: ["trade-in", "street-deals", address, area_m2, rooms], queryFn: () => apiFetch(`${BASE}/street-deals?${params}`), enabled: !!address && area_m2 !== null && rooms !== null, staleTime: 5 * 60_000, }); } /** * GET /api/v1/trade-in/sales-vs-listings * ДКП-сделки + paired listings по улице (PR L / #564 Phase 2). Возвращает все * пары LEFT JOIN — сделки без listing match сохраняются (listing_* = null), * чтобы вычислить linkage_rate. Используется в StreetDealsCard для показа * исторических ASK рядом с фактической ценой сделки. * * staleTime: 10 минут — данные обновляются раз в сутки (rosreestr import + * scrapers), нет смысла часто рефетчить. */ export function useSalesVsListings( address: string | null, area_m2: number | null, rooms: number | null, ) { const params = new URLSearchParams(); if (address) params.set("address", address); if (area_m2 !== null) params.set("area_m2", String(area_m2)); if (rooms !== null) params.set("rooms", String(rooms)); return useQuery({ queryKey: ["trade-in", "sales-vs-listings", address, area_m2, rooms], queryFn: () => apiFetch(`${BASE}/sales-vs-listings?${params}`), enabled: !!address && area_m2 !== null && rooms !== null, staleTime: 10 * 60_000, }); } /** * GET /api/v1/trade-in/estimate/{id}/sell-time-sensitivity * Median exposure days bucketed by price premium (-5%, 0, +5%, +10%). */ export function useEstimateSellTimeSensitivity(estimate_id: string | null) { return useQuery({ queryKey: ["trade-in", "estimate", estimate_id, "sell-time-sensitivity"], queryFn: () => apiFetch( `${BASE}/estimate/${estimate_id}/sell-time-sensitivity`, ), enabled: estimate_id !== null && estimate_id.length > 0, staleTime: 10 * 60_000, }); }