1- import React , { useState , useEffect } from 'react' ;
1+ import React , { useState , useEffect , useMemo } from 'react' ;
22import TopicsSidebar from './TopicsSidebar' ;
33import IndicatorsList from './IndicatorsList' ;
44import DataVisualization from './DataVisualization' ;
5- import { generateTimeSeriesData } from '../../data/mockData' ; // Keep for now until we have real time series
5+ import { generateTimeSeriesData } from '../../data/mockData' ; // Only keeping this until we have real time series data
66import type { Topic , Indicator } from './types' ;
77
88const GHGDashboard : React . FC = ( ) => {
@@ -12,68 +12,134 @@ const GHGDashboard: React.FC = () => {
1212 const [ selectedKeywords , setSelectedKeywords ] = useState < string [ ] > ( [ ] ) ;
1313 const [ viewMode , setViewMode ] = useState < 'chart' | 'table' > ( 'chart' ) ;
1414 const [ indicators , setIndicators ] = useState < Indicator [ ] > ( [ ] ) ;
15- const [ topics , setTopics ] = useState < Topic [ ] > ( [ ] ) ;
16-
15+
1716 // Fetch indicators from the API
1817 useEffect ( ( ) => {
1918 const fetchIndicators = async ( ) => {
2019 try {
2120 const response = await fetch ( 'https://api.unepgrid.ch/stats/v1/indicators?language=eq.en' ) ;
2221 const data = await response . json ( ) ;
2322 setIndicators ( data ) ;
24-
25- // Process collections into topics
26- const collectionsMap = new Map < number , Topic > ( ) ;
27-
28- data . forEach ( ( indicator : Indicator ) => {
29- indicator . collections . forEach ( collection => {
30- if ( ! collectionsMap . has ( collection . id ) ) {
31- collectionsMap . set ( collection . id , {
32- id : collection . id ,
33- title : collection . title ,
34- count : 1 ,
35- subtopics : [ ]
36- } ) ;
37- } else {
38- const topic = collectionsMap . get ( collection . id ) ;
39- if ( topic ) {
40- topic . count += 1 ;
41- }
42- }
43- } ) ;
44- } ) ;
45-
46- setTopics ( Array . from ( collectionsMap . values ( ) ) ) ;
4723 } catch ( error ) {
4824 console . error ( 'Error fetching indicators:' , error ) ;
4925 }
5026 } ;
5127
5228 fetchIndicators ( ) ;
5329 } , [ ] ) ;
54-
30+
31+ // Derive topics from collections in indicators
32+ const topics = useMemo ( ( ) => {
33+ const collectionsMap = new Map < number , Topic > ( ) ;
34+
35+ indicators . forEach ( indicator => {
36+ indicator . collections . forEach ( collection => {
37+ if ( ! collectionsMap . has ( collection . id ) ) {
38+ collectionsMap . set ( collection . id , {
39+ id : collection . id ,
40+ title : collection . title ,
41+ count : 1 ,
42+ subtopics : [ ] // We could potentially derive these from the data if needed
43+ } ) ;
44+ } else {
45+ const topic = collectionsMap . get ( collection . id ) ;
46+ if ( topic ) {
47+ topic . count += 1 ;
48+ }
49+ }
50+ } ) ;
51+ } ) ;
52+
53+ return Array . from ( collectionsMap . values ( ) ) ;
54+ } , [ indicators ] ) ;
55+
5556 // Filter indicators based on search term, selected topic, and keywords
56- const filteredIndicators = indicators . filter ( indicator => {
57- const matchesSearch = indicator . title . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
58- indicator . description . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
59- indicator . keywords . some ( keyword => keyword . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ) ;
57+ const filteredIndicators = useMemo ( ( ) => {
58+ return indicators . filter ( indicator => {
59+ const matchesSearch = searchTerm === '' ||
60+ indicator . title . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
61+ indicator . description . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
62+ indicator . keywords . some ( keyword =>
63+ keyword . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) )
64+ ) ;
6065
61- const matchesTopic = ! selectedTopic ||
62- indicator . collections . some ( collection => collection . id === selectedTopic . id ) ;
66+ const matchesTopic = ! selectedTopic ||
67+ indicator . collections . some ( collection =>
68+ collection . id === selectedTopic . id
69+ ) ;
6370
64- const matchesKeywords = selectedKeywords . length === 0 ||
65- selectedKeywords . every ( keyword =>
66- indicator . keywords . includes ( keyword )
67- ) ;
71+ const matchesKeywords = selectedKeywords . length === 0 ||
72+ selectedKeywords . every ( keyword =>
73+ indicator . keywords . includes ( keyword )
74+ ) ;
6875
69- return matchesSearch && matchesTopic && matchesKeywords ;
70- } ) ;
76+ return matchesSearch && matchesTopic && matchesKeywords ;
77+ } ) ;
78+ } , [ indicators , searchTerm , selectedTopic , selectedKeywords ] ) ;
79+
80+ // Calculate dynamic facet counts
81+ const dynamicFacets = useMemo ( ( ) => {
82+ // Get counts excluding the selected topic filter
83+ const topicResults = indicators . filter ( indicator => {
84+ const matchesSearch = searchTerm === '' ||
85+ indicator . title . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
86+ indicator . description . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ;
87+
88+ const matchesKeywords = selectedKeywords . length === 0 ||
89+ selectedKeywords . every ( keyword =>
90+ indicator . keywords . includes ( keyword )
91+ ) ;
92+
93+ return matchesSearch && matchesKeywords ;
94+ } ) ;
95+
96+ // Calculate topic counts
97+ const topicCounts = new Map < number , number > ( ) ;
98+ topicResults . forEach ( indicator => {
99+ indicator . collections . forEach ( collection => {
100+ topicCounts . set (
101+ collection . id ,
102+ ( topicCounts . get ( collection . id ) || 0 ) + 1
103+ ) ;
104+ } ) ;
105+ } ) ;
106+
107+ // Get counts excluding the selected keywords filter
108+ const keywordResults = indicators . filter ( indicator => {
109+ const matchesSearch = searchTerm === '' ||
110+ indicator . title . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
111+ indicator . description . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ;
112+
113+ const matchesTopic = ! selectedTopic ||
114+ indicator . collections . some ( collection =>
115+ collection . id === selectedTopic . id
116+ ) ;
117+
118+ return matchesSearch && matchesTopic ;
119+ } ) ;
120+
121+ // Calculate keyword counts
122+ const keywordCounts = new Map < string , number > ( ) ;
123+ keywordResults . forEach ( indicator => {
124+ indicator . keywords . forEach ( keyword => {
125+ keywordCounts . set (
126+ keyword ,
127+ ( keywordCounts . get ( keyword ) || 0 ) + 1
128+ ) ;
129+ } ) ;
130+ } ) ;
131+
132+ return {
133+ topicCounts,
134+ keywordCounts
135+ } ;
136+ } , [ indicators , searchTerm , selectedTopic , selectedKeywords ] ) ;
71137
72138 // Keep using mock time series data for now
73139 const timeSeriesData = generateTimeSeriesData ( ) ;
74140
75141 return (
76- < div className = "flex h-screen bg-white" >
142+ < div className = "flex h-screen overflow-hidden bg-white" >
77143 < div className = "w-[300px] border-r border-gray-200" >
78144 < TopicsSidebar
79145 topics = { topics }
@@ -84,10 +150,11 @@ const GHGDashboard: React.FC = () => {
84150 indicators = { indicators }
85151 selectedKeywords = { selectedKeywords }
86152 setSelectedKeywords = { setSelectedKeywords }
153+ dynamicCounts = { dynamicFacets }
87154 />
88155 </ div >
89156
90- < div className = "w-[400px] border-r border-gray-200 flex flex-col h-screen overflow-hidden" >
157+ < div className = "w-[400px] border-r border-gray-200 overflow-hidden" >
91158 < IndicatorsList
92159 indicators = { filteredIndicators }
93160 selectedIndicator = { selectedIndicator }
@@ -96,7 +163,7 @@ const GHGDashboard: React.FC = () => {
96163 />
97164 </ div >
98165
99- < div className = "flex-1 p-6" >
166+ < div className = "flex-1 p-6 overflow-y-auto " >
100167 < DataVisualization
101168 data = { timeSeriesData }
102169 selectedIndicator = { selectedIndicator }
0 commit comments