330 lines
9.1 KiB
TypeScript
330 lines
9.1 KiB
TypeScript
"use client";
|
||
|
||
// "КОЭФФИЦИЕНТ ЛОКАЦИИ" right-side drawer for the /trade-in/v2 "МЕРА Оценка"
|
||
// design port. Faithful markup port of the design drawer (МЕРА Оценка.dc.html,
|
||
// lines 618-660): scrim + 452px right slider that slides in via translateX, a
|
||
// big location coefficient, intro, base×coef formula, the factors that raise /
|
||
// lower price, and a geo-source footer. Static markup, data from fixtures.
|
||
// Open/close is driven entirely by props (no internal state, no fetch).
|
||
|
||
import { tokens } from "./tokens";
|
||
import type { LocationFactor } from "./types";
|
||
import { locationFactors } from "./fixtures";
|
||
|
||
interface LocationDrawerProps {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
}
|
||
|
||
function FactorRow({
|
||
factor,
|
||
variant,
|
||
}: {
|
||
factor: LocationFactor;
|
||
variant: "pos" | "neg";
|
||
}) {
|
||
const isPos = variant === "pos";
|
||
return (
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "space-between",
|
||
fontSize: 12,
|
||
color: tokens.body,
|
||
}}
|
||
>
|
||
<span style={{ display: "flex", alignItems: "center", gap: 9 }}>
|
||
<span
|
||
style={{
|
||
width: 16,
|
||
height: 16,
|
||
borderRadius: 4,
|
||
background: isPos
|
||
? "rgba(27,170,107,.12)"
|
||
: "rgba(111,129,149,.14)",
|
||
color: isPos ? tokens.success : tokens.muted,
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
fontSize: isPos ? 11 : 12,
|
||
}}
|
||
>
|
||
{isPos ? "+" : "−"}
|
||
</span>
|
||
{factor.label}
|
||
</span>
|
||
<span
|
||
style={{
|
||
fontFamily: tokens.font.mono,
|
||
fontSize: 11,
|
||
color: isPos ? tokens.success : tokens.muted,
|
||
}}
|
||
>
|
||
{factor.delta}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function LocationDrawer({ open, onClose }: LocationDrawerProps) {
|
||
return (
|
||
<>
|
||
<style>{`
|
||
.ld-close {
|
||
width: 32px;
|
||
height: 32px;
|
||
flex: 0 0 auto;
|
||
border: 1px solid ${tokens.line};
|
||
border-radius: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
color: ${tokens.muted};
|
||
font-size: 16px;
|
||
background: ${tokens.surface.w60};
|
||
transition: all .15s;
|
||
}
|
||
.ld-close:hover {
|
||
border-color: ${tokens.accent};
|
||
color: ${tokens.accent};
|
||
}
|
||
`}</style>
|
||
|
||
{/* scrim */}
|
||
<div
|
||
onClick={onClose}
|
||
style={{
|
||
position: "absolute",
|
||
inset: 0,
|
||
background: "rgba(22,42,70,.32)",
|
||
backdropFilter: "blur(2px)",
|
||
opacity: open ? 1 : 0,
|
||
pointerEvents: open ? "auto" : "none",
|
||
transition: "opacity .3s",
|
||
zIndex: 40,
|
||
}}
|
||
/>
|
||
|
||
{/* drawer */}
|
||
<div
|
||
style={{
|
||
position: "absolute",
|
||
top: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
width: 452,
|
||
transform: open ? "translateX(0)" : "translateX(108%)",
|
||
transition: "transform .38s cubic-bezier(.22,.61,.36,1)",
|
||
zIndex: 41,
|
||
background:
|
||
"linear-gradient(180deg,rgba(245,249,253,.97),rgba(235,242,250,.97))",
|
||
backdropFilter: "blur(10px)",
|
||
borderLeft: `1px solid ${tokens.line3}`,
|
||
boxShadow: "-18px 0 50px rgba(40,80,130,.16)",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
padding: "30px 32px",
|
||
overflowY: "auto",
|
||
fontFamily: tokens.font.sans,
|
||
}}
|
||
>
|
||
{/* left accent rail */}
|
||
<div
|
||
style={{
|
||
position: "absolute",
|
||
left: 0,
|
||
top: 18,
|
||
bottom: 18,
|
||
width: 1,
|
||
background: `linear-gradient(180deg,transparent,${tokens.accent},transparent)`,
|
||
opacity: 0.5,
|
||
}}
|
||
/>
|
||
{/* corner brackets */}
|
||
<div
|
||
style={{
|
||
position: "absolute",
|
||
left: 14,
|
||
top: 14,
|
||
width: 16,
|
||
height: 16,
|
||
borderLeft: `1.5px solid ${tokens.accent}`,
|
||
borderTop: `1.5px solid ${tokens.accent}`,
|
||
}}
|
||
/>
|
||
<div
|
||
style={{
|
||
position: "absolute",
|
||
right: 14,
|
||
bottom: 14,
|
||
width: 16,
|
||
height: 16,
|
||
borderRight: `1.5px solid ${tokens.accent}`,
|
||
borderBottom: `1.5px solid ${tokens.accent}`,
|
||
}}
|
||
/>
|
||
|
||
{/* header */}
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "flex-start",
|
||
justifyContent: "space-between",
|
||
}}
|
||
>
|
||
<div>
|
||
<div
|
||
style={{
|
||
fontSize: 9,
|
||
letterSpacing: "3px",
|
||
color: tokens.muted2,
|
||
}}
|
||
>
|
||
ПОЯСНЕНИЕ К РАСЧЁТУ
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontSize: 22,
|
||
fontWeight: 300,
|
||
letterSpacing: "1.5px",
|
||
color: tokens.ink2,
|
||
marginTop: 8,
|
||
}}
|
||
>
|
||
КОЭФФИЦИЕНТ ЛОКАЦИИ
|
||
</div>
|
||
</div>
|
||
<div className="ld-close" onClick={onClose}>
|
||
✕
|
||
</div>
|
||
</div>
|
||
|
||
{/* big coefficient */}
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "baseline",
|
||
gap: 14,
|
||
marginTop: 24,
|
||
paddingBottom: 20,
|
||
borderBottom: `1px solid ${tokens.lineSoft}`,
|
||
}}
|
||
>
|
||
<span
|
||
style={{
|
||
fontFamily: tokens.font.mono,
|
||
fontSize: 52,
|
||
fontWeight: 300,
|
||
color: tokens.accent,
|
||
lineHeight: 1,
|
||
}}
|
||
>
|
||
{locationFactors.coef}
|
||
</span>
|
||
<div style={{ fontSize: 11, color: tokens.muted, lineHeight: 1.5 }}>
|
||
множитель к базовой
|
||
<br />
|
||
цене по городу
|
||
</div>
|
||
</div>
|
||
|
||
{/* intro */}
|
||
<div
|
||
style={{
|
||
fontSize: 12.5,
|
||
lineHeight: 1.7,
|
||
color: tokens.body2,
|
||
marginTop: 20,
|
||
}}
|
||
>
|
||
Показывает, как адрес корректирует стоимость относительно медианы по
|
||
Екатеринбургу. <b style={{ color: tokens.ink2 }}>1.00</b> — средний
|
||
уровень. <b style={{ color: tokens.accent }}>0.87</b> означает, что
|
||
локация снижает цену примерно на{" "}
|
||
<b style={{ color: tokens.ink2 }}>13%</b> из-за баланса факторов ниже.
|
||
</div>
|
||
|
||
{/* formula */}
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 10,
|
||
marginTop: 18,
|
||
background: tokens.surface.w55,
|
||
border: `1px solid ${tokens.lineSoft}`,
|
||
borderRadius: 7,
|
||
padding: "12px 14px",
|
||
fontFamily: tokens.font.mono,
|
||
fontSize: 12,
|
||
color: tokens.ink2,
|
||
}}
|
||
>
|
||
<span style={{ color: tokens.muted }}>
|
||
{locationFactors.formula.base}
|
||
</span>
|
||
<span style={{ color: tokens.muted2 }}>×</span>
|
||
<span style={{ color: tokens.accent }}>
|
||
{locationFactors.formula.coef}
|
||
</span>
|
||
<span style={{ color: tokens.muted2 }}>=</span>
|
||
<span style={{ fontWeight: 500 }}>
|
||
{locationFactors.formula.result}
|
||
</span>
|
||
</div>
|
||
|
||
{/* positives */}
|
||
<div
|
||
style={{
|
||
fontSize: 9,
|
||
letterSpacing: "2px",
|
||
color: tokens.muted2,
|
||
marginTop: 26,
|
||
marginBottom: 12,
|
||
}}
|
||
>
|
||
ЧТО ПОВЫШАЕТ ЦЕНУ
|
||
</div>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 9 }}>
|
||
{locationFactors.positives.map((f, i) => (
|
||
<FactorRow key={i} factor={f} variant="pos" />
|
||
))}
|
||
</div>
|
||
|
||
{/* negatives */}
|
||
<div
|
||
style={{
|
||
fontSize: 9,
|
||
letterSpacing: "2px",
|
||
color: tokens.muted2,
|
||
marginTop: 24,
|
||
marginBottom: 12,
|
||
}}
|
||
>
|
||
ЧТО СНИЖАЕТ ЦЕНУ
|
||
</div>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 9 }}>
|
||
{locationFactors.negatives.map((f, i) => (
|
||
<FactorRow key={i} factor={f} variant="neg" />
|
||
))}
|
||
</div>
|
||
|
||
{/* footer source */}
|
||
<div
|
||
style={{
|
||
marginTop: 24,
|
||
paddingTop: 16,
|
||
borderTop: `1px solid ${tokens.lineSoft}`,
|
||
fontSize: 10.5,
|
||
lineHeight: 1.6,
|
||
color: tokens.hint,
|
||
}}
|
||
>
|
||
{locationFactors.footer}
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|