diff --git a/src/app/api/portfolio/summary/[portfolioId]/route.ts b/src/app/api/portfolio/scenario/[scenarioId]/route.ts
similarity index 59%
rename from src/app/api/portfolio/summary/[portfolioId]/route.ts
rename to src/app/api/portfolio/scenario/[scenarioId]/route.ts
index fae683e2..32de0baa 100644
--- a/src/app/api/portfolio/summary/[portfolioId]/route.ts
+++ b/src/app/api/portfolio/scenario/[scenarioId]/route.ts
@@ -1,46 +1,46 @@
-import { portfolio } from "@/app/db/schema/portfolio";
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/app/db/db";
import { z } from "zod";
import { formatNumber } from "@/app/utils";
import { DataItem, ChartData } from "@/app/portfolio/[slug]/utils";
import { eq } from "drizzle-orm";
+import { scenario } from "@/app/db/schema/recommendations";
export async function GET(
request: NextRequest,
- { params }: { params: { portfolioId: string } }
+ { params }: { params: { scenarioId: string } }
) {
- const portfolioId = params.portfolioId;
+ const scenarioId = params.scenarioId;
const data = await db
.select({
- name: portfolio.name,
- cost: portfolio.cost,
- epcBreakdownPostRetrofit: portfolio.epcBreakdownPostRetrofit,
- numberOfProperties: portfolio.numberOfProperties,
- nUnitsToRetrofit: portfolio.nUnitsToRetrofit,
- co2PerUnitPostRetrofit: portfolio.co2PerUnitPostRetrofit,
- energyBillPerUnitPostRetrofit: portfolio.energyBillPerUnitPostRetrofit,
+ name: scenario.name,
+ cost: scenario.cost,
+ epcBreakdownPostRetrofit: scenario.epcBreakdownPostRetrofit,
+ numberOfProperties: scenario.numberOfProperties,
+ nUnitsToRetrofit: scenario.nUnitsToRetrofit,
+ co2PerUnitPostRetrofit: scenario.co2PerUnitPostRetrofit,
+ energyBillPerUnitPostRetrofit: scenario.energyBillPerUnitPostRetrofit,
energyConsumptionPerUnitPostRetrofit:
- portfolio.energyConsumptionPerUnitPostRetrofit,
- valuationImprovementPerUnit: portfolio.valuationImprovementPerUnit,
- costPerUnit: portfolio.costPerUnit,
- costPerCo2Saved: portfolio.costPerCo2Saved,
- costPerSapPoint: portfolio.costPerSapPoint,
- valuationReturnOnInvestment: portfolio.valuationReturnOnInvestment,
+ scenario.energyConsumptionPerUnitPostRetrofit,
+ valuationImprovementPerUnit: scenario.valuationImprovementPerUnit,
+ costPerUnit: scenario.costPerUnit,
+ costPerCo2Saved: scenario.costPerCo2Saved,
+ costPerSapPoint: scenario.costPerSapPoint,
+ valuationReturnOnInvestment: scenario.valuationReturnOnInvestment,
})
- .from(portfolio)
- .where(eq(portfolio.id, BigInt(portfolioId)));
+ .from(scenario)
+ .where(eq(scenario.id, BigInt(scenarioId)));
if (data.length === 0) {
- throw new Error("Portfolio not found");
+ throw new Error("Scenario not found");
}
if (data.length > 1) {
- throw new Error("More than one portfolio found");
+ throw new Error("More than one scenario found");
}
- const portfolioName = data[0].name;
+ const scenarioName = data[0].name || "Default";
// Format the data we need for the overview
const output: DataItem[] = [
@@ -48,7 +48,7 @@ export async function GET(
title: "EPCs",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: JSON.parse(
data[0].epcBreakdownPostRetrofit || "[]"
) as ChartData[],
@@ -59,7 +59,7 @@ export async function GET(
title: "# Units",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: String(data[0].numberOfProperties) || "",
},
],
@@ -68,7 +68,7 @@ export async function GET(
title: "# Units to retrofit",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: String(data[0].nUnitsToRetrofit) || "",
},
],
@@ -77,7 +77,7 @@ export async function GET(
title: "Co2/unit",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: data[0].co2PerUnitPostRetrofit || "",
},
],
@@ -86,7 +86,7 @@ export async function GET(
title: "Annual energy bill/unit",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: data[0].energyBillPerUnitPostRetrofit || "",
},
],
@@ -95,7 +95,7 @@ export async function GET(
title: "Annual Energy consumption (kWh)/unit",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: data[0].energyConsumptionPerUnitPostRetrofit || "",
},
],
@@ -104,7 +104,7 @@ export async function GET(
title: "Cost (£)",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: "£" + formatNumber(data[0].cost || 0),
},
],
@@ -112,19 +112,19 @@ export async function GET(
{
title: "Cost (£)/unit",
scenarios: [
- { scenarioName: portfolioName, data: data[0].costPerUnit || "" },
+ { scenarioName: scenarioName, data: data[0].costPerUnit || "" },
],
},
{
title: "£ per CO2 reduction",
scenarios: [
- { scenarioName: portfolioName, data: data[0].costPerCo2Saved || "" },
+ { scenarioName: scenarioName, data: data[0].costPerCo2Saved || "" },
],
},
{
title: "£ per SAP point",
scenarios: [
- { scenarioName: portfolioName, data: data[0].costPerSapPoint || "" },
+ { scenarioName: scenarioName, data: data[0].costPerSapPoint || "" },
],
},
{
@@ -132,7 +132,7 @@ export async function GET(
"Potential valuation improvement/unit - highly indicative EPC effect*",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: data[0].valuationImprovementPerUnit || "",
},
],
@@ -142,7 +142,7 @@ export async function GET(
"Potential valuation return on investment - highly indicative EPC effect*",
scenarios: [
{
- scenarioName: portfolioName,
+ scenarioName: scenarioName,
data: data[0].valuationReturnOnInvestment || "",
},
],
diff --git a/src/app/components/portfolio/summary/SelectComparisonModal.tsx b/src/app/components/portfolio/summary/SelectComparisonModal.tsx
index 4468d668..cab21995 100644
--- a/src/app/components/portfolio/summary/SelectComparisonModal.tsx
+++ b/src/app/components/portfolio/summary/SelectComparisonModal.tsx
@@ -4,18 +4,18 @@ import { Fragment, useState } from "react";
const SelectComparisonModal = ({
isOpen,
setIsOpen,
- userPortfolios,
+ scenarios,
onAddColumn,
}: {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
- userPortfolios: { name: string; id: bigint }[];
+ scenarios: { name: string | null; id: bigint }[];
onAddColumn: (columnName: string) => void;
}) => {
- const [selectedPortfolio, setSelectedPortfolio] = useState("");
+ const [selectedScenario, setSelectedScenario] = useState("");
const addColumn = () => {
- onAddColumn(selectedPortfolio);
+ onAddColumn(selectedScenario);
setIsOpen(false);
};
@@ -65,15 +65,15 @@ const SelectComparisonModal = ({
diff --git a/src/app/components/portfolio/summary/SummaryTable.tsx b/src/app/components/portfolio/summary/SummaryTable.tsx
index 8272200e..ef7b2e0b 100644
--- a/src/app/components/portfolio/summary/SummaryTable.tsx
+++ b/src/app/components/portfolio/summary/SummaryTable.tsx
@@ -20,12 +20,12 @@ import EpcBarChart from "./EpcBarChart";
import { DataItem, ChartData } from "@/app/portfolio/[slug]/utils";
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query";
-export async function getComparsionOverviewPortfolioData(
- portfolioId: string
+export async function getComparsionOverviewScenarioData(
+ scenarioId: string
): Promise
> {
- const url = process.env.URL || "" + `/api/portfolio/summary/${portfolioId}`;
+ const url = process.env.URL || "" + `/api/portfolio/scenario/${scenarioId}`;
- // This is the client side counterpart to getOverviewPortfolioData
+ // This is the client side counterpart to getOverviewScenarioData
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
@@ -37,21 +37,21 @@ export async function getComparsionOverviewPortfolioData(
return response.json();
}
-interface UseFetchPortfolioSummaryProps {
- portfolioId: string; // Assuming portfolioId can be null to handle cases before it's available
+interface UseFetchScenarioSummaryProps {
+ scenarioId: string; // Assuming scenarioId can be null to handle cases before it's available
onSuccess: (data: DataItem[]) => void;
options?: Omit, "queryKey" | "queryFn">;
}
-export const useFetchPortfolioSummary = ({
- portfolioId,
+export const useFetchScenarioSummary = ({
+ scenarioId,
onSuccess,
-}: UseFetchPortfolioSummaryProps) => {
+}: UseFetchScenarioSummaryProps) => {
return useQuery({
- queryKey: ["portfolioSummary", portfolioId],
- queryFn: () => getComparsionOverviewPortfolioData(portfolioId as string), // Ensure non-null assertion or handle potential null inside the function
+ queryKey: ["scenarioSummary", scenarioId],
+ queryFn: () => getComparsionOverviewScenarioData(scenarioId as string), // Ensure non-null assertion or handle potential null inside the function
onSuccess,
- enabled: portfolioId !== "no-id", // Ensure the query only runs when portfolioId is not null
+ enabled: scenarioId !== "no-id", // Ensure the query only runs when portfolioId is not null
});
};
@@ -64,10 +64,10 @@ const shouldHighlightRow = (title: string) => {
const SummaryTable = ({
data,
- userPortfolios,
+ scenarios,
}: {
data: DataItem[];
- userPortfolios: { name: string; id: bigint }[];
+ scenarios: { name: string | null; id: bigint }[];
}) => {
// TODO: Right now a user can ad multiple comparisons of the same column, we should prevent this
@@ -117,16 +117,15 @@ const SummaryTable = ({
const [isModalOpen, setIsModalOpen] = useState(false);
- const [selectedPortfolioId, setSelectedPortfolioId] =
- useState("no-id");
+ const [selectedScenarioId, setSelectedScenarioId] = useState("no-id");
- // Fetch and handle data when a new portfolio is selected
+ // Fetch and handle data when a new scenario is selected
const {
isLoading,
error,
data: fetchedData,
- } = useFetchPortfolioSummary({
- portfolioId: selectedPortfolioId,
+ } = useFetchScenarioSummary({
+ scenarioId: selectedScenarioId,
onSuccess: (fetchedData: DataItem[]) => {
const updatedData = managedData.map((item) => ({
...item,
@@ -171,15 +170,17 @@ const SummaryTable = ({
setSelectedPortfolioId(id)}
+ scenarios={scenarios}
+ onAddColumn={(id: string) => setSelectedScenarioId(id)}
/>
diff --git a/src/app/portfolio/[slug]/(portfolio)/summary/page.tsx b/src/app/portfolio/[slug]/(portfolio)/summary/page.tsx
index e64a2904..c78a39d7 100644
--- a/src/app/portfolio/[slug]/(portfolio)/summary/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/summary/page.tsx
@@ -1,5 +1,8 @@
import SummaryTable from "@/app/components/portfolio/summary/SummaryTable";
-import { getUserPortfolios, getOverviewPortfolioData } from "../../utils";
+import {
+ getNonDefaultPortfolioScenarios,
+ getOverviewPortfolioData,
+} from "../../utils";
export default async function PortfolioSummary({
params,
@@ -11,14 +14,23 @@ export default async function PortfolioSummary({
// Retrieve all of the names and ids of the portfolios, attributed to this user
// Get user id from the session
- const userPortfolios = await getUserPortfolios(portfolioId);
+ const scenarios = await getNonDefaultPortfolioScenarios(portfolioId);
return (
Summary
-
+
* Valuation improvement is based on these studies from
+
+ Rightmove
+
+ ,
{
// Keep just the columns we need for the overview
- const data = await db
+ // We firstly attempt to retrieve the data from the scenarios table, starting with the default
+ // scenario. If the default scenario doesn't exist, we'll use the data from the
+ // portfolio
+ let data = await db
.select({
- name: portfolio.name,
- cost: portfolio.cost,
- epcBreakdownPreRetrofit: portfolio.epcBreakdownPreRetrofit,
- epcBreakdownPostRetrofit: portfolio.epcBreakdownPostRetrofit,
- numberOfProperties: portfolio.numberOfProperties,
- nUnitsToRetrofit: portfolio.nUnitsToRetrofit,
- co2PerUnitPreRetrofit: portfolio.co2PerUnitPreRetrofit,
- co2PerUnitPostRetrofit: portfolio.co2PerUnitPostRetrofit,
- energyBillPerUnitPreRetrofit: portfolio.energyBillPerUnitPreRetrofit,
- energyBillPerUnitPostRetrofit: portfolio.energyBillPerUnitPostRetrofit,
+ name: scenario.name,
+ cost: scenario.cost,
+ epcBreakdownPreRetrofit: scenario.epcBreakdownPreRetrofit,
+ epcBreakdownPostRetrofit: scenario.epcBreakdownPostRetrofit,
+ numberOfProperties: scenario.numberOfProperties,
+ nUnitsToRetrofit: scenario.nUnitsToRetrofit,
+ co2PerUnitPreRetrofit: scenario.co2PerUnitPreRetrofit,
+ co2PerUnitPostRetrofit: scenario.co2PerUnitPostRetrofit,
+ energyBillPerUnitPreRetrofit: scenario.energyBillPerUnitPreRetrofit,
+ energyBillPerUnitPostRetrofit: scenario.energyBillPerUnitPostRetrofit,
energyConsumptionPerUnitPreRetrofit:
- portfolio.energyConsumptionPerUnitPreRetrofit,
+ scenario.energyConsumptionPerUnitPreRetrofit,
energyConsumptionPerUnitPostRetrofit:
- portfolio.energyConsumptionPerUnitPostRetrofit,
- valuationImprovementPerUnit: portfolio.valuationImprovementPerUnit,
- costPerUnit: portfolio.costPerUnit,
- costPerCo2Saved: portfolio.costPerCo2Saved,
- costPerSapPoint: portfolio.costPerSapPoint,
- valuationReturnOnInvestment: portfolio.valuationReturnOnInvestment,
+ scenario.energyConsumptionPerUnitPostRetrofit,
+ valuationImprovementPerUnit: scenario.valuationImprovementPerUnit,
+ costPerUnit: scenario.costPerUnit,
+ costPerCo2Saved: scenario.costPerCo2Saved,
+ costPerSapPoint: scenario.costPerSapPoint,
+ valuationReturnOnInvestment: scenario.valuationReturnOnInvestment,
})
- .from(portfolio)
- .where(eq(portfolio.id, BigInt(portfolioId)));
+ .from(scenario)
+ .where(
+ and(
+ eq(scenario.portfolioId, BigInt(portfolioId)),
+ eq(scenario.isDefault, true)
+ )
+ );
if (data.length === 0) {
- throw new Error("Portfolio not found");
+ // If we have no data, we fetch it from the scenarios. This will facilitate this for older portfolios
+ data = await db
+ .select({
+ name: portfolio.name,
+ cost: portfolio.cost,
+ epcBreakdownPreRetrofit: portfolio.epcBreakdownPreRetrofit,
+ epcBreakdownPostRetrofit: portfolio.epcBreakdownPostRetrofit,
+ numberOfProperties: portfolio.numberOfProperties,
+ nUnitsToRetrofit: portfolio.nUnitsToRetrofit,
+ co2PerUnitPreRetrofit: portfolio.co2PerUnitPreRetrofit,
+ co2PerUnitPostRetrofit: portfolio.co2PerUnitPostRetrofit,
+ energyBillPerUnitPreRetrofit: portfolio.energyBillPerUnitPreRetrofit,
+ energyBillPerUnitPostRetrofit: portfolio.energyBillPerUnitPostRetrofit,
+ energyConsumptionPerUnitPreRetrofit:
+ portfolio.energyConsumptionPerUnitPreRetrofit,
+ energyConsumptionPerUnitPostRetrofit:
+ portfolio.energyConsumptionPerUnitPostRetrofit,
+ valuationImprovementPerUnit: portfolio.valuationImprovementPerUnit,
+ costPerUnit: portfolio.costPerUnit,
+ costPerCo2Saved: portfolio.costPerCo2Saved,
+ costPerSapPoint: portfolio.costPerSapPoint,
+ valuationReturnOnInvestment: portfolio.valuationReturnOnInvestment,
+ })
+ .from(portfolio)
+ .where(eq(portfolio.id, BigInt(portfolioId)));
+
+ // If we still have no data, we throw and error
+ if (data.length === 0) {
+ throw new Error("Portfolio not found");
+ }
}
if (data.length > 1) {
@@ -118,7 +155,7 @@ export async function getOverviewPortfolioData(
) as ChartData[],
},
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: JSON.parse(
data[0].epcBreakdownPostRetrofit || "[]"
) as ChartData[],
@@ -133,7 +170,7 @@ export async function getOverviewPortfolioData(
data: String(data[0].numberOfProperties) || "",
},
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: String(data[0].numberOfProperties) || "",
},
],
@@ -143,7 +180,7 @@ export async function getOverviewPortfolioData(
scenarios: [
{ scenarioName: "Today", data: "-" },
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: String(data[0].nUnitsToRetrofit) || "",
},
],
@@ -153,7 +190,7 @@ export async function getOverviewPortfolioData(
scenarios: [
{ scenarioName: "Today", data: data[0].co2PerUnitPreRetrofit || "" },
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: data[0].co2PerUnitPostRetrofit || "",
},
],
@@ -166,7 +203,7 @@ export async function getOverviewPortfolioData(
data: data[0].energyBillPerUnitPreRetrofit || "",
},
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: data[0].energyBillPerUnitPostRetrofit || "",
},
],
@@ -179,7 +216,7 @@ export async function getOverviewPortfolioData(
data: data[0].energyConsumptionPerUnitPreRetrofit || "",
},
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: data[0].energyConsumptionPerUnitPostRetrofit || "",
},
],
@@ -189,7 +226,7 @@ export async function getOverviewPortfolioData(
scenarios: [
{ scenarioName: "Today", data: "" },
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: "£" + formatNumber(data[0].cost || 0),
},
],
@@ -198,21 +235,30 @@ export async function getOverviewPortfolioData(
title: "Cost (£)/unit",
scenarios: [
{ scenarioName: "Today", data: "" },
- { scenarioName: portfolioName, data: data[0].costPerUnit || "" },
+ {
+ scenarioName: portfolioName || "Default",
+ data: data[0].costPerUnit || "",
+ },
],
},
{
title: "£ per CO2 reduction",
scenarios: [
{ scenarioName: "Today", data: "" },
- { scenarioName: portfolioName, data: data[0].costPerCo2Saved || "" },
+ {
+ scenarioName: portfolioName || "Default",
+ data: data[0].costPerCo2Saved || "",
+ },
],
},
{
title: "£ per SAP point",
scenarios: [
{ scenarioName: "Today", data: "" },
- { scenarioName: portfolioName, data: data[0].costPerSapPoint || "" },
+ {
+ scenarioName: portfolioName || "Default",
+ data: data[0].costPerSapPoint || "",
+ },
],
},
{
@@ -221,7 +267,7 @@ export async function getOverviewPortfolioData(
scenarios: [
{ scenarioName: "Today", data: "" },
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: data[0].valuationImprovementPerUnit || "",
},
],
@@ -232,7 +278,7 @@ export async function getOverviewPortfolioData(
scenarios: [
{ scenarioName: "Today", data: "" },
{
- scenarioName: portfolioName,
+ scenarioName: portfolioName || "Default",
data: data[0].valuationReturnOnInvestment || "",
},
],
@@ -242,42 +288,25 @@ export async function getOverviewPortfolioData(
return output;
}
-export async function getUserPortfolios(
+export async function getNonDefaultPortfolioScenarios(
portfolioId: string
-): Promise<{ id: bigint; name: string }[]> {
- // This function will retrieve all of the portfolio ids attributed to a user
- // This can be done by querying the portfolioUsers table
+): Promise<{ id: bigint; name: string | null }[]> {
+ // For a given portfolio Id, we want to retrieve all of the scenarios that are attributed to this portfolio
+ // We exclude the default scenario
+ const scenarios = await db
+ .select({
+ id: scenario.id,
+ name: scenario.name,
+ })
+ .from(scenario)
+ .where(
+ and(
+ eq(scenario.portfolioId, BigInt(portfolioId)),
+ eq(scenario.isDefault, false)
+ )
+ );
- const portfolioUser = await db.query.portfolioUsers.findFirst({
- where: eq(portfolioUsers.portfolioId, BigInt(portfolioId)),
- });
-
- if (!portfolioUser) {
- throw new Error("Portfolio not found");
- }
-
- const userId = portfolioUser?.userId;
-
- if (!userId) {
- throw new Error("User not found");
- }
-
- // We now filter portfolioUsers by the userId
- const data = await db.query.portfolioUsers.findMany({
- where: eq(portfolioUsers.userId, userId),
- with: {
- portfolio: {
- // Make sure this matches the relation name defined in relations setup
- columns: {
- id: true,
- name: true,
- },
- },
- },
- });
-
- // Extract the portfolio data from the nested object
- return data.map((item) => item.portfolio);
+ return scenarios;
}
export async function getProperties(
diff --git a/tsconfig.json b/tsconfig.json
index 7f10c944..64681559 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,8 +1,8 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
- // "target": "es5",
- "target": "ESNext",
+ "target": "es5",
+ // "target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,