DockerSpace / frontend /src /components /InstitutionalFlowTable.tsx
DennisChan0909's picture
feat: add 三大法人 buy/sell/net table and dealer_net line to institutional chart
42fcd29
Raw
History Blame Contribute Delete
4.25 kB
import React, { useState } from 'react'
import { OHLCVPoint } from '../api/stockApi'
interface Props {
data: OHLCVPoint[]
rows?: number
}
function fmt(v: number | undefined): string {
if (v == null) return '—'
const abs = Math.abs(v)
const sign = v < 0 ? '-' : '+'
if (abs >= 1_000_000) return `${sign}${(abs / 1_000_000).toFixed(2)}M`
if (abs >= 1_000) return `${sign}${(abs / 1_000).toFixed(0)}K`
return `${sign}${Math.round(abs)}`
}
function cls(v: number | undefined): string {
if (v == null || v === 0) return 'text-slate-400'
return v > 0 ? 'text-green-400' : 'text-red-400'
}
export const InstitutionalFlowTable: React.FC<Props> = ({ data, rows = 20 }) => {
const [expanded, setExpanded] = useState(false)
const visible = expanded ? rows : 10
const filtered = data
.filter((d) => d.institutional_available)
.slice(-rows)
.reverse()
.slice(0, visible)
if (!filtered.length) return (
<p className="text-slate-500 text-xs px-2 py-3">尚無三大法人資料(僅台股 TWSE/TPEx)</p>
)
return (
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse min-w-[640px]">
<thead>
<tr className="text-slate-400 border-b border-slate-700">
<th className="text-left py-1.5 px-2 font-medium">日期</th>
<th className="text-right py-1.5 px-2 font-medium text-blue-400">外資買</th>
<th className="text-right py-1.5 px-2 font-medium text-blue-400">外資賣</th>
<th className="text-right py-1.5 px-2 font-medium text-blue-300">外資淨</th>
<th className="text-right py-1.5 px-2 font-medium text-yellow-400">投信買</th>
<th className="text-right py-1.5 px-2 font-medium text-yellow-400">投信賣</th>
<th className="text-right py-1.5 px-2 font-medium text-yellow-300">投信淨</th>
<th className="text-right py-1.5 px-2 font-medium text-pink-400">自營買</th>
<th className="text-right py-1.5 px-2 font-medium text-pink-400">自營賣</th>
<th className="text-right py-1.5 px-2 font-medium text-pink-300">自營淨</th>
<th className="text-right py-1.5 px-2 font-medium text-white">合計淨</th>
</tr>
</thead>
<tbody>
{filtered.map((d, i) => (
<tr key={d.date} className={i % 2 === 0 ? 'bg-slate-900/40' : ''}>
<td className="py-1 px-2 text-slate-300 whitespace-nowrap">
{d.date}
{d.institutional_carry_forward && (
<span className="ml-1 text-slate-600 text-[10px]"></span>
)}
</td>
<td className={`py-1 px-2 text-right ${cls(d.foreign_buy)}`}>{fmt(d.foreign_buy)}</td>
<td className={`py-1 px-2 text-right ${cls(-(d.foreign_sell ?? 0))}`}>{d.foreign_sell != null ? fmt(-d.foreign_sell) : '—'}</td>
<td className={`py-1 px-2 text-right font-medium ${cls(d.foreign_net)}`}>{fmt(d.foreign_net)}</td>
<td className={`py-1 px-2 text-right ${cls(d.trust_buy)}`}>{fmt(d.trust_buy)}</td>
<td className={`py-1 px-2 text-right ${cls(-(d.trust_sell ?? 0))}`}>{d.trust_sell != null ? fmt(-d.trust_sell) : '—'}</td>
<td className={`py-1 px-2 text-right font-medium ${cls(d.trust_net)}`}>{fmt(d.trust_net)}</td>
<td className={`py-1 px-2 text-right ${cls(d.dealer_buy)}`}>{fmt(d.dealer_buy)}</td>
<td className={`py-1 px-2 text-right ${cls(-(d.dealer_sell ?? 0))}`}>{d.dealer_sell != null ? fmt(-d.dealer_sell) : '—'}</td>
<td className={`py-1 px-2 text-right font-medium ${cls(d.dealer_net)}`}>{fmt(d.dealer_net)}</td>
<td className={`py-1 px-2 text-right font-bold ${cls(d.institutional_net)}`}>{fmt(d.institutional_net)}</td>
</tr>
))}
</tbody>
</table>
{filtered.length >= 10 && rows > 10 && (
<button
onClick={() => setExpanded(e => !e)}
className="mt-2 text-xs text-slate-500 hover:text-slate-300 px-2"
>
{expanded ? '▲ 收合' : `▼ 顯示更多(最近 ${rows} 日)`}
</button>
)}
</div>
)
}