From 81ae21ada965c27c7f1746ba2007b02ac054867e Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sun, 28 Jun 2026 16:14:34 +0300 Subject: [PATCH] feat(tradein/v2): CacheView history + PDF link + quota + address autocomplete (#2042) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FE-5, last FE wiring on the mappers pattern. Closes #2042. - New hooks (lib/trade-in-api.ts): useEstimateHistory (GET /history), useGeocodeSuggest (GET /geocode/suggest?q=&limit=, debounced, enabled>=3 chars); useQuota reused. - types/trade-in.ts: EstimateHistoryItem (area_m2 string, median_price), GeocodeSuggestion. - mappers.ts: mapCache(history) -> {rows: CacheRow[], kpis: CacheKpi[]} — per-row status by 24h TTL; KPIs FE-derived from /history (всего/средняя цена/повторные %), NOT the global /cache-stats. - CacheView: real previous-estimates list. ParamsPanel: address autocomplete dropdown → captures lat/lon into submit (cleared on manual edit). HeroBar: «Скачать PDF» → /estimate/ {id}/pdf, UUID-gated + safeUrl scheme check + disabled when no estimate. TopNav: «Мои отчёты» from quota.used/history. page.tsx: invalidate history+quota on new estimate. - Hydration-safe: PDF link gated behind a mounted flag (urlId is SSR-null/client-uuid). - FE-1..4 untouched. Verified: next build green (/v2 39.6 kB); tsx harness ran mapCache vs real /history (10 items: ВСЕГО 10 / СРЕДНЯЯ 8,85 млн / ПОВТОРНЫЕ 50%, statuses, null graceful). code-reviewer APPROVE after hydration + reports-fallback fixes. --- tradein-mvp/frontend/src/app/v2/page.tsx | 45 +++++- .../src/components/trade-in/v2/CacheView.tsx | 11 +- .../src/components/trade-in/v2/HeroBar.tsx | 105 ++++++++++---- .../components/trade-in/v2/ParamsPanel.tsx | 122 +++++++++++++++- .../components/trade-in/v2/SectionOverlay.tsx | 11 +- .../src/components/trade-in/v2/TopNav.tsx | 11 +- .../src/components/trade-in/v2/mappers.ts | 134 ++++++++++++++++++ tradein-mvp/frontend/src/lib/trade-in-api.ts | 50 ++++++- tradein-mvp/frontend/src/types/trade-in.ts | 31 ++++ 9 files changed, 473 insertions(+), 47 deletions(-) diff --git a/tradein-mvp/frontend/src/app/v2/page.tsx b/tradein-mvp/frontend/src/app/v2/page.tsx index 9004b698..b2fe36a3 100644 --- a/tradein-mvp/frontend/src/app/v2/page.tsx +++ b/tradein-mvp/frontend/src/app/v2/page.tsx @@ -10,9 +10,10 @@ // a pending or errored street-deals / analytics call degrades its section to a // fallback inside the mapper instead of blanking the page. -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import type { CSSProperties, ReactNode } from "react"; import { useRouter } from "next/navigation"; +import { useQueryClient } from "@tanstack/react-query"; import { tokens } from "@/components/trade-in/v2/tokens"; import TopNav from "@/components/trade-in/v2/TopNav"; @@ -25,11 +26,13 @@ import SectionOverlay from "@/components/trade-in/v2/SectionOverlay"; import { LocationDrawer } from "@/components/trade-in/v2/LocationDrawer"; import type { Analytics, ObjectInfo, Report } from "@/components/trade-in/v2/types"; import type { + CacheData, HistoryData, SourcesData, } from "@/components/trade-in/v2/mappers"; import { mapAnalytics, + mapCache, mapHistory, mapObject, mapReport, @@ -45,6 +48,7 @@ import type { import { HTTPError } from "@/lib/api"; import { useEstimate, + useEstimateHistory, useEstimateHouseAnalytics, useEstimateMutation, useEstimatePlacementHistory, @@ -52,6 +56,7 @@ import { useSalesVsListings, useStreetDeals, } from "@/lib/trade-in-api"; +import { useQuota } from "@/lib/useQuota"; // OUTER HUD FRAME + 4 corner brackets (design lines 31-37). Decorative, // non-interactive overlay drawn over the artboard gradient. The frame has @@ -372,6 +377,7 @@ function readUrlId(): string | null { export default function TradeInV2Page() { const router = useRouter(); + const queryClient = useQueryClient(); const [nav, setNav] = useState(0); const [drawerOpen, setDrawerOpen] = useState(false); @@ -383,6 +389,13 @@ export default function TradeInV2Page() { const urlId = readUrlId(); + // Post-mount flag: readUrlId() is null on SSR but a UUID on the client's first + // render, so any structure that depends on the id (e.g. the PDF vs disabled + // + {pdfBtnInner} + + ) : ( + + )}