- 프론트: 토스트 알림 시스템(lib/toast + ToastHost) 추가, 매도/매수 성공·실패를 화면에 표시 (기존엔 console.log만, 실패는 블로킹 alert). 성공 토스트에 손익 표시. - 매도 직후 예수금/평가액 요약 즉시 갱신(apply_sell), 최근 5분 내 수동 매도 종목은 KIS 잔고 반영 지연으로 동기화가 되살리지 않도록 스킵(recent_sells). - 예수금이 D+2 결제 기준(dnca_tot_amt)이라 당일 매도대금이 화면·데이트레이딩 가용현금에 반영되지 않던 문제: 매수가능조회(inquire-psbl-order) ord_psbl_cash 를 우선 사용. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
52 lines
2.2 KiB
TypeScript
52 lines
2.2 KiB
TypeScript
/**
|
|
* ToastHost.tsx — 우상단 토스트 스택. Layout에 1회 마운트.
|
|
*/
|
|
import React, { useEffect, useState } from 'react'
|
|
import { CheckCircle2, XCircle, Info, X } from 'lucide-react'
|
|
import { TOAST_EVENT, ToastMessage } from '../lib/toast'
|
|
|
|
const STYLE: Record<ToastMessage['kind'], { border: string; icon: React.ReactNode }> = {
|
|
success: { border: 'border-[#22c55e]', icon: <CheckCircle2 className="w-4 h-4 text-[#22c55e]" /> },
|
|
error: { border: 'border-[#ef4444]', icon: <XCircle className="w-4 h-4 text-[#ef4444]" /> },
|
|
info: { border: 'border-[#2b65d9]', icon: <Info className="w-4 h-4 text-[#2b65d9]" /> },
|
|
}
|
|
|
|
const ToastHost: React.FC = () => {
|
|
const [items, setItems] = useState<ToastMessage[]>([])
|
|
|
|
useEffect(() => {
|
|
const onToast = (e: Event) => {
|
|
const msg = (e as CustomEvent<ToastMessage>).detail
|
|
setItems((prev) => [...prev.slice(-4), msg])
|
|
window.setTimeout(() => setItems((prev) => prev.filter((m) => m.id !== msg.id)), msg.ttl)
|
|
}
|
|
window.addEventListener(TOAST_EVENT, onToast)
|
|
return () => window.removeEventListener(TOAST_EVENT, onToast)
|
|
}, [])
|
|
|
|
if (items.length === 0) return null
|
|
return (
|
|
<div className="fixed top-20 right-4 z-[100] flex flex-col gap-2 w-[320px] max-w-[calc(100vw-2rem)]">
|
|
{items.map((m) => (
|
|
<div
|
|
key={m.id}
|
|
role="status"
|
|
className={`flex items-start gap-2 bg-[#0c1522] border-l-4 ${STYLE[m.kind].border} border border-[#152035] rounded shadow-lg px-3 py-2 text-xs text-[#c9d7e5]`}
|
|
>
|
|
<span className="mt-px">{STYLE[m.kind].icon}</span>
|
|
<span className="flex-1 whitespace-pre-line">{m.text}</span>
|
|
<button
|
|
onClick={() => setItems((prev) => prev.filter((x) => x.id !== m.id))}
|
|
className="text-[#4a6580] hover:text-[#8faabf]"
|
|
aria-label="close"
|
|
>
|
|
<X className="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ToastHost
|