Port the approved ptica-v2 prototype to a new non-destructive route
/site-finder/analysis/{cad}/v2 — dark/light cockpit shell, real Leaflet
map (reuses PticaMapInner), 6 scan cards, lower/bottom grids, 11 drawers
and grounded chat. All values flow through the existing ptica-adapt layer
(real /analyze data or honest «—»). One beta link added on the analysis page.
116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* V2ScanCard — generic «scan» card for row 2 (prototype `.row2 .card`). Renders a
|
||
* PticaScanCard view-model: a title (+ optional badge), kv-rows OR status-rows
|
||
* (dot + state, used by Инженерия), an empty-state, and a "ПОДРОБНЕЕ →" link to
|
||
* the card's drawer. All values come from the ptica-adapt layer (REAL or honest
|
||
* «—»).
|
||
*/
|
||
|
||
import styles from "@/app/site-finder/analysis/[cad]/v2/v2.module.css";
|
||
import type {
|
||
PticaScanCard,
|
||
PticaScanRow,
|
||
PticaStatusTone,
|
||
} from "@/components/site-finder/ptica/ptica-adapt";
|
||
import type { V2DrawerKey } from "@/components/site-finder/ptica-v2/v2-drawer-registry";
|
||
|
||
const STATUS_COLOR: Record<PticaStatusTone, string> = {
|
||
ok: "var(--accent-green)",
|
||
warn: "var(--accent-orange)",
|
||
bad: "var(--accent-red)",
|
||
};
|
||
|
||
const STATUS_LABEL: Record<PticaStatusTone, string> = {
|
||
ok: "Доступно",
|
||
warn: "Огранич.",
|
||
bad: "Нет",
|
||
};
|
||
|
||
const STATUS_CLASS: Record<PticaStatusTone, string> = {
|
||
ok: styles.ok,
|
||
warn: styles.warnc,
|
||
bad: styles.warnc,
|
||
};
|
||
|
||
function KvRow({ row }: { row: PticaScanRow }) {
|
||
return (
|
||
<div className={styles.kv}>
|
||
<span className={styles.k}>{row.key}</span>
|
||
<span
|
||
className={`${styles.v} ${styles.mono} ${row.field.isReal ? "" : styles.dash}`}
|
||
title={row.field.isReal ? undefined : row.field.caption}
|
||
>
|
||
{row.field.value}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function StatusRow({ row }: { row: PticaScanRow }) {
|
||
const tone = row.status ?? "ok";
|
||
return (
|
||
<div className={styles.engRow}>
|
||
<span className={styles.nm}>
|
||
<span
|
||
className={styles.sd}
|
||
style={{ color: STATUS_COLOR[tone] }}
|
||
aria-hidden="true"
|
||
/>
|
||
{row.key}
|
||
</span>
|
||
<span className={styles.st}>
|
||
<span className={`${styles.stt} ${STATUS_CLASS[tone]}`}>
|
||
{STATUS_LABEL[tone]}
|
||
</span>
|
||
<span className={styles.dm} title={row.field.isReal ? undefined : row.field.caption}>
|
||
{row.field.value}
|
||
</span>
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
interface Props {
|
||
card: PticaScanCard;
|
||
drawer: V2DrawerKey;
|
||
onOpenDrawer: (key: V2DrawerKey) => void;
|
||
}
|
||
|
||
export function V2ScanCard({ card, drawer, onOpenDrawer }: Props) {
|
||
return (
|
||
<div className={styles.card}>
|
||
<div className={styles.cardH}>
|
||
<div className={styles.ttl}>
|
||
<span className={styles.sectionTitle}>{card.title}</span>
|
||
{card.badge && <span className={styles.badge}>{card.badge}</span>}
|
||
</div>
|
||
</div>
|
||
<div className={styles.cardB}>
|
||
{card.rows.length === 0 ? (
|
||
<div className={styles.emptyState}>
|
||
{card.emptyState ?? "Нет данных"}
|
||
</div>
|
||
) : (
|
||
card.rows.map((row) =>
|
||
row.status ? (
|
||
<StatusRow key={row.key} row={row} />
|
||
) : (
|
||
<KvRow key={row.key} row={row} />
|
||
),
|
||
)
|
||
)}
|
||
<div className={styles.moreRow}>
|
||
<button
|
||
type="button"
|
||
className={styles.more}
|
||
onClick={() => onOpenDrawer(drawer)}
|
||
>
|
||
ПОДРОБНЕЕ →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|