@@ -3,76 +3,19 @@ import { Badge, Card } from 'react-bootstrap';
33
44import { INDEX_CATEGORY_LABEL_KEYS , INDEX_RISK_LABEL_KEYS } from '../../constants/finance' ;
55import { I18nContext } from '../../models/Translation' ;
6- import { IndexFundSnapshot , IndexHistoryPoint } from '../../types/finance' ;
6+ import { IndexFundSnapshot } from '../../types/finance' ;
77import styles from './Finance.module.less' ;
8+ import { SparkLine } from './SparkLine' ;
89
9- export interface FundCardProps {
10- data : IndexFundSnapshot ;
11- }
12-
13- const formatNumber = ( value ?: number | null ) =>
14- value != null ? value . toLocaleString ( 'zh-CN' , { maximumFractionDigits : 2 } ) : '--' ;
10+ const formatNumber = ( value ?: number | null , locale = 'zh-CN' ) =>
11+ value != null ? value . toLocaleString ( locale , { maximumFractionDigits : 2 } ) : '--' ;
1512
1613const formatPercent = ( value ?: number | null ) =>
1714 value != null ? `${ ( value * 100 ) . toFixed ( 2 ) } %` : '--' ;
1815
1916const valueTone = ( value ?: number | null ) =>
2017 value != null ? ( value >= 0 ? styles . positiveText : styles . negativeText ) : styles . mutedText ;
2118
22- const Sparkline : FC < { points : IndexHistoryPoint [ ] ; chartId : string } > = ( { points, chartId } ) => {
23- const { t } = useContext ( I18nContext ) ;
24-
25- const { polyline, gradientStops } = useMemo ( ( ) => {
26- if ( ! points . length ) return { polyline : '' , gradientStops : [ ] as number [ ] } ;
27-
28- const values = points . map ( ( { value } ) => value ) ;
29- const min = Math . min ( ...values ) ;
30- const max = Math . max ( ...values ) ;
31- const delta = max - min || 1 ;
32-
33- const polylinePoints = points
34- . map ( ( { value } , index , { length } ) => {
35- const x = ( index / Math . max ( length - 1 , 1 ) ) * 100 ;
36- const y = ( ( max - value ) / delta ) * 40 ;
37-
38- return `${ x . toFixed ( 2 ) } ,${ y . toFixed ( 2 ) } ` ;
39- } )
40- . join ( ' ' ) ;
41-
42- const gradientOffsets = [ 0 , 50 , 100 ] ;
43-
44- return { polyline : polylinePoints , gradientStops : gradientOffsets } ;
45- } , [ points ] ) ;
46- const gradientId = `${ chartId } -gradient` ;
47-
48- return (
49- < div className = { styles . sparkline } >
50- < svg viewBox = "0 0 100 40" role = "img" aria-label = { t ( 'index_sparkline_60d_label' ) } >
51- < defs >
52- < linearGradient id = { gradientId } x1 = "0%" y1 = "0%" x2 = "100%" y2 = "0%" >
53- { gradientStops . map ( offset => (
54- < stop
55- key = { offset }
56- offset = { `${ offset } %` }
57- stopColor = "var(--bs-primary)"
58- stopOpacity = "0.5"
59- />
60- ) ) }
61- </ linearGradient >
62- </ defs >
63- < polyline
64- fill = "none"
65- stroke = { `url(#${ gradientId } )` }
66- strokeWidth = "2"
67- strokeLinejoin = "round"
68- strokeLinecap = "round"
69- points = { polyline }
70- />
71- </ svg >
72- </ div >
73- ) ;
74- } ;
75-
7619export const FundCard : FC < IndexFundSnapshot > = ( {
7720 symbol,
7821 displayName,
@@ -89,16 +32,14 @@ export const FundCard: FC<IndexFundSnapshot> = ({
8932 description,
9033 source,
9134} ) => {
92- const { t } = useContext ( I18nContext ) ;
35+ const { currentLanguage , t } = useContext ( I18nContext ) ;
9336 const sparklineId = useMemo ( ( ) => `fund-${ symbol } ` , [ symbol ] ) ;
9437 const updatedAtISO = updatedAt ? new Date ( updatedAt ) . toJSON ( ) : undefined ;
9538
9639 return (
9740 < Card className = { `${ styles . fundCard } h-100` } >
9841 < Card . Body className = "d-flex flex-column gap-3" >
99- < div
100- className = { `${ styles . fundCardHeader } d-flex justify-content-between align-items-start` }
101- >
42+ < div className = { `${ styles . header } d-flex justify-content-between align-items-start` } >
10243 < div >
10344 < h3 className = "h5 mb-1" > { displayName } </ h3 >
10445 < div className = "d-flex flex-wrap gap-2 align-items-center" >
@@ -131,9 +72,9 @@ export const FundCard: FC<IndexFundSnapshot> = ({
13172
13273 < p className = "text-muted mb-0" > { description } </ p >
13374
134- < dl className = { `${ styles . metricRow } d-flex flex-wrap gap-4 align-items-center` } >
75+ < dl className = { `${ styles . metric } d-flex flex-wrap gap-4 align-items-center` } >
13576 < dt className = "text-muted mb-1" > { t ( 'index_metric_latest_value' ) } </ dt >
136- < dd className = { styles . metricValue } > { formatNumber ( latestValue ) } </ dd >
77+ < dd className = { styles . value } > { formatNumber ( latestValue , currentLanguage ) } </ dd >
13778
13879 < dt className = "text-muted mb-1" > { t ( 'index_metric_daily_change' ) } </ dt >
13980 < dd className = { valueTone ( dailyChangePct ) } > { formatPercent ( dailyChangePct ) } </ dd >
@@ -145,11 +86,7 @@ export const FundCard: FC<IndexFundSnapshot> = ({
14586 < dd className = { valueTone ( maxDrawdownPct ) } > { formatPercent ( maxDrawdownPct ) } </ dd >
14687 </ dl >
14788
148- { sparkline . length ? (
149- < Sparkline points = { sparkline } chartId = { sparklineId } />
150- ) : (
151- < div className = { styles . sparklinePlaceholder } > { t ( 'data_preparing' ) } </ div >
152- ) }
89+ < SparkLine points = { sparkline } chartId = { sparklineId } />
15390
15491 < ul className = "list-unstyled m-0 d-flex flex-wrap gap-2" >
15592 { tags ?. map ( tag => (
0 commit comments