gendesign/tradein-mvp/frontend/src/components/trade-in/v2/SectionOverlay.tsx
bot-backend 81ae21ada9
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
feat(tradein/v2): CacheView history + PDF link + quota + address autocomplete (#2042)
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.
2026-06-28 16:14:34 +03:00

165 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
// Overlay wrapper for the nav-driven section panels of the /trade-in/v2
// "МЕРА Оценка" design. Faithful port of the design overlay container
// (МЕРА Оценка.dc.html lines 443-453 + 615-616): a glass/blur absolute layer
// with HUD corner brackets, a header (section number + title + "← К ОЦЕНКЕ"
// button) and a scrollable body that swaps the active view.
import { tokens } from "./tokens";
import { overlayNums, overlayTitles } from "./fixtures";
import HistoryView from "./HistoryView";
import SourcesView from "./SourcesView";
import AnalyticsView from "./AnalyticsView";
import { CacheView } from "./CacheView";
import type { CacheData, HistoryData, SourcesData } from "./mappers";
import type { Analytics } from "./types";
interface SectionOverlayProps {
active: number;
onClose: () => void;
onNavigate: (i: number) => void;
// Mapped overlay data, fed by page.tsx (mapHistory / mapSources / mapAnalytics
// / mapCache). Optional so unwired/storybook usage falls back to each view's
// fixture default.
history?: HistoryData;
sources?: SourcesData;
analytics?: Analytics;
cache?: CacheData;
}
export default function SectionOverlay({
active,
onClose,
onNavigate,
history,
sources,
analytics,
cache,
}: SectionOverlayProps) {
return (
<div
style={{
position: "absolute",
left: 28,
right: 28,
top: 80,
bottom: 30,
zIndex: 30,
display: "flex",
flexDirection: "column",
background:
"linear-gradient(180deg,rgba(244,248,252,.985),rgba(235,242,250,.985))",
backdropFilter: "blur(12px)",
border: `1px solid ${tokens.line3}`,
borderRadius: 8,
boxShadow: "0 22px 60px rgba(40,80,130,.18)",
overflow: "hidden",
fontFamily: tokens.font.sans,
}}
>
<style>{`
.tiv2-so-back { transition: all .15s; }
.tiv2-so-back:hover { border-color: ${tokens.accent}; color: ${tokens.accent}; }
`}</style>
{/* HUD corner brackets */}
<div
style={{
position: "absolute",
left: 12,
top: 12,
width: 15,
height: 15,
borderLeft: `1.5px solid ${tokens.accent}`,
borderTop: `1.5px solid ${tokens.accent}`,
pointerEvents: "none",
}}
/>
<div
style={{
position: "absolute",
right: 12,
bottom: 12,
width: 15,
height: 15,
borderRight: `1.5px solid ${tokens.accent}`,
borderBottom: `1.5px solid ${tokens.accent}`,
pointerEvents: "none",
}}
/>
{/* overlay header */}
<div
style={{
flex: "0 0 auto",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "16px 24px",
borderBottom: "1px solid #cdd9e6",
}}
>
<div style={{ display: "flex", alignItems: "baseline", gap: 11 }}>
<span
style={{
fontFamily: tokens.font.mono,
fontSize: 15,
color: tokens.accent,
}}
>
{overlayNums[active]}
</span>
<span
style={{
fontSize: 14,
fontWeight: 600,
letterSpacing: 2.5,
color: tokens.ink2,
}}
>
{overlayTitles[active]}
</span>
</div>
<div
onClick={onClose}
className="tiv2-so-back"
style={{
display: "flex",
alignItems: "center",
gap: 8,
cursor: "pointer",
border: `1px solid ${tokens.line}`,
borderRadius: 6,
padding: "6px 12px",
background: tokens.surface.w60,
}}
>
<span style={{ fontSize: 13 }}></span>
<span
style={{ fontSize: 10, letterSpacing: 1.5, color: tokens.muted }}
>
К ОЦЕНКЕ
</span>
</div>
</div>
{/* overlay body */}
<div
style={{
flex: 1,
minHeight: 0,
overflowY: "auto",
padding: "22px 24px 26px",
}}
>
{active === 1 && <HistoryView data={history} />}
{active === 2 && <SourcesView data={sources} />}
{active === 3 && (
<AnalyticsView data={analytics} onNavigate={onNavigate} />
)}
{active === 4 && <CacheView data={cache} />}
</div>
</div>
);
}