// Shell.jsx — IBK 리스크 인사이트 chrome: brand header + tab bar. function IBKBrand() { return (
IBK
리스크 인사이트Risk Insight
IBK 기업은행 · 유가증권 리스크 모니터링
); } // ── 전체 검색 오버레이 ───────────────────────────────────────────────────────── function SearchOverlay({ onSelect, onClose }) { const [q, setQ] = React.useState(''); const inputRef = React.useRef(null); React.useEffect(() => { inputRef.current && inputRef.current.focus(); }, []); // Esc 닫기 React.useEffect(() => { const h = (e) => e.key === 'Escape' && onClose(); window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, [onClose]); const results = React.useMemo(() => { const s = q.trim().toLowerCase(); if (!s) return { stocks: [], bonds: [] }; const stocks = [ ...(typeof STOCKS !== 'undefined' ? STOCKS : []), ...(typeof STOCK_RECO !== 'undefined' ? STOCK_RECO.map(r => ({ ticker: r.ticker, name: r.name, sector: r.sector, status: (typeof STOCK_RISK_MAP !== 'undefined' && STOCK_RISK_MAP[r.ticker]?.status) || '적정', _reco: r, })) : []), ].filter(s2 => !q || s2.name.toLowerCase().includes(s) || s2.ticker.toLowerCase().includes(s)) .slice(0, 8); const bonds = (typeof BONDS !== 'undefined' ? BONDS : []) .filter(b => b.name.toLowerCase().includes(s) || b.isin.toLowerCase().includes(s) || (b.ledger && b.ledger.toLowerCase().includes(s))) .slice(0, 8); return { stocks, bonds }; }, [q]); const total = results.stocks.length + results.bonds.length; const selectStock = (s) => { const item = s._reco ? recoToItem(s._reco) : { asset: '주식', isin: s.ticker, name: s.name, ccy: 'KRW', type: s.sector, status: s.status, cvar: s.risk ? +(s.risk / 100).toFixed(4) : 0.05, dod: s.dod ? +(s.dod / 100).toFixed(4) : 0, pending: false, stock: s, }; onSelect(item); onClose(); }; const selectBond = (b) => { onSelect({ asset: '채권', isin: b.isin, ledger: b.ledger, name: b.name, ccy: b.ccy, type: b.cat, cvar: b.cvar, dod: b.dod, status: b.status, pending: b.pending, bond: b }); onClose(); }; const Row = ({ icon, label, sub, badge, onClick }) => (
e.currentTarget.style.background=T.c100} onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
{label}
{sub}
{badge && {badge}}
); return (
{/* 배경 딤 */}
{/* 패널 */}
{/* 검색 입력 */}
setQ(e.target.value)} placeholder="종목명·티커·ISIN 검색..." style={{ flex:1, border:0, outline:'none', fontFamily:T.font, fontWeight:500, fontSize:14.5, color:T.c900, background:'transparent' }} /> {q && } ESC
{/* 결과 */}
{!q && (
종목명, 티커, ISIN으로 검색하세요
)} {q && total === 0 && (
"{q}" 검색 결과가 없어요
)} {results.stocks.length > 0 && <>
주식 ({results.stocks.length})
{results.stocks.map(s => selectStock(s)} />)} } {results.bonds.length > 0 && <>
채권 ({results.bonds.length})
{results.bonds.map(b => selectBond(b)} />)} }
); } function Header({ onSearch }) { const [open, setOpen] = React.useState(false); // ⌘K / Ctrl+K 단축키 React.useEffect(() => { const h = (e) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setOpen(true); } }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, []); return ( <> {open && setOpen(false)} />}
setOpen(true)} style={{ display: 'flex', alignItems: 'center', gap: 8, background: T.c100, border: '1px solid ' + T.line, borderRadius: 9, padding: '7px 12px', width: 248, cursor: 'pointer' }}> 종목명·티커·ISIN 검색 ⌘K
실시간
김진호
리스크총괄부
); } function TabBar({ tabs, active, onSelect, onClose }) { return (
{tabs.map((t) => {const on = t.id === active;const closeable = t.kind === 'detail'; return ( ); })}
); } function Shell({ tabs, active, onSelect, onClose, onSearch, children }) { return (
{children}
); } Object.assign(window, { Shell });