diff --git a/webapp/src/app/(dashboard)/[organizationId]/projects/[projectId]/page.tsx b/webapp/src/app/(dashboard)/[organizationId]/projects/[projectId]/page.tsx index 73303e1dc..0766322cc 100644 --- a/webapp/src/app/(dashboard)/[organizationId]/projects/[projectId]/page.tsx +++ b/webapp/src/app/(dashboard)/[organizationId]/projects/[projectId]/page.tsx @@ -8,16 +8,14 @@ import { getEquivalentTvTime, } from "@/helpers/constants"; import { getDefaultDateRange } from "@/helpers/date-utils"; -import { - getExperiments, - getProjectEmissionsByExperiment, -} from "@/server-functions/experiments"; -import { getOneProject } from "@/server-functions/projects"; +import { fetcher } from "@/helpers/swr"; +import { REFRESH_INTERVAL_ONE_MINUTE } from "@/helpers/time-constants"; import { Experiment } from "@/types/experiment"; import { ExperimentReport } from "@/types/experiment-report"; import { Project } from "@/types/project"; -import { use, useCallback, useEffect, useState } from "react"; +import { use, useCallback, useEffect, useMemo, useState } from "react"; import { DateRange } from "react-day-picker"; +import useSWR, { mutate } from "swr"; export default function ProjectPage({ params, @@ -28,158 +26,124 @@ export default function ProjectPage({ }>; }>) { const { projectId, organizationId } = use(params); - const [isLoading, setIsLoading] = useState(true); - - const [project, setProject] = useState({ - name: "", - description: "", - } as Project); - - // This function now just refreshes the project data instead of navigating - const handleSettingsClick = async () => { - try { - const updatedProject = await getOneProject(projectId); - if (updatedProject) { - setProject(updatedProject); - } - } catch (error) { - console.error("Error refreshing project data:", error); - } - }; - const default_date = getDefaultDateRange(); + const default_date = useMemo(() => getDefaultDateRange(), []); const [date, setDate] = useState(default_date); - const [radialChartData, setRadialChartData] = useState({ - energy: { label: "kWh", value: 0 }, - emissions: { label: "kg eq CO2", value: 0 }, - duration: { label: "days", value: 0 }, - }); - // The experiments of the current project. We need this because experimentReport only contains the experiments that have been run - const [projectExperiments, setProjectExperiments] = useState( - [], + // Fetch project details + const { data: project } = useSWR( + `/projects/${projectId}`, + fetcher, + { + refreshInterval: REFRESH_INTERVAL_ONE_MINUTE, + }, ); - // The reports (if any) of the experiments - const [experimentsReportData, setExperimentsReportData] = useState< - ExperimentReport[] - >([]); - - const [runData, setRunData] = useState({ - experimentId: "", - startDate: default_date.from.toISOString(), - endDate: default_date.to.toISOString(), - }); - const [convertedValues, setConvertedValues] = useState({ - citizen: "0", - transportation: "0", - tvTime: "0", + // Fetch experiments list + const { data: projectExperiments = [] } = useSWR( + `/projects/${projectId}/experiments`, + fetcher, + { + refreshInterval: REFRESH_INTERVAL_ONE_MINUTE, + }, + ); + + // Construct report URL with date filters + const reportUrl = useMemo(() => { + let url = `/projects/${projectId}/experiments/sums`; + if (date?.from || date?.to) { + const params = new URLSearchParams(); + if (date.from) { + params.append("start_date", date.from.toISOString()); + } + if (date.to) { + params.append("end_date", date.to.toISOString()); + } + url += `?${params.toString()}`; + } + return url; + }, [projectId, date]); + + // Fetch emissions report + const { + data: experimentsReportData = [], + isLoading, + error, + } = useSWR(reportUrl, fetcher, { + refreshInterval: REFRESH_INTERVAL_ONE_MINUTE, }); const [selectedExperimentId, setSelectedExperimentId] = useState(""); const [selectedRunId, setSelectedRunId] = useState(""); - const refreshExperimentList = useCallback(async () => { - // Logic to refresh experiments if needed - const experiments: Experiment[] = await getExperiments(projectId); - setProjectExperiments(experiments); - }, [projectId]); - - /** Use effect functions */ + // Initialize selectedExperimentId when data arrives useEffect(() => { - const fetchProjectDetails = async () => { - try { - const project: Project | null = await getOneProject(projectId); - if (!project) { - return; - } - setProject(project); - } catch (error) { - console.error("Error fetching project description:", error); - } - }; - - fetchProjectDetails(); - refreshExperimentList(); - }, [projectId, refreshExperimentList]); - // Fetch the experiment report of the current project - useEffect(() => { - async function fetchData() { - setIsLoading(true); - try { - const report = await getProjectEmissionsByExperiment( - projectId, - date, - ); - - const newRadialChartData = { - energy: { - label: "kWh", - value: parseFloat( - report - .reduce( - (n, { energy_consumed }) => - n + energy_consumed, - 0, - ) - .toFixed(2), - ), - }, - emissions: { - label: "kg eq CO2", - value: parseFloat( - report - .reduce((n, { emissions }) => n + emissions, 0) - .toFixed(2), - ), - }, - duration: { - label: "days", - value: parseFloat( - report - .reduce( - (n, { duration }) => n + duration / 86400, - 0, - ) - .toFixed(2), - ), - }, - }; - setRadialChartData(newRadialChartData); - - setExperimentsReportData(report); - - setRunData({ - experimentId: report[0]?.experiment_id ?? "", - startDate: date?.from?.toISOString() ?? "", - endDate: date?.to?.toISOString() ?? "", - }); - - setSelectedExperimentId(report[0]?.experiment_id ?? ""); - - setConvertedValues({ - citizen: getEquivalentCitizenPercentage( - newRadialChartData.emissions.value, - ).toFixed(2), - transportation: getEquivalentCarKm( - newRadialChartData.emissions.value, - ).toFixed(2), - tvTime: getEquivalentTvTime( - newRadialChartData.energy.value, - ).toFixed(2), - }); - } catch (error) { - console.error("Error fetching project data:", error); - } finally { - setIsLoading(false); - } + if (!selectedExperimentId && experimentsReportData.length > 0) { + setSelectedExperimentId(experimentsReportData[0].experiment_id); } + }, [experimentsReportData, selectedExperimentId]); + + // Derive radial chart data + const radialChartData = useMemo(() => { + return { + energy: { + label: "kWh", + value: parseFloat( + experimentsReportData + .reduce( + (n, { energy_consumed }) => n + (energy_consumed ?? 0), + 0, + ) + .toFixed(2), + ), + }, + emissions: { + label: "kg eq CO2", + value: parseFloat( + experimentsReportData + .reduce((n, { emissions }) => n + (emissions ?? 0), 0) + .toFixed(2), + ), + }, + duration: { + label: "days", + value: parseFloat( + experimentsReportData + .reduce((n, { duration }) => n + (duration ?? 0) / 86400, 0) + .toFixed(2), + ), + }, + }; + }, [experimentsReportData]); + + // Derive converted values + const convertedValues = useMemo(() => { + return { + citizen: getEquivalentCitizenPercentage( + radialChartData.emissions.value, + ).toFixed(2), + transportation: getEquivalentCarKm( + radialChartData.emissions.value, + ).toFixed(2), + tvTime: getEquivalentTvTime(radialChartData.energy.value).toFixed( + 2, + ), + }; + }, [radialChartData]); + + // Derive run data + const runData = useMemo(() => { + return { + experimentId: selectedExperimentId || experimentsReportData[0]?.experiment_id || "", + startDate: date?.from?.toISOString() ?? "", + endDate: date?.to?.toISOString() ?? "", + }; + }, [selectedExperimentId, experimentsReportData, date]); - if (projectId) { - fetchData(); - } - }, [projectId, date]); + const handleSettingsClick = async () => { + mutate(`/projects/${projectId}`); + }; const handleExperimentClick = useCallback( (experimentId: string) => { @@ -205,6 +169,18 @@ export default function ProjectPage({ [selectedRunId], ); + if (error) { + console.error("Error fetching project data:", error); + return ( +
+
+

Failed to load data

+

Please try refreshing the page or check your connection.

+
+
+ ); + } + return (
setDate(newDates || getDefaultDateRange()) @@ -248,3 +224,4 @@ export default function ProjectPage({
); } +