feat: enrich portfolio list with total value and position breakdown from MOEX #10
@ -2,6 +2,15 @@ import { Link } from 'react-router-dom';
|
|||||||
import type { Portfolio } from '../../api/responses';
|
import type { Portfolio } from '../../api/responses';
|
||||||
|
|
||||||
export function PortfolioCard({ portfolio }: { portfolio: Portfolio }) {
|
export function PortfolioCard({ portfolio }: { portfolio: Portfolio }) {
|
||||||
|
const chipStyle = (bg: string): React.CSSProperties => ({
|
||||||
|
background: bg,
|
||||||
|
padding: '4px 10px',
|
||||||
|
borderRadius: 6,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: 500,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={`/portfolios/${portfolio.id}`}
|
to={`/portfolios/${portfolio.id}`}
|
||||||
@ -18,22 +27,61 @@ export function PortfolioCard({ portfolio }: { portfolio: Portfolio }) {
|
|||||||
onMouseEnter={(e) => (e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)')}
|
onMouseEnter={(e) => (e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)')}
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = 'none')}
|
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = 'none')}
|
||||||
>
|
>
|
||||||
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>{portfolio.name}</h3>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
marginBottom: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 600, flex: 1 }}>{portfolio.name}</h3>
|
||||||
|
<div style={{ textAlign: 'right' }}>
|
||||||
|
<div style={{ fontSize: 20, fontWeight: 700, lineHeight: 1.2 }}>
|
||||||
|
{portfolio.totalValue.toLocaleString('ru-RU', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--color-text-secondary)' }}>
|
||||||
|
{portfolio.currency}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{portfolio.description && (
|
{portfolio.description && (
|
||||||
<p style={{ margin: '4px 0 0', fontSize: 13, color: 'var(--color-text-secondary)' }}>
|
<p style={{ margin: '0 0 12px', fontSize: 13, color: 'var(--color-text-secondary)' }}>
|
||||||
{portfolio.description}
|
{portfolio.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<span
|
|
||||||
style={{
|
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
|
||||||
fontSize: 12,
|
{portfolio.shareCount > 0 && (
|
||||||
color: 'var(--color-text-secondary)',
|
<span style={chipStyle('#1b5e20')}>
|
||||||
marginTop: 8,
|
{portfolio.shareCount} {pluralize(portfolio.shareCount, 'акция', 'акции', 'акций')}
|
||||||
display: 'inline-block',
|
</span>
|
||||||
}}
|
)}
|
||||||
>
|
{portfolio.bondCount > 0 && (
|
||||||
{portfolio.currency} · обновлён {new Date(portfolio.updatedAt).toLocaleDateString('ru-RU')}
|
<span style={chipStyle('#0d47a1')}>
|
||||||
|
{portfolio.bondCount}{' '}
|
||||||
|
{pluralize(portfolio.bondCount, 'облигация', 'облигации', 'облигаций')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span style={chipStyle('#424242')}>
|
||||||
|
{portfolio.positionCount}{' '}
|
||||||
|
{pluralize(portfolio.positionCount, 'позиция', 'позиции', 'позиций')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span style={{ fontSize: 12, color: 'var(--color-text-secondary)' }}>
|
||||||
|
обновлён {new Date(portfolio.updatedAt).toLocaleDateString('ru-RU')}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pluralize(n: number, one: string, few: string, many: string): string {
|
||||||
|
if (n % 10 === 1 && n % 100 !== 11) return one;
|
||||||
|
if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return few;
|
||||||
|
return many;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user