refactored summary box to allow for dropdown capability

This commit is contained in:
Khalim Conn-Kowlessar 2024-07-31 10:41:04 +01:00
parent a9d4d8587f
commit 24c76d78b6
2 changed files with 240 additions and 186 deletions

View file

@ -0,0 +1,203 @@
"use client";
import { useState } from "react";
import { convertDaysToWorkingWeeks, formatNumber } from "@/app/utils";
interface SummaryBoxProps {
scenarios: Array<{
id: bigint;
name: string;
budget: number | null;
totalCost: number | null;
co2EquivalentSavings: number | null;
totalWorkHours: number | null;
propertyValuationIncrease: number | null;
energySavings: number | null;
energyCostSavings: number | null;
labourDays: number | null;
isDefault: boolean;
}>;
numProperties: number;
}
function SummaryBox({ scenarios, numProperties }: SummaryBoxProps) {
// Get the default scenario
const defaultScenario =
scenarios.find((scenario) => scenario.isDefault) || scenarios[0];
const [selectedScenarioId, setSelectedScenarioId] = useState(
Number(defaultScenario.id)
);
const [budgetFormatted, setBudgetFormatted] = useState(
formatBudget(defaultScenario.budget)
);
const [totalCostFormatted, setTotalCostFormatted] = useState(
formatMoney(defaultScenario.totalCost)
);
const [totalValueIncreaseFormatted, setTotalValueIncreaseFormatted] =
useState(formatMoney(defaultScenario.propertyValuationIncrease));
const [energyCostSavingsFormatted, setEnergyCostSavingsFormatted] = useState(
formatMoney(defaultScenario.energyCostSavings)
);
const [co2EquivalentSavingsFormatted, setCo2EquivalentSavingsFormatted] =
useState(formatCo2(defaultScenario.co2EquivalentSavings));
const [energySavingsFormatted, setEnergySavingsFormatted] = useState(
formatKwh(defaultScenario.energySavings)
);
const [estimatedDurationFormatted, setEstimatedDurationFormatted] = useState(
convertDaysToWorkingWeeks(defaultScenario.labourDays)
);
const handleScenarioChange = (scenarioId: string) => {
setSelectedScenarioId(Number(scenarioId));
const selectedScenario = scenarios.find(
(scenario) => Number(scenario.id) === Number(scenarioId)
);
if (!selectedScenario) {
return;
}
setBudgetFormatted(formatBudget(selectedScenario.budget));
setTotalCostFormatted(formatMoney(selectedScenario.totalCost));
setTotalValueIncreaseFormatted(
formatMoney(selectedScenario.propertyValuationIncrease)
);
setEnergyCostSavingsFormatted(
formatMoney(selectedScenario.energyCostSavings)
);
setCo2EquivalentSavingsFormatted(
formatCo2(selectedScenario.co2EquivalentSavings)
);
setEnergySavingsFormatted(formatKwh(selectedScenario.energySavings));
setEstimatedDurationFormatted(
convertDaysToWorkingWeeks(selectedScenario.labourDays)
);
};
function formatMoney(amount: number | null) {
return amount === null ? "£0" : "£" + formatNumber(amount);
}
function formatBudget(budget: number | null) {
return budget === null ? "Not set" : formatMoney(budget);
}
function formatHours(hours: number | null) {
return hours === null ? "0 hours" : Math.round(hours) + " hours";
}
function formatCo2(co2: number | null) {
return co2 === null ? "-" : co2.toFixed(1) + " tonnes";
}
function formatKwh(energy: number | null) {
return energy === null ? "-" : formatNumber(energy) + " kWh";
}
return (
<div className="p-6 bg-white rounded-lg leading-relaxed">
<h2 className="text-2xl font-bold mb-4 text-brandblue text-center">
Portfolio Summary
</h2>
<div className="mb-4 text-center">
<label htmlFor="scenario-select" className="mr-2">
Select Scenario:
</label>
<select
id="scenario-select"
value={selectedScenarioId}
onChange={(e) => handleScenarioChange(e.target.value)}
className="p-2 border rounded"
>
{scenarios.map((scenario) => (
<option key={String(scenario.id)} value={String(scenario.id)}>
{scenario.name}
</option>
))}
</select>
</div>
<div className="grid grid-cols-1 md:grid-cols-1 gap-6">
<div className="p-4 bg-gray-50 rounded-lg">
<h3 className="text-lg font-semibold text-brandblue mb-2">
Work Package
</h3>
<table className="w-full">
<tbody>
<tr>
<td className="text-brandblue">Total Budget</td>
<td className="text-brandblue text-end">{budgetFormatted}</td>
</tr>
<tr>
<td className="text-brandblue">Total Cost</td>
<td className="text-brandblue text-end">
{totalCostFormatted}
</td>
</tr>
<tr>
<td className="text-brandblue">Estimated Duration</td>
<td className="text-brandblue text-end">
{estimatedDurationFormatted}
</td>
</tr>
<tr>
<td className="text-brandblue">Total properties</td>
<td className="text-brandblue text-end">{numProperties}</td>
</tr>
</tbody>
</table>
</div>
<div className="p-4 bg-gray-50 rounded-lg">
<h3 className="text-lg font-semibold text-brandblue mb-2">
Environmental Impact
</h3>
<table className="w-full">
<tbody>
<tr>
<td className="text-brandblue">
Annual{" "}
<span>
CO<sub>2</sub>
</span>{" "}
Savings
</td>
<td className="text-brandblue text-end">
{co2EquivalentSavingsFormatted}
</td>
</tr>
<tr>
<td className="text-brandblue">Annual Energy Savings</td>
<td className="text-brandblue text-end">
{energySavingsFormatted}
</td>
</tr>
</tbody>
</table>
</div>
<div className="p-4 bg-gray-50 rounded-lg">
<h3 className="text-lg font-semibold text-brandblue mb-2">
Financial Impact
</h3>
<table className="w-full">
<tbody>
<tr>
<td className="text-brandblue">Annual Energy Bill Reduction</td>
<td className="text-brandblue text-end">
{energyCostSavingsFormatted}
</td>
</tr>
<tr>
<td className="text-brandblue">Total Value Increase</td>
<td className="text-brandblue text-end">
{totalValueIncreaseFormatted}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
);
}
export default SummaryBox;

View file

@ -4,6 +4,8 @@ import DataTable from "@/app/portfolio/[slug]/components/propertyTable";
import { columns } from "@/app/portfolio/[slug]/components/propertyTableColumns";
import { PropertyWithRelations } from "@/app/db/schema/property";
import { formatNumber, convertDaysToWorkingWeeks } from "@/app/utils";
import SummaryBox from "@/app/components/portfolio/SummaryBox";
import { is } from "cypress/types/bluebird";
// We enfore caching of data for 60 seconds
export const revalidate = 60;
@ -22,175 +24,6 @@ function EmptyPropertyState() {
);
}
interface SummaryBoxProps {
budget: number | null;
totalCost: number | null;
numProperties: number;
co2EquivalentSavings: number | null;
totalWorkHours: number | null;
propertyValuationIncrease: number | null;
energySavings: number | null;
energyCostSavings: number | null;
estimatedDuration: number | null;
}
function SummaryBox({
budget,
totalCost,
numProperties,
co2EquivalentSavings,
totalWorkHours,
propertyValuationIncrease,
energySavings,
energyCostSavings,
estimatedDuration,
}: SummaryBoxProps) {
function formatMoney(amount: number | null) {
if (amount === null) {
return "£0";
} else {
return "£" + formatNumber(amount);
}
}
function formatBudget(budget: number | null) {
if (budget === null) {
return "Not set";
} else {
return formatMoney(budget);
}
}
function formatHours(hours: number | null) {
if (hours === null) {
return "0 hours";
} else {
return Math.round(hours) + " hours";
}
}
function formatCo2(co2: number | null) {
if (co2 === null) {
return "-";
} else {
return co2.toFixed(1) + " tonnes";
}
}
function formatKwh(energy: number | null) {
if (energy === null) {
return "-";
} else {
return formatNumber(energy) + " kWh";
}
}
const budgetFormatted = formatBudget(budget);
const totalCostFormatted = formatMoney(totalCost);
const totalValueIncreaseFormatted = formatMoney(propertyValuationIncrease);
// const totalWorkHoursFormatted = formatHours(totalWorkHours);
// const rentalYieldIncreaseFormatted = formatMoney(rentalYieldIncrease);
const energyCostSavingsFormatted = formatMoney(energyCostSavings);
const co2EquivalentSavingsFormatted = formatCo2(co2EquivalentSavings);
const energySavingsFormatted = formatKwh(energySavings);
const estimatedDurationFormatted =
convertDaysToWorkingWeeks(estimatedDuration);
return (
<div className="p-6 bg-white rounded-lg leading-relaxed">
<h2 className="text-2xl font-bold mb-4 text-brandblue text-center">
Portfolio Summary
</h2>
<div className="grid grid-cols-1 md:grid-cols-1 gap-6">
<div className="p-4 bg-gray-50 rounded-lg">
<h3 className="text-lg font-semibold text-brandblue mb-2">
Work Package
</h3>
<table className="w-full">
<tbody>
<tr>
<td className="text-brandblue ">Total Budget</td>
<td className="text-brandblue text-end">{budgetFormatted}</td>
</tr>
<tr>
<td className="text-brandblue ">Total Cost</td>
<td className="text-brandblue text-end">
{totalCostFormatted}
</td>
</tr>
<tr>
<td className="text-brandblue">Estimated Duration</td>
<td className="text-brandblue text-end">
{estimatedDurationFormatted}
</td>
</tr>
<tr>
<td className="text-brandblue">Total properties</td>
<td className="text-brandblue text-end">{numProperties}</td>
</tr>
</tbody>
</table>
</div>
<div className="p-4 bg-gray-50 rounded-lg">
{/* Environmental Impact */}
<h3 className="text-lg font-semibold text-brandblue mb-2">
Environmental Impact
</h3>
<table className="w-full">
<tbody>
<tr>
<td className="text-brandblue">
Annual{" "}
<span>
CO<sub>2</sub>
</span>{" "}
Savings
</td>
<td className="text-brandblue text-end">
{co2EquivalentSavingsFormatted}
</td>
</tr>
<tr>
<td className="text-brandblue">Annual Energy Savings</td>
<td className="text-brandblue text-end">
{energySavingsFormatted}
</td>
</tr>
</tbody>
</table>
</div>
<div className="p-4 bg-gray-50 rounded-lg">
{/* Financial Impact table */}
<h3 className="text-lg font-semibold text-brandblue mb-2">
Financial Impact
</h3>
<table className="w-full">
<tbody>
<tr>
<td className="text-brandblue ">
Annual Energy Bill Reduction
</td>
<td className="text-brandblue text-end">
{energyCostSavingsFormatted}
</td>
</tr>
<tr>
<td className="text-brandblue ">Total Value Increase</td>
<td className="text-brandblue text-end">
{totalValueIncreaseFormatted}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
);
}
export default async function Page({
params,
searchParams,
@ -201,16 +34,43 @@ export default async function Page({
// This page is served from the server so we can make calls to the database
const portfolioId = params.slug;
const portfolio = await getPortfolio(portfolioId);
const portfolioPerformance = await getPortfolioPerformance(portfolioId);
const defaultPerformance = portfolioPerformance.find(
(perf) => perf.isDefault
);
let portfolioPerformance = await getPortfolioPerformance(portfolioId);
let scenarios;
const portfolioData = defaultPerformance || portfolio;
if (portfolioPerformance.length > 0) {
scenarios = portfolioPerformance.map((performance) => ({
id: BigInt(performance.id),
name: performance.name || "Default Scenario",
budget: performance.budget,
totalCost: performance.cost,
co2EquivalentSavings: performance.co2EquivalentSavings,
totalWorkHours: performance.totalWorkHours,
propertyValuationIncrease: performance.propertyValuationIncrease,
energySavings: performance.energySavings,
energyCostSavings: performance.energyCostSavings,
labourDays: performance.labourDays,
isDefault: performance.isDefault,
}));
} else {
const portfolio = await getPortfolio(portfolioId);
scenarios = [
{
id: BigInt(0),
name: "Default",
budget: portfolio.budget,
totalCost: portfolio.cost,
co2EquivalentSavings: portfolio.co2EquivalentSavings,
totalWorkHours: portfolio.totalWorkHours,
propertyValuationIncrease: portfolio.propertyValuationIncrease,
energySavings: portfolio.energySavings,
energyCostSavings: portfolio.energyCostSavings,
labourDays: portfolio.labourDays,
isDefault: true,
},
];
}
// Default limit to 1000 and offset to 0 for now - will handle pagination later
const properties: PropertyWithRelations[] = await getProperties(
portfolioId,
1000,
@ -223,17 +83,8 @@ export default async function Page({
<div className="grid grid-cols-11 w-full max-w-8xl h-screen">
<div className="col-span-3 flex-col">
<SummaryBox
budget={portfolioData.budget}
totalCost={portfolioData.cost}
scenarios={scenarios}
numProperties={properties.length}
co2EquivalentSavings={portfolioData.co2EquivalentSavings}
totalWorkHours={portfolioData.totalWorkHours}
propertyValuationIncrease={
portfolioData.propertyValuationIncrease
}
energySavings={portfolioData.energySavings}
energyCostSavings={portfolioData.energyCostSavings}
estimatedDuration={portfolioData.labourDays}
/>
</div>
<div className="col-span-8 bg-white">