gendesign/frontend/src/components/site-finder/ptica/InvestScoreBlock.tsx
Light1YT 88865bf6a7
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 57s
CI / openapi-codegen-check (pull_request) Successful in 1m52s
fix(ptica): drawer modal a11y (focus-trap + restore) + polish
PticaDrawer: proper modal focus management —
- focus-trap: Tab/Shift+Tab wrap inside the dialog (focusables re-queried per
  Tab so it survives lazy panels/sub-tabs), focus-already-outside pulled back in;
- focus-restore: the trigger element is captured on open and re-focused on close
  (guards null + detached node);
- onClose held in a ref so the effect depends on [open] only — fixes focus
  thrashing when the host's URL/searchParams change mid-open.
Esc/scrim close, body-scroll-lock, role=dialog/aria-modal preserved.

Cleanups (code-review follow-ups):
- MassingScene: drop duplicate initial mass build (floors/sections effect owns
  it); corrected the effect-ordering comment.
- Static inline styles → CSS-module classes (scan/gauge/score/spaced rows).
- DrawerPrimitives: remove !important on .dtabActive via specificity
  (.dtabs button.dtabActive).
- Remove unused SoonCard component + its CSS.

tsc/lint/prettier clean; next build green (route bundled). ptica-only, no any.
2026-06-20 20:13:26 +05:00

83 lines
2.7 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";
/**
* InvestScoreBlock — hero score panel (col 3): big /10 invest-score number + a
* buy-signal badge. In INCREMENT 1 the invest-score and buy-signal are
* PLACEHOLDERS (surfaced after the §22 forecast / Scenarios), so the big number
* renders muted "—" with a caption instead of a fabricated value.
*/
import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css";
import type { ParcelAnalysis } from "@/types/site-finder";
import type { ForecastReport } from "@/types/forecast";
import { adaptInvestScore } from "@/components/site-finder/ptica/ptica-adapt";
import type { DrawerKey } from "@/components/site-finder/ptica/drawers/drawer-keys";
interface Props {
analysis: ParcelAnalysis;
/** §22 forecast report — when ready, surfaces the REAL invest-score / buy-signal. */
forecastReport?: ForecastReport;
onOpenDrawer: (key: DrawerKey) => void;
}
export function InvestScoreBlock({
analysis,
forecastReport,
onOpenDrawer,
}: Props) {
const inv = adaptInvestScore(analysis, forecastReport);
return (
<div className={styles.scoreStack}>
<div>
<div className={styles.statLine}>
<span className={styles.k}>Инвест-скор</span>
</div>
<div className={styles.investScore}>
{inv.score.isReal ? (
<>
<span className={styles.investBig}>{inv.score.value}</span>
<span className={styles.investSmall}>/ 10</span>
</>
) : (
<span className={`${styles.investBig} ${styles.investPlaceholder}`}>
</span>
)}
</div>
{inv.score.caption && (
<span className={styles.fieldCaption}>{inv.score.caption}</span>
)}
</div>
<div className={styles.statLine}>
<span className={styles.k}>Потенциал</span>
<span className={styles.v} title={inv.potential.caption}>
{inv.potential.value}
</span>
</div>
<div className={styles.statLine}>
<span className={styles.k}>Риск</span>
<span className={styles.v} title={inv.risk.caption}>
{inv.risk.value}
</span>
</div>
<div className={styles.statLine}>
<span className={styles.k}>Buy Signal</span>
<span className={styles.badge} title={inv.buySignal.caption}>
{inv.buySignal.isReal ? inv.buySignal.value : "после прогноза"}
</span>
</div>
<button
type="button"
className={`${styles.detailBtn} ${styles.detailBtnScan}`}
onClick={() => onOpenDrawer("potential")}
title="Открыть расчёт ёмкости"
>
Подробнее
</button>
</div>
);
}