From 71c4ccca2be25d31f712094d03d2db801e720af3 Mon Sep 17 00:00:00 2001 From: Jacobojijo Date: Fri, 30 May 2025 12:10:33 +0300 Subject: [PATCH] fix recent disasters loading --- src/pages/homePage/HomePage.tsx | 493 ++++++++++-------- .../components/RecentDisasterCardMini.tsx | 65 ++- 2 files changed, 321 insertions(+), 237 deletions(-) diff --git a/src/pages/homePage/HomePage.tsx b/src/pages/homePage/HomePage.tsx index 9cf9ec9..3321c7c 100644 --- a/src/pages/homePage/HomePage.tsx +++ b/src/pages/homePage/HomePage.tsx @@ -1,6 +1,5 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { Carousel } from 'react-responsive-carousel'; - import { Badge, Button, Image } from '@chakra-ui/react'; import { Link } from 'react-router-dom'; import 'react-responsive-carousel/lib/styles/carousel.min.css'; @@ -15,161 +14,340 @@ import SDGAILabLogo from 'assets/landing/sdg_ai_lab.png'; import CBILogo from 'assets/landing/cbi_logo.png'; import { ROUTES } from 'navigation/routes'; import { CAROUSEL_ITEMS } from './helpers'; - import './HomePage.scss'; import { isSignedIn } from 'components/shared/helpers/auth'; import { RecentDisasterCardMini } from './components/RecentDisasterCardMini'; export const HomePage: React.FC = () => { - const [projectsToUse, setProjectsToUse] = useState([]); - const [technologies, setTechnologies] = useState([]); - const [disasterTypes, setDisasterTypes] = useState([]); - const [recentDisasters, setRecentDisasters] = useState([]); - const [disasterEvents, setDisasterEvents] = useState([]); + const [projectsToUse, setProjectsToUse] = useState([]); + const [technologies, setTechnologies] = useState([]); + const [disasterTypes, setDisasterTypes] = useState([]); + const [recentDisasters, setRecentDisasters] = useState([]); + const [disasterEvents, setDisasterEvents] = useState([]); const [itemDescription, setItemDescription] = useState( CAROUSEL_ITEMS[0].label ); + const [loading, setLoading] = useState({ + projects: true, + technologies: true, + disasters: true, + recentDisasters: true, + disasterEvents: true + }); - useEffect(() => { - void getProjects(); - void getTechnologies(); - void getDisasters(); - void getRecentDisasters(); - void getDisasterEvents(); - }, []); + const fallbackImages = useMemo( + () => ['fallback/map.png', 'fallback/tech.png', 'fallback/globe.png'], + [] + ); - let fallbackIndex = 0; - const fallbackImages = [ - 'fallback/map.png', - 'fallback/tech.png', - 'fallback/globe.png' - ]; + const getProjects = useCallback(async (): Promise => { + try { + const storedProjects = JSON.parse( + localStorage.getItem('drr-projects-homepage') || 'null' + ); + if (storedProjects?.version === DATA_VERSION) { + setProjectsToUse(storedProjects.data); + return; + } - const getProjects = async (): Promise => { - const storedProjects = JSON.parse( - localStorage.getItem('drr-projects-homepage') as string - ); - if (storedProjects && storedProjects.version === DATA_VERSION) { - const data = storedProjects.data; - setProjectsToUse(data); - } else { const { data, error } = await supabase .from('tr_projects') .select() .limit(4); - if (!error) { - data.splice(0, 1); - setProjectsToUse(data); + + if (!error && data) { + const filteredData = data.slice(1); + setProjectsToUse(filteredData); localStorage.setItem( 'drr-projects-homepage', - JSON.stringify({ - version: DATA_VERSION, - data - }) + JSON.stringify({ version: DATA_VERSION, data: filteredData }) ); } + } catch (error) { + console.error('Error fetching projects:', error); + } finally { + setLoading((prev) => ({ ...prev, projects: false })); } - }; - const getTechnologies = async (): Promise => { - const storedTechnologies = JSON.parse( - localStorage.getItem('drr-technologies-homepage') as string - ); - if (storedTechnologies && storedTechnologies.version === DATA_VERSION) { - setTechnologies(storedTechnologies.data); - } else { + }, []); + + const getTechnologies = useCallback(async (): Promise => { + try { + const storedTechnologies = JSON.parse( + localStorage.getItem('drr-technologies-homepage') || 'null' + ); + if (storedTechnologies?.version === DATA_VERSION) { + setTechnologies(storedTechnologies.data); + return; + } + const { data, error } = await supabase .from('technologies') .select() .limit(3); - if (!error) { + + if (!error && data) { setTechnologies(data); localStorage.setItem( 'drr-technologies-homepage', - JSON.stringify({ - version: DATA_VERSION, - data - }) + JSON.stringify({ version: DATA_VERSION, data }) ); } + } catch (error) { + console.error('Error fetching technologies:', error); + } finally { + setLoading((prev) => ({ ...prev, technologies: false })); } - }; + }, []); + + const getDisasters = useCallback(async (): Promise => { + try { + const storedDisasters = JSON.parse( + localStorage.getItem('drr-disaster-types-homepage') || 'null' + ); + if (storedDisasters?.version === DATA_VERSION) { + setDisasterTypes(storedDisasters.data); + return; + } - const getDisasters = async (): Promise => { - const storedDisasters = JSON.parse( - localStorage.getItem('drr-disaster-types-homepage') as string - ); - if (storedDisasters && storedDisasters.version === DATA_VERSION) { - setDisasterTypes(storedDisasters.data); - } else { const { data, error } = await supabase .from('disaster_types') .select() .order('id') .limit(3); - if (!error) { + + if (!error && data) { setDisasterTypes(data); localStorage.setItem( 'drr-disaster-types-homepage', - JSON.stringify({ - version: DATA_VERSION, - data - }) + JSON.stringify({ version: DATA_VERSION, data }) ); } + } catch (error) { + console.error('Error fetching disasters:', error); + } finally { + setLoading((prev) => ({ ...prev, disasters: false })); } - }; + }, []); + + const getRecentDisasters = useCallback(async (): Promise => { + try { + const storedRecentDisasters = JSON.parse( + localStorage.getItem('drr-recent-disasters') || 'null' + ); + if (storedRecentDisasters?.version === DATA_VERSION) { + setRecentDisasters(storedRecentDisasters.data); + return; + } - const getRecentDisasters = async (): Promise => { - const storedRecentDisasters = JSON.parse( - localStorage.getItem('drr-recent-disasters') as string - ); - if ( - storedRecentDisasters && - storedRecentDisasters.version === DATA_VERSION - ) { - setRecentDisasters(storedRecentDisasters.data); - } else { const { data, error } = await supabase .from('disaster_events') .select() .eq('help_needed', 1) .order('id', { ascending: false }); - if (!error) { + + if (!error && data) { setRecentDisasters(data); localStorage.setItem( 'drr-recent-disasters', - JSON.stringify({ - version: DATA_VERSION, - data - }) + JSON.stringify({ version: DATA_VERSION, data }) ); } + } catch (error) { + console.error('Error fetching recent disasters:', error); + } finally { + setLoading((prev) => ({ ...prev, recentDisasters: false })); } - }; - const getDisasterEvents = async (): Promise => { - const storedDisasterEvents = JSON.parse( - localStorage.getItem('drr-disaster-events') as string - ); - if (storedDisasterEvents && storedDisasterEvents.version === DATA_VERSION) { - setDisasterEvents(storedDisasterEvents.data); - } else { + }, []); + + const getDisasterEvents = useCallback(async (): Promise => { + try { + const storedDisasterEvents = JSON.parse( + localStorage.getItem('drr-disaster-events') || 'null' + ); + if (storedDisasterEvents?.version === DATA_VERSION) { + setDisasterEvents(storedDisasterEvents.data); + return; + } + const { data, error } = await supabase .from('disaster_events') .select() .eq('help_needed', 0) .order('id', { ascending: false }); - if (!error) { + + if (!error && data) { setDisasterEvents(data); localStorage.setItem( 'drr-disaster-events', - JSON.stringify({ - version: DATA_VERSION, - data - }) + JSON.stringify({ version: DATA_VERSION, data }) ); } + } catch (error) { + console.error('Error fetching disaster events:', error); + } finally { + setLoading((prev) => ({ ...prev, disasterEvents: false })); + } + }, []); + + useEffect(() => { + const fetchData = async (): Promise => { + await Promise.all([ + getProjects(), + getTechnologies(), + getDisasters(), + getRecentDisasters(), + getDisasterEvents() + ]); + }; + + void fetchData(); // Fixed: explicitly mark as ignored with void operator + }, [ + getProjects, + getTechnologies, + getDisasters, + getRecentDisasters, + getDisasterEvents + ]); + + const handleCarouselChange = useCallback((idx: number): void => { + // Fixed: added return type + setItemDescription(CAROUSEL_ITEMS[idx].label); + }, []); + + const handleCarouselClick = useCallback((e: number): void => { + // Fixed: added return type + window.location.href = CAROUSEL_ITEMS[e].route; + }, []); + + const renderRecentDisastersSection = useMemo(() => { + if (loading.recentDisasters || loading.disasterEvents) { + return ; } - }; + + if (recentDisasters.length === 0 && disasterEvents.length === 0) { + return ( +
+

No recent disasters

+
+ ); + } + + return ( + <> +
+ {recentDisasters.length > 0 && ( +
+
+ + Help Needed + +
+ +
+ )} + {disasterEvents.length > 0 && ( +
+ {disasterEvents.slice(0, 2).map((disasterEvent: any) => ( +
+ +
+ ))} +
+ )} +
+
+ + ); + }, [ + recentDisasters, + disasterEvents, + loading.recentDisasters, + loading.disasterEvents + ]); + + const renderProjectsSection = useMemo(() => { + if (loading.projects) return ; + if (projectsToUse.length === 0) return null; + + return ( + <> +
+ +

Projects

+ +
+
+ {projectsToUse.map((project: any, index: number) => ( +
+ +
+ ))} +
+
+ + ); + }, [projectsToUse, loading.projects, fallbackImages]); + + const renderTechnologiesSection = useMemo(() => { + if (loading.technologies) return ; + if (technologies.length === 0) return null; + + return ( + <> +
+ +

Technologies

+ +
+
+ {technologies.map((tech: any) => ( +
+ +
+ ))} +
+
+ + ); + }, [technologies, loading.technologies]); + + const renderDisasterTypesSection = useMemo(() => { + if (loading.disasters) return ; + if (disasterTypes.length === 0) return null; + + return ( + <> +
+ +

Disasters

+ +
+
+ {disasterTypes.map((disaster: any) => ( +
+ +
+ ))} +
+
+ + ); + }, [disasterTypes, loading.disasters]); return (
@@ -204,14 +382,12 @@ export const HomePage: React.FC = () => { infiniteLoop={true} showIndicators={false} showStatus={false} - onChange={(idx) => setItemDescription(CAROUSEL_ITEMS[idx].label)} - onClickItem={(e) => { - window.location.href = CAROUSEL_ITEMS[e].route; - }} + onChange={handleCarouselChange} + onClickItem={handleCarouselClick} > {CAROUSEL_ITEMS.map((item, idx) => (
- + {item.label}
))} @@ -241,7 +417,6 @@ export const HomePage: React.FC = () => { alt='SDG AI Lab Logo' className='SDGAILogo' /> - CBI Logo
@@ -256,126 +431,12 @@ export const HomePage: React.FC = () => { Add new event )} - {recentDisasters.length > 0 || disasterEvents.length > 0 ? ( - <> -
- {recentDisasters.length > 0 && ( -
-
- - Help Needed - -
- -
- )} - {disasterEvents.length > 0 && ( -
- {disasterEvents.slice(0, 2).map((disasterEvent: any) => ( -
- -
- ))} -
- )} -
-
- - ) : ( -
-

No recent disasters

-
- )} - -
- {projectsToUse.length > 0 ? ( - <> -
- -

Projects

- -
-
- {projectsToUse.map((project: any) => ( -
- -
- ))} -
-
- - ) : ( - - )} + {renderRecentDisastersSection}
-
- {technologies.length > 0 ? ( - <> -
- -

Technologies

- -
-
- {technologies.map((tech: any) => ( -
- -
- ))} -
-
- - ) : ( - - )} -
-
- {disasterTypes.length > 0 ? ( - <> -
- -

Disasters

- -
-
- {disasterTypes.map((disaster: any) => ( -
- -
- ))} -
-
- - ) : ( - - )} -
+ {renderProjectsSection} + {renderTechnologiesSection} + {renderDisasterTypesSection} diff --git a/src/pages/homePage/components/RecentDisasterCardMini.tsx b/src/pages/homePage/components/RecentDisasterCardMini.tsx index cd0a916..046117f 100644 --- a/src/pages/homePage/components/RecentDisasterCardMini.tsx +++ b/src/pages/homePage/components/RecentDisasterCardMini.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { memo, useMemo } from 'react'; import { Link } from 'react-router-dom'; +import PropTypes from 'prop-types'; import './HomeCard.scss'; interface RecentDisasterProps { @@ -14,24 +15,46 @@ interface Props { recentDisaster: RecentDisasterProps; } -export const RecentDisasterCardMini: React.FC = ({ recentDisaster }) => { - return ( -
- -
- Default Image -
-
-
{recentDisaster?.title}
- {recentDisaster?.summary && ( -
- {recentDisaster?.summary.length > 150 - ? recentDisaster?.summary.substring(0, 150).concat('...') - : recentDisaster?.summary} -
- )} -
- -
- ); +export const RecentDisasterCardMini: React.FC = memo( + ({ recentDisaster }) => { + const truncatedSummary = useMemo(() => { + if (!recentDisaster?.summary) return null; + return recentDisaster.summary.length > 150 + ? `${recentDisaster.summary.substring(0, 150)}...` + : recentDisaster.summary; + }, [recentDisaster?.summary]); + + return ( +
+ +
+ {recentDisaster.title +
+
+
{recentDisaster?.title}
+ {truncatedSummary && ( +
{truncatedSummary}
+ )} +
+ +
+ ); + } +); + +RecentDisasterCardMini.displayName = 'RecentDisasterCardMini'; + +// PropTypes for runtime validation (since you're using React with prop-types) +RecentDisasterCardMini.propTypes = { + recentDisaster: PropTypes.shape({ + uuid: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + summary: PropTypes.string.isRequired, + img_url: PropTypes.string.isRequired, + id: PropTypes.number.isRequired + }).isRequired };