@@ -9,10 +9,15 @@ import {
99 Container ,
1010 KeyFigure ,
1111 LegendItem ,
12+ TextOutput ,
1213 TimeSeriesChart ,
1314} from '@ifrc-go/ui' ;
1415import { useTranslation } from '@ifrc-go/ui/hooks' ;
15- import { getDatesSeparatedByYear } from '@ifrc-go/ui/utils' ;
16+ import {
17+ formatDate ,
18+ getDatesSeparatedByYear ,
19+ getFormattedDateKey ,
20+ } from '@ifrc-go/ui/utils' ;
1621import {
1722 isDefined ,
1823 isNotDefined ,
@@ -46,6 +51,12 @@ const regionValueSelector = (datum: RegionStatItem) => datum.count;
4651const regionLabelSelector = ( datum : RegionStatItem ) => datum . region_name ;
4752
4853type SourceType = 'dref' | 'emergencyAppeal' | 'others' ;
54+ interface SourceTypeOption {
55+ key : SourceType ;
56+ label : string ;
57+ color : string ;
58+ }
59+
4960const dataKeys : SourceType [ ] = [
5061 'dref' ,
5162 'emergencyAppeal' ,
@@ -62,11 +73,6 @@ const xAxisFormatter = (date: Date) => date.toLocaleString(
6273 { year : 'numeric' } ,
6374) ;
6475
65- interface SourceTypeOption {
66- key : SourceType ;
67- label : string ;
68- color : string ;
69- }
7076type SourceTypeEnum = components < 'read' > [ 'schemas' ] [ 'ApiAppealTypeEnumKey' ] ;
7177const SOURCE_TYPE_EMERGENCY = 1 satisfies SourceTypeEnum ;
7278const SOURCE_TYPE_DREF = 0 satisfies SourceTypeEnum ;
@@ -75,13 +81,13 @@ const transformSourcesOverTimeData = (data: SourcesOverTimeItem[]) => {
7581 const groupedData : Record < string , Record < SourceType , number > > = { } ;
7682
7783 data . forEach ( ( entry ) => {
78- const year = new Date ( entry . date ) . getFullYear ( ) . toString ( ) ;
84+ const year = getFormattedDateKey ( entry . date ) ;
7985 if ( ! groupedData [ year ] ) {
8086 groupedData [ year ] = { dref : 0 , emergencyAppeal : 0 , others : 0 } ;
8187 }
82- if ( entry . type === SOURCE_TYPE_DREF ) {
88+ if ( entry . atype === SOURCE_TYPE_DREF ) {
8389 groupedData [ year ] . dref += entry . count ;
84- } else if ( entry . type === SOURCE_TYPE_EMERGENCY ) {
90+ } else if ( entry . atype === SOURCE_TYPE_EMERGENCY ) {
8591 groupedData [ year ] . emergencyAppeal += entry . count ;
8692 } else {
8793 groupedData [ year ] . others += entry . count ;
@@ -127,19 +133,20 @@ function Stats(props: Props) {
127133 } ,
128134 [ learningStatsResponse ] ,
129135 ) ;
136+
130137 const dateList = useMemo ( ( ) => {
131138 if ( isNotDefined ( sourcesOverTimeData ) ) {
132139 return undefined ;
133140 }
134- const dates = Object . keys ( sourcesOverTimeData ) . map ( ( year ) => new Date ( Number ( year ) , 0 , 1 ) ) ;
141+ const dates = Object . keys ( sourcesOverTimeData ) . map ( ( year ) => new Date ( year ) ) ;
135142 const oldestDate = new Date ( Math . min ( ...dates . map ( ( date ) => date . getTime ( ) ) ) ) ;
136143 const latestDate = new Date ( Math . max ( ...dates . map ( ( date ) => date . getTime ( ) ) ) ) ;
137144 return getDatesSeparatedByYear ( oldestDate , latestDate ) ;
138145 } , [ sourcesOverTimeData ] ) ;
139146
140147 const sourcesOverTimeValueSelector = useCallback (
141148 ( key : SourceType , date : Date ) => {
142- const value = sourcesOverTimeData ?. [ date . getFullYear ( ) ] ?. [ key ] ;
149+ const value = sourcesOverTimeData ?. [ getFormattedDateKey ( date ) ] ?. [ key ] ;
143150 if ( isDefined ( value ) && value > 0 ) {
144151 return value ;
145152 }
@@ -170,26 +177,32 @@ function Stats(props: Props) {
170177 strings . sourceOthers ,
171178 ] ) ;
172179
180+ const activePointData = activePointKey ? sourcesOverTimeData ?. [ activePointKey ] : undefined ;
181+
173182 return (
174183 < div className = { styles . stats } >
175184 { learningStatsPending && < BlockLoading /> }
176- < div className = { styles . keyFigureCard } >
185+ < div className = { styles . keyFigureList } >
177186 < KeyFigure
187+ className = { styles . keyFigure }
178188 value = { learningStatsResponse ?. operations_included }
179189 label = { strings . operationsIncluded }
180190 labelClassName = { styles . keyFigureDescription }
181191 />
182192 < KeyFigure
193+ className = { styles . keyFigure }
183194 value = { learningStatsResponse ?. sources_used }
184195 label = { strings . sourcesUsed }
185196 labelClassName = { styles . keyFigureDescription }
186197 />
187198 < KeyFigure
199+ className = { styles . keyFigure }
188200 value = { learningStatsResponse ?. learning_extracts }
189201 label = { strings . learningExtract }
190202 labelClassName = { styles . keyFigureDescription }
191203 />
192204 < KeyFigure
205+ className = { styles . keyFigure }
193206 value = { learningStatsResponse ?. sectors_covered }
194207 label = { strings . sectorsCovered }
195208 labelClassName = { styles . keyFigureDescription }
@@ -245,38 +258,84 @@ function Stats(props: Props) {
245258 withInternalPadding
246259 compactMessage
247260 pending = { learningStatsPending }
261+ childrenContainerClassName = { styles . chartContainer }
248262 empty = { isDefined ( learningStatsResponse ?. sources_overtime ) && (
249263 ( learningStatsResponse ?. sources_overtime . length ?? 0 ) < 1
250264 ) }
251- footerIcons = { (
252- < div className = { styles . typeOfSourceLegend } >
253- < div className = { styles . legendLabel } >
254- { strings . sourcesTypeLegendLabel }
255- </ div >
256- < div className = { styles . legendContent } >
257- { sourceTypeOptions . map ( ( source ) => (
258- < LegendItem
259- key = { source . key }
260- label = { source . label }
261- color = { source . color }
262- />
263- ) ) }
264- </ div >
265- </ div >
266- ) }
267265 >
268266 { isDefined ( dateList ) && (
269- < TimeSeriesChart
270- className = { styles . timeSeriesChart }
271- xAxisTickClassName = { styles . xAxisTick }
272- timePoints = { dateList }
273- dataKeys = { dataKeys }
274- valueSelector = { sourcesOverTimeValueSelector }
275- classNameSelector = { sourceClassNameSelector }
276- activePointKey = { activePointKey }
277- onTimePointClick = { setActivePointKey }
278- xAxisFormatter = { xAxisFormatter }
279- />
267+ < >
268+ < TimeSeriesChart
269+ className = { styles . timeSeriesChart }
270+ xAxisTickClassName = { styles . xAxisTick }
271+ timePoints = { dateList }
272+ dataKeys = { dataKeys }
273+ valueSelector = { sourcesOverTimeValueSelector }
274+ classNameSelector = { sourceClassNameSelector }
275+ activePointKey = { activePointKey }
276+ onTimePointClick = { setActivePointKey }
277+ xAxisFormatter = { xAxisFormatter }
278+ />
279+ { isDefined ( activePointKey ) ? (
280+ < div
281+ className = { styles . legend }
282+ >
283+ < TextOutput
284+ value = { formatDate ( activePointKey , 'yyyy' ) ?? '--' }
285+ strongValue
286+ />
287+ < TextOutput
288+ label = { (
289+ < LegendItem
290+ label = { strings . sourceDREF }
291+ color = "var(--color-source-dref)"
292+ />
293+ ) }
294+ withoutLabelColon
295+ value = { activePointData ?. dref }
296+ valueType = "number"
297+ />
298+ < TextOutput
299+ label = { (
300+ < LegendItem
301+ label = { strings . sourceEmergencyAppeal }
302+ color = "var(--color-source-emergency-appeal)"
303+ />
304+ ) }
305+ withoutLabelColon
306+ value = { activePointData ?. emergencyAppeal }
307+ valueType = "number"
308+ />
309+ < TextOutput
310+ label = { (
311+ < LegendItem
312+ label = { strings . sourceOthers }
313+ color = "var(--color-source-emergency-appeal)"
314+ />
315+ ) }
316+ withoutLabelColon
317+ value = { activePointData ?. others }
318+ valueType = "number"
319+ />
320+ </ div >
321+ ) : (
322+ < div className = { styles . typeOfSourceLegend } >
323+ < div className = { styles . legendLabel } >
324+ { strings . sourcesTypeLegendLabel }
325+ </ div >
326+ < div className = { styles . legendContent } >
327+ { sourceTypeOptions . map ( ( source ) => (
328+ < LegendItem
329+ key = { source . key }
330+ label = { source . label }
331+ color = { source . color }
332+ />
333+ ) ) }
334+ </ div >
335+ </ div >
336+ ) }
337+ </ >
338+
280339 ) }
281340
282341 </ Container >
0 commit comments