Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<DateRange>(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<Experiment[]>(
[],
// Fetch project details
const { data: project } = useSWR<Project>(
`/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<Experiment[]>(
`/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<ExperimentReport[]>(reportUrl, fetcher, {
refreshInterval: REFRESH_INTERVAL_ONE_MINUTE,
});

const [selectedExperimentId, setSelectedExperimentId] =
useState<string>("");
const [selectedRunId, setSelectedRunId] = useState<string>("");

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) => {
Expand All @@ -205,6 +169,18 @@ export default function ProjectPage({
[selectedRunId],
);

if (error) {
console.error("Error fetching project data:", error);
return (
<div className="flex h-full w-full items-center justify-center p-8">
<div className="text-center">
<h2 className="text-lg font-semibold text-red-600">Failed to load data</h2>
<p className="text-sm text-gray-500">Please try refreshing the page or check your connection.</p>
</div>
</div>
);
}

return (
<div className="h-full w-full overflow-auto">
<BreadcrumbHeader
Expand All @@ -220,14 +196,14 @@ export default function ProjectPage({
href: `/${organizationId}/projects`,
},
{
title: `Project ${project.name || ""}`,
title: `Project ${project?.name || ""}`,
href: null,
},
]}
/>
<div className="flex flex-col gap-4 p-4 md:gap-8 md:p-8">
<ProjectDashboard
project={project}
project={project || ({ name: "", description: "" } as Project)}
date={date}
onDateChange={(newDates: DateRange | undefined) =>
setDate(newDates || getDefaultDateRange())
Expand All @@ -248,3 +224,4 @@ export default function ProjectPage({
</div>
);
}