mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
refactored summary box to allow for dropdown capability
This commit is contained in:
parent
a9d4d8587f
commit
24c76d78b6
2 changed files with 240 additions and 186 deletions
203
src/app/components/portfolio/SummaryBox.tsx
Normal file
203
src/app/components/portfolio/SummaryBox.tsx
Normal 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;
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue