feat: show stock names as primary display in portfolio card, detail, and rebalance pages
This commit is contained in:
parent
628b431171
commit
87dff8bfa7
@ -33,6 +33,7 @@ interface Target {
|
||||
interface Transaction {
|
||||
id: number;
|
||||
ticker: string;
|
||||
name: string | null;
|
||||
tx_type: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
@ -341,10 +342,7 @@ export default function PortfolioDetailPage() {
|
||||
{portfolio.holdings.map((holding, index) => (
|
||||
<tr key={holding.ticker}>
|
||||
<td className="px-4 py-3">
|
||||
<div className="font-medium text-sm">{holding.name || holding.ticker}</div>
|
||||
{holding.name && (
|
||||
<div className="text-xs text-muted-foreground">{holding.ticker}</div>
|
||||
)}
|
||||
<span className="font-medium text-sm" title={holding.ticker}>{holding.name || holding.ticker}</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-right">
|
||||
{holding.quantity.toLocaleString()}
|
||||
@ -454,7 +452,7 @@ export default function PortfolioDetailPage() {
|
||||
<td className="px-4 py-3 text-sm text-muted-foreground">
|
||||
{new Date(tx.executed_at).toLocaleString('ko-KR')}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm font-medium">{tx.ticker}</td>
|
||||
<td className="px-4 py-3 text-sm font-medium" title={tx.ticker}>{tx.name || tx.ticker}</td>
|
||||
<td className="px-4 py-3 text-sm text-center">
|
||||
<span
|
||||
className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
@ -519,7 +517,7 @@ export default function PortfolioDetailPage() {
|
||||
return (
|
||||
<div key={target.ticker} className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="font-medium">{holding?.name || target.ticker}</span>
|
||||
<span className="font-medium" title={target.ticker}>{holding?.name || target.ticker}</span>
|
||||
<span className="text-muted-foreground">
|
||||
{actualRatio.toFixed(1)}% / {target.target_ratio.toFixed(1)}%
|
||||
<span
|
||||
|
||||
@ -59,6 +59,7 @@ export default function RebalancePage() {
|
||||
const [result, setResult] = useState<RebalanceResponse | null>(null);
|
||||
const [calculating, setCalculating] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [nameMap, setNameMap] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
@ -81,6 +82,18 @@ export default function RebalancePage() {
|
||||
initialPrices[ticker] = '';
|
||||
});
|
||||
setPrices(initialPrices);
|
||||
|
||||
// Fetch stock names from portfolio detail
|
||||
try {
|
||||
const detail = await api.get<{ holdings: { ticker: string; name: string | null }[] }>(`/api/portfolios/${portfolioId}/detail`);
|
||||
const names: Record<string, string> = {};
|
||||
for (const h of detail.holdings) {
|
||||
if (h.name) names[h.ticker] = h.name;
|
||||
}
|
||||
setNameMap(names);
|
||||
} catch {
|
||||
// Names are optional, continue without
|
||||
}
|
||||
} catch {
|
||||
router.push('/login');
|
||||
} finally {
|
||||
@ -115,6 +128,12 @@ export default function RebalancePage() {
|
||||
body
|
||||
);
|
||||
setResult(data);
|
||||
// Update name map from results
|
||||
const newNames = { ...nameMap };
|
||||
for (const item of data.items) {
|
||||
if (item.name) newNames[item.ticker] = item.name;
|
||||
}
|
||||
setNameMap(newNames);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Calculation failed');
|
||||
} finally {
|
||||
@ -180,7 +199,7 @@ export default function RebalancePage() {
|
||||
return (
|
||||
<div key={ticker}>
|
||||
<Label htmlFor={`price-${ticker}`}>
|
||||
{ticker} {target ? `(목표 ${target.target_ratio}%)` : ''} - 보유 {getHoldingQty(ticker)}주
|
||||
{nameMap[ticker] || ticker} {target ? `(목표 ${target.target_ratio}%)` : ''} - 보유 {getHoldingQty(ticker)}주
|
||||
</Label>
|
||||
<Input
|
||||
id={`price-${ticker}`}
|
||||
@ -297,10 +316,7 @@ export default function RebalancePage() {
|
||||
{result.items.map((item) => (
|
||||
<tr key={item.ticker}>
|
||||
<td className="px-3 py-3">
|
||||
<div className="font-medium">{item.ticker}</div>
|
||||
{item.name && (
|
||||
<div className="text-xs text-muted-foreground">{item.name}</div>
|
||||
)}
|
||||
<span className="font-medium" title={item.ticker}>{item.name || item.ticker}</span>
|
||||
</td>
|
||||
<td className="px-3 py-3 text-sm text-right">
|
||||
{item.current_quantity.toLocaleString()}
|
||||
|
||||
@ -11,6 +11,7 @@ import { api } from '@/lib/api';
|
||||
|
||||
interface HoldingWithValue {
|
||||
ticker: string;
|
||||
name: string | null;
|
||||
current_ratio: number | null;
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts';
|
||||
|
||||
interface Holding {
|
||||
ticker: string;
|
||||
name: string | null;
|
||||
current_ratio: number | null;
|
||||
}
|
||||
|
||||
@ -61,7 +62,8 @@ export function PortfolioCard({
|
||||
.filter((h) => h.current_ratio !== null && h.current_ratio > 0)
|
||||
.slice(0, 6)
|
||||
.map((h, index) => ({
|
||||
name: h.ticker,
|
||||
name: h.name || h.ticker,
|
||||
ticker: h.ticker,
|
||||
value: h.current_ratio ?? 0,
|
||||
color: CHART_COLORS[index % CHART_COLORS.length],
|
||||
}));
|
||||
@ -144,6 +146,7 @@ export function PortfolioCard({
|
||||
<span
|
||||
key={index}
|
||||
className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground"
|
||||
title={item.ticker}
|
||||
>
|
||||
{item.name}
|
||||
</span>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user