feat(ui): 관심종목(localStorage) + DART 공시 패널
- web/lib/watchlist.ts: KEY=stockchart.watchlist, storage+custom event 구독 - StarButton: 종목 페이지 헤더에서 ☆/★ 토글 - /watchlist 페이지: 카드 그리드 + 현재가/전일대비 + ✕ 제거 - DisclosuresPanel: 기존 /api/news?source=dart 재사용 (백엔드 신설 없음) - 종목 페이지 하단에 공시 패널 부착, 홈에 ★ 관심종목 링크 추가 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
64
web/lib/watchlist.ts
Normal file
64
web/lib/watchlist.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
// 관심종목 — localStorage 기반. 로그인 없이 토스의 '내 종목' 느낌.
|
||||
// 단순 string[] 직렬화. 다중 탭 동기화는 'storage' 이벤트로 처리.
|
||||
|
||||
const KEY = "stockchart.watchlist";
|
||||
|
||||
function read(): string[] {
|
||||
if (typeof window === "undefined") return [];
|
||||
try {
|
||||
const raw = window.localStorage.getItem(KEY);
|
||||
if (!raw) return [];
|
||||
const v = JSON.parse(raw);
|
||||
return Array.isArray(v) ? v.filter((s) => typeof s === "string") : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function write(codes: string[]) {
|
||||
if (typeof window === "undefined") return;
|
||||
window.localStorage.setItem(KEY, JSON.stringify(codes));
|
||||
// 같은 탭은 storage 이벤트가 안 떠서 수동 dispatch.
|
||||
window.dispatchEvent(new CustomEvent("watchlist:change"));
|
||||
}
|
||||
|
||||
export const watchlist = {
|
||||
get(): string[] {
|
||||
return read();
|
||||
},
|
||||
has(code: string): boolean {
|
||||
return read().includes(code);
|
||||
},
|
||||
add(code: string): void {
|
||||
const cur = read();
|
||||
if (cur.includes(code)) return;
|
||||
write([code, ...cur]);
|
||||
},
|
||||
remove(code: string): void {
|
||||
const cur = read();
|
||||
write(cur.filter((c) => c !== code));
|
||||
},
|
||||
toggle(code: string): boolean {
|
||||
const cur = read();
|
||||
if (cur.includes(code)) {
|
||||
write(cur.filter((c) => c !== code));
|
||||
return false;
|
||||
}
|
||||
write([code, ...cur]);
|
||||
return true;
|
||||
},
|
||||
// 컴포넌트가 변경 구독. unsubscribe 함수 반환.
|
||||
subscribe(cb: () => void): () => void {
|
||||
if (typeof window === "undefined") return () => {};
|
||||
const onStorage = (e: StorageEvent) => {
|
||||
if (e.key === KEY) cb();
|
||||
};
|
||||
const onCustom = () => cb();
|
||||
window.addEventListener("storage", onStorage);
|
||||
window.addEventListener("watchlist:change", onCustom as EventListener);
|
||||
return () => {
|
||||
window.removeEventListener("storage", onStorage);
|
||||
window.removeEventListener("watchlist:change", onCustom as EventListener);
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user