feat: show stock names as primary display in portfolio card, detail, and rebalance pages

This commit is contained in:
ayuriel 2026-02-16 12:51:56 +09:00
parent 628b431171
commit 87dff8bfa7
4 changed files with 30 additions and 12 deletions

View File

@ -33,6 +33,7 @@ interface Target {
interface Transaction { interface Transaction {
id: number; id: number;
ticker: string; ticker: string;
name: string | null;
tx_type: string; tx_type: string;
quantity: number; quantity: number;
price: number; price: number;
@ -341,10 +342,7 @@ export default function PortfolioDetailPage() {
{portfolio.holdings.map((holding, index) => ( {portfolio.holdings.map((holding, index) => (
<tr key={holding.ticker}> <tr key={holding.ticker}>
<td className="px-4 py-3"> <td className="px-4 py-3">
<div className="font-medium text-sm">{holding.name || holding.ticker}</div> <span className="font-medium text-sm" title={holding.ticker}>{holding.name || holding.ticker}</span>
{holding.name && (
<div className="text-xs text-muted-foreground">{holding.ticker}</div>
)}
</td> </td>
<td className="px-4 py-3 text-sm text-right"> <td className="px-4 py-3 text-sm text-right">
{holding.quantity.toLocaleString()} {holding.quantity.toLocaleString()}
@ -454,7 +452,7 @@ export default function PortfolioDetailPage() {
<td className="px-4 py-3 text-sm text-muted-foreground"> <td className="px-4 py-3 text-sm text-muted-foreground">
{new Date(tx.executed_at).toLocaleString('ko-KR')} {new Date(tx.executed_at).toLocaleString('ko-KR')}
</td> </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"> <td className="px-4 py-3 text-sm text-center">
<span <span
className={`px-2 py-1 rounded text-xs font-medium ${ className={`px-2 py-1 rounded text-xs font-medium ${
@ -519,7 +517,7 @@ export default function PortfolioDetailPage() {
return ( return (
<div key={target.ticker} className="space-y-2"> <div key={target.ticker} className="space-y-2">
<div className="flex justify-between text-sm"> <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"> <span className="text-muted-foreground">
{actualRatio.toFixed(1)}% / {target.target_ratio.toFixed(1)}% {actualRatio.toFixed(1)}% / {target.target_ratio.toFixed(1)}%
<span <span

View File

@ -59,6 +59,7 @@ export default function RebalancePage() {
const [result, setResult] = useState<RebalanceResponse | null>(null); const [result, setResult] = useState<RebalanceResponse | null>(null);
const [calculating, setCalculating] = useState(false); const [calculating, setCalculating] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [nameMap, setNameMap] = useState<Record<string, string>>({});
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
@ -81,6 +82,18 @@ export default function RebalancePage() {
initialPrices[ticker] = ''; initialPrices[ticker] = '';
}); });
setPrices(initialPrices); 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 { } catch {
router.push('/login'); router.push('/login');
} finally { } finally {
@ -115,6 +128,12 @@ export default function RebalancePage() {
body body
); );
setResult(data); 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) { } catch (err) {
setError(err instanceof Error ? err.message : 'Calculation failed'); setError(err instanceof Error ? err.message : 'Calculation failed');
} finally { } finally {
@ -180,7 +199,7 @@ export default function RebalancePage() {
return ( return (
<div key={ticker}> <div key={ticker}>
<Label htmlFor={`price-${ticker}`}> <Label htmlFor={`price-${ticker}`}>
{ticker} {target ? `(목표 ${target.target_ratio}%)` : ''} - {getHoldingQty(ticker)} {nameMap[ticker] || ticker} {target ? `(목표 ${target.target_ratio}%)` : ''} - {getHoldingQty(ticker)}
</Label> </Label>
<Input <Input
id={`price-${ticker}`} id={`price-${ticker}`}
@ -297,10 +316,7 @@ export default function RebalancePage() {
{result.items.map((item) => ( {result.items.map((item) => (
<tr key={item.ticker}> <tr key={item.ticker}>
<td className="px-3 py-3"> <td className="px-3 py-3">
<div className="font-medium">{item.ticker}</div> <span className="font-medium" title={item.ticker}>{item.name || item.ticker}</span>
{item.name && (
<div className="text-xs text-muted-foreground">{item.name}</div>
)}
</td> </td>
<td className="px-3 py-3 text-sm text-right"> <td className="px-3 py-3 text-sm text-right">
{item.current_quantity.toLocaleString()} {item.current_quantity.toLocaleString()}

View File

@ -11,6 +11,7 @@ import { api } from '@/lib/api';
interface HoldingWithValue { interface HoldingWithValue {
ticker: string; ticker: string;
name: string | null;
current_ratio: number | null; current_ratio: number | null;
} }

View File

@ -6,6 +6,7 @@ import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts';
interface Holding { interface Holding {
ticker: string; ticker: string;
name: string | null;
current_ratio: number | null; current_ratio: number | null;
} }
@ -61,7 +62,8 @@ export function PortfolioCard({
.filter((h) => h.current_ratio !== null && h.current_ratio > 0) .filter((h) => h.current_ratio !== null && h.current_ratio > 0)
.slice(0, 6) .slice(0, 6)
.map((h, index) => ({ .map((h, index) => ({
name: h.ticker, name: h.name || h.ticker,
ticker: h.ticker,
value: h.current_ratio ?? 0, value: h.current_ratio ?? 0,
color: CHART_COLORS[index % CHART_COLORS.length], color: CHART_COLORS[index % CHART_COLORS.length],
})); }));
@ -144,6 +146,7 @@ export function PortfolioCard({
<span <span
key={index} key={index}
className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground" className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground"
title={item.ticker}
> >
{item.name} {item.name}
</span> </span>