// ferro-app.jsx — Ferrolend КП detail page with click-to-expand manager calc const { useState, useMemo, useRef, useEffect } = React; /* ============================ small bits ============================ */ function DownloadIcon() { return ( ); } function Pill({ children }) { return {children}; } function Chevron({ open }) { return ( ); } function Warn({ size = 15 }) { return ( ); } // Подсказка "откуда взято значение" — заменяет нативный title. source бывает: // null/undefined (сработал дефолт), объект {before, match, after} (дословная // цитата с контекстом — see extractor._ctx_source) или обычная строка // (описание вроде "рассчитано по проёму ..." — не цитата, просто пояснение). function SourceTip({ source, defaultText, children }) { const [pos, setPos] = useState(null); // [БАГ] Раньше позиция считалась через ref.getBoundingClientRect() — но обёртка // .stip-anchor это display:contents (нужно, чтобы не ломать вёрстку ячейки // таблицы), а у display:contents элементов нет собственного box'а, поэтому // getBoundingClientRect() возвращал пустой прямоугольник (0,0) — подсказка // улетала в левый верхний угол экрана. Позиционируем прямо от курсора // (координаты события) — заодно это и есть то, что просили: рядом с курсором. const place = (e) => { const x = Math.max(6, Math.min(e.clientX + 12, window.innerWidth - 340)); const y = Math.min(e.clientY + 16, window.innerHeight - 20); setPos({ x, y }); }; const hide = () => setPos(null); let body; if (!source) { body = {defaultText || "По умолчанию — в тексте заявки не указано"}; } else if (typeof source === "string") { body = source.startsWith("рассчитано") ? <>Рассчитано: {source}> : <>Из текста заявки: {source}>; } else { body = ( <> Из текста заявки: