/** * 코스피200 종목 목록 페이지 * - /api/kospi200 폴링 (1분 캐시 기반) * - 정렬: 등락률 / 거래량 / 현재가 * - 필터: 전체 / 상승 / 하락 * - 종목명 검색 */ (function () { let allStocks = []; let currentSort = 'fluRt'; // fluRt | volume | curPrc let currentDir = 'all'; // all | up | down let sortDesc = true; // 기본 내림차순 const listEl = document.getElementById('k200List'); const countEl = document.getElementById('k200Count'); const searchEl = document.getElementById('k200Search'); const updatedEl = document.getElementById('lastUpdated'); // ── 포맷 유틸 ──────────────────────────────────────────────── function fmtNum(n) { if (n == null || n === 0) return '-'; return Math.abs(n).toLocaleString('ko-KR'); } function fmtRate(f) { if (f == null) return '-'; const sign = f > 0 ? '+' : ''; return sign + f.toFixed(2) + '%'; } function fmtDiff(n) { if (n == null || n === 0) return '0'; const sign = n > 0 ? '+' : ''; return sign + Math.abs(n).toLocaleString('ko-KR'); } function rateClass(f) { if (f > 0) return 'text-red-500'; if (f < 0) return 'text-blue-500'; return 'text-gray-500'; } function rateBg(f) { if (f > 0) return 'bg-red-50 text-red-500'; if (f < 0) return 'bg-blue-50 text-blue-500'; return 'bg-gray-100 text-gray-500'; } // ── 행 렌더 ────────────────────────────────────────────────── function makeRow(s, rank) { const cls = rateClass(s.fluRt); const bgCls = rateBg(s.fluRt); return ` ${rank}

${s.name}

${s.code}

${fmtNum(s.curPrc)}원
${fmtDiff(s.predPre)}
${fmtRate(s.fluRt)}
${fmtNum(s.volume)}
${fmtNum(s.open)}
${fmtNum(s.high)}
${fmtNum(s.low)}
`; } // ── 필터 + 정렬 + 렌더 ─────────────────────────────────────── function renderList() { const q = searchEl.value.trim(); let filtered = allStocks; // 상승/하락 필터 if (currentDir === 'up') filtered = filtered.filter(s => s.fluRt > 0); if (currentDir === 'down') filtered = filtered.filter(s => s.fluRt < 0); // 종목명 검색 if (q) filtered = filtered.filter(s => s.name.includes(q) || s.code.includes(q)); // 정렬 filtered = [...filtered].sort((a, b) => { const diff = b[currentSort] - a[currentSort]; return sortDesc ? diff : -diff; }); countEl.textContent = `${filtered.length}개 종목`; if (filtered.length === 0) { listEl.innerHTML = `
조건에 맞는 종목이 없습니다.
`; return; } listEl.innerHTML = filtered.map((s, i) => makeRow(s, i + 1)).join(''); } // ── 데이터 로드 ─────────────────────────────────────────────── async function loadData() { try { const resp = await fetch('/api/kospi200'); if (!resp.ok) throw new Error('조회 실패'); allStocks = await resp.json(); updatedEl.textContent = new Date().toTimeString().slice(0, 8) + ' 기준'; renderList(); } catch (e) { listEl.innerHTML = `
데이터를 불러오지 못했습니다.
`; console.error('코스피200 조회 실패:', e); } } // ── 헤더 정렬 화살표 갱신 ──────────────────────────────────── function updateColHeaders() { document.querySelectorAll('.col-sort').forEach(el => { const arrow = el.querySelector('.sort-arrow'); if (!arrow) return; if (el.dataset.col === currentSort) { arrow.textContent = sortDesc ? '▼' : '▲'; arrow.className = 'sort-arrow text-blue-400'; el.classList.add('text-blue-500'); el.classList.remove('text-gray-500'); } else { arrow.textContent = ''; arrow.className = 'sort-arrow'; el.classList.remove('text-blue-500'); el.classList.add('text-gray-500'); } }); } // ── 정렬 탭 이벤트 ─────────────────────────────────────────── document.querySelectorAll('.sort-tab').forEach(btn => { btn.addEventListener('click', () => { if (currentSort === btn.dataset.sort) { sortDesc = !sortDesc; } else { currentSort = btn.dataset.sort; sortDesc = true; } document.querySelectorAll('.sort-tab').forEach(b => { const active = b.dataset.sort === currentSort; b.className = active ? 'sort-tab px-3 py-1.5 bg-blue-500 text-white text-xs font-medium' : 'sort-tab px-3 py-1.5 bg-white text-gray-600 hover:bg-gray-50 border-l border-gray-200 text-xs font-medium'; }); updateColHeaders(); renderList(); }); }); // ── 헤더 컬럼 클릭 정렬 ────────────────────────────────────── document.querySelectorAll('.col-sort').forEach(el => { el.addEventListener('click', () => { const col = el.dataset.col; if (currentSort === col) { sortDesc = !sortDesc; } else { currentSort = col; sortDesc = true; } // 상단 정렬 탭 동기화 document.querySelectorAll('.sort-tab').forEach(b => { const active = b.dataset.sort === currentSort; b.className = active ? 'sort-tab px-3 py-1.5 bg-blue-500 text-white text-xs font-medium' : 'sort-tab px-3 py-1.5 bg-white text-gray-600 hover:bg-gray-50 border-l border-gray-200 text-xs font-medium'; }); updateColHeaders(); renderList(); }); }); // ── 방향 필터 탭 이벤트 ────────────────────────────────────── document.querySelectorAll('.dir-tab').forEach(btn => { btn.addEventListener('click', () => { currentDir = btn.dataset.dir; document.querySelectorAll('.dir-tab').forEach(b => { const active = b.dataset.dir === currentDir; b.className = active ? 'dir-tab px-3 py-1.5 bg-blue-500 text-white text-xs font-medium' : 'dir-tab px-3 py-1.5 bg-white text-gray-600 hover:bg-gray-50 border-l border-gray-200 text-xs font-medium'; }); renderList(); }); }); // ── 검색 이벤트 ────────────────────────────────────────────── searchEl.addEventListener('input', renderList); // ── 초기 로드 + 1분 자동 갱신 ──────────────────────────────── updateColHeaders(); loadData(); setInterval(loadData, 60 * 1000); })();