/**
* 코스피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}
${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);
})();