mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Added reactive impact to plan page
This commit is contained in:
parent
8e0720c3f0
commit
ffc14bd630
11 changed files with 1849 additions and 117 deletions
|
|
@ -0,0 +1,93 @@
|
|||
interface EnergyEfficiencyImpactCardProps {
|
||||
currentEpcRating: string;
|
||||
expectedEpcRating: string;
|
||||
currentSapPoints: number;
|
||||
expectedSapPoints: number;
|
||||
totalSapPoints: number;
|
||||
}
|
||||
|
||||
export function EnergyEfficiencyImpactCard({
|
||||
currentEpcRating,
|
||||
expectedEpcRating,
|
||||
currentSapPoints,
|
||||
expectedSapPoints,
|
||||
totalSapPoints,
|
||||
}: EnergyEfficiencyImpactCardProps) {
|
||||
return (
|
||||
<div>
|
||||
<table className="text-left bg-brandblue rounded-md text-gray-100 w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Energy Efficiency Impact</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Current EPC Rating:</td>
|
||||
<td className="font-bold pr-2">
|
||||
{currentSapPoints + " " + currentEpcRating}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Expected EPC Rating:</td>
|
||||
<td className="font-bold pr-2">
|
||||
{Math.floor(expectedSapPoints) + " " + expectedEpcRating}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">
|
||||
Total SAP Points Improvement:
|
||||
</td>
|
||||
<td className="pr-2">
|
||||
{Math.round((totalSapPoints + Number.EPSILON) * 100) / 100}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SecondaryEnergyEfficiencyImpactCardProps {
|
||||
TotalCo2Savings: number;
|
||||
totalEnergyCostSavings: number;
|
||||
totalHeatDemandSavings: number;
|
||||
}
|
||||
|
||||
export function SecondaryEnergyEfficiencyImpactCard({
|
||||
TotalCo2Savings,
|
||||
totalEnergyCostSavings,
|
||||
totalHeatDemandSavings,
|
||||
}: SecondaryEnergyEfficiencyImpactCardProps) {
|
||||
return (
|
||||
<div>
|
||||
<table className="text-left bg-brandblue rounded-md text-gray-100 w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={{ height: "48px" }}></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">
|
||||
CO<sub>2</sub> Reduction
|
||||
</td>
|
||||
<td className=" pr-2">{TotalCo2Savings.toFixed(1)}t</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Energy Savings</td>
|
||||
<td className="pr-2">
|
||||
{totalHeatDemandSavings.toFixed(0) + "kWh"}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Energy Bill Savings</td>
|
||||
<td className="pr-2">{"£" + Math.round(totalEnergyCostSavings)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -35,6 +35,31 @@ const TitleMap = {
|
|||
roof_insulation: "Roof Insulation",
|
||||
};
|
||||
|
||||
type RecommendationCardProps = {
|
||||
componentType: RecommendationType;
|
||||
recommendationData: Recommendation[];
|
||||
setCostMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
costMap: RecommendationMetricMap;
|
||||
setTotalEstimatedCost: Dispatch<SetStateAction<number>>;
|
||||
sapMap: RecommendationMetricMap;
|
||||
setSapMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
setTotalSapPoints: Dispatch<SetStateAction<number>>;
|
||||
currentSapPoints: number;
|
||||
setExpectedEpcRating: Dispatch<SetStateAction<string>>;
|
||||
setTotalLabourDays: Dispatch<SetStateAction<number>>;
|
||||
labourDaysMap: RecommendationMetricMap;
|
||||
setLabourDaysMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
setCo2SavingsMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
co2SavingsMap: RecommendationMetricMap;
|
||||
setTotalCo2Savings: Dispatch<SetStateAction<number>>;
|
||||
setEnergyCostSavingsMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
energyCostSavingsMap: RecommendationMetricMap;
|
||||
setTotalEnergyCostSavings: Dispatch<SetStateAction<number>>;
|
||||
setHeatDemandMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
heatDemandMap: RecommendationMetricMap;
|
||||
setTotalHeatDemandSavings: Dispatch<SetStateAction<number>>;
|
||||
};
|
||||
|
||||
export default function RecommendationCard({
|
||||
componentType,
|
||||
recommendationData,
|
||||
|
|
@ -46,18 +71,19 @@ export default function RecommendationCard({
|
|||
setTotalSapPoints,
|
||||
currentSapPoints,
|
||||
setExpectedEpcRating,
|
||||
}: {
|
||||
componentType: RecommendationType;
|
||||
recommendationData: Recommendation[];
|
||||
setCostMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
costMap: RecommendationMetricMap;
|
||||
setTotalEstimatedCost: Dispatch<SetStateAction<number>>;
|
||||
sapMap: RecommendationMetricMap;
|
||||
setSapMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
setTotalSapPoints: Dispatch<SetStateAction<number>>;
|
||||
currentSapPoints: number;
|
||||
setExpectedEpcRating: Dispatch<SetStateAction<string>>;
|
||||
}) {
|
||||
setTotalLabourDays,
|
||||
labourDaysMap,
|
||||
setLabourDaysMap,
|
||||
setCo2SavingsMap,
|
||||
co2SavingsMap,
|
||||
setTotalCo2Savings,
|
||||
setEnergyCostSavingsMap,
|
||||
energyCostSavingsMap,
|
||||
setTotalEnergyCostSavings,
|
||||
setHeatDemandMap,
|
||||
heatDemandMap,
|
||||
setTotalHeatDemandSavings,
|
||||
}: RecommendationCardProps) {
|
||||
const defaultComponent = recommendationData.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) as Recommendation;
|
||||
|
|
@ -138,6 +164,22 @@ export default function RecommendationCard({
|
|||
setTotalSapPoints={setTotalSapPoints}
|
||||
currentSapPoints={currentSapPoints}
|
||||
setExpectedEpcRating={setExpectedEpcRating}
|
||||
// Labour
|
||||
setTotalLabourDays={setTotalLabourDays}
|
||||
labourDaysMap={labourDaysMap}
|
||||
setLabourDaysMap={setLabourDaysMap}
|
||||
// Co2
|
||||
setCo2SavingsMap={setCo2SavingsMap}
|
||||
co2SavingsMap={co2SavingsMap}
|
||||
setTotalCo2Savings={setTotalCo2Savings}
|
||||
// Energy Cost
|
||||
setEnergyCostSavingsMap={setEnergyCostSavingsMap}
|
||||
energyCostSavingsMap={energyCostSavingsMap}
|
||||
setTotalEnergyCostSavings={setTotalEnergyCostSavings}
|
||||
// Heat Demand
|
||||
setHeatDemandMap={setHeatDemandMap}
|
||||
heatDemandMap={heatDemandMap}
|
||||
setTotalHeatDemandSavings={setTotalHeatDemandSavings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,14 +5,17 @@ import {
|
|||
RecommendationType,
|
||||
} from "@/app/db/schema/recommendations";
|
||||
import RecommendationCard from "./RecommendationCard";
|
||||
import RecommendationCostSummaryCard from "./RecommendationCostSummaryCard";
|
||||
import WorksPackageCard from "./WorksPackageCard";
|
||||
import { Separator } from "@/app/shadcn_components/ui/separator";
|
||||
import { PropertyMeta } from "@/app/db/schema/property";
|
||||
import { sapToEpc } from "@/app/utils";
|
||||
import { useState } from "react";
|
||||
import { sumRecommendationMetricMap } from "@/app/portfolio/[slug]/building-passport/[propertyId]/plans/utils";
|
||||
import { RecommendationMetricMap } from "@/types/recommendations";
|
||||
import RecommendationEpcSummaryCard from "./RecommendationEpcSummaryCard";
|
||||
import {
|
||||
EnergyEfficiencyImpactCard,
|
||||
SecondaryEnergyEfficiencyImpactCard,
|
||||
} from "./EnergyEfficiencyImpactCard";
|
||||
|
||||
interface RecommendationContainerProps {
|
||||
recommendations: Recommendation[];
|
||||
|
|
@ -32,6 +35,15 @@ const typeToCategoryMap: { [key in RecommendationType]?: RecommendationType } =
|
|||
exposed_floor_insulation: "floor_insulation",
|
||||
};
|
||||
|
||||
const emptyImpactState = {
|
||||
estimatedCost: 0,
|
||||
sapPoints: 0,
|
||||
labourDays: 0,
|
||||
co2EquivalentSavings: 0,
|
||||
energyCostSavings: 0,
|
||||
heatDemand: 0,
|
||||
};
|
||||
|
||||
export default function RecommendationContainer({
|
||||
recommendations,
|
||||
propertyMeta,
|
||||
|
|
@ -48,37 +60,35 @@ export default function RecommendationContainer({
|
|||
return acc;
|
||||
}, {} as Record<RecommendationType, (typeof recommendations)[0][]>);
|
||||
|
||||
console.log(categorizedRecommendations);
|
||||
|
||||
const defaultWallsRecommendations =
|
||||
categorizedRecommendations.wall_insulation?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || { estimatedCost: 0, sapPoints: 0 };
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultFloorRecommendations =
|
||||
categorizedRecommendations.floor_insulation?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || { estimatedCost: 0, sapPoints: 0 };
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultRoofRecommendations =
|
||||
categorizedRecommendations.roof_insulation?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || { estimatedCost: 0, sapPoints: 0 };
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultVentiliationRecommendations =
|
||||
categorizedRecommendations.mechanical_ventilation?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || { estimatedCost: 0, sapPoints: 0 };
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultFireplaceRecommendations =
|
||||
categorizedRecommendations.sealing_open_fireplace?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || { estimatedCost: 0, sapPoints: 0 };
|
||||
) || emptyImpactState;
|
||||
|
||||
const defaultLightingRecommendations =
|
||||
categorizedRecommendations.low_energy_lighting?.find(
|
||||
(rec: Recommendation) => rec.default
|
||||
) || { estimatedCost: 0, sapPoints: 0 };
|
||||
) || emptyImpactState;
|
||||
|
||||
const [costMap, setCostMap] = useState<RecommendationMetricMap>({
|
||||
wall_insulation: defaultWallsRecommendations?.estimatedCost || 0,
|
||||
|
|
@ -99,6 +109,49 @@ export default function RecommendationContainer({
|
|||
low_energy_lighting: defaultLightingRecommendations.sapPoints || 0,
|
||||
});
|
||||
|
||||
const [labourDaysMap, setLabourDaysMap] = useState<RecommendationMetricMap>({
|
||||
wall_insulation: defaultWallsRecommendations?.labourDays || 0,
|
||||
floor_insulation: defaultFloorRecommendations.labourDays || 0,
|
||||
roof_insulation: defaultRoofRecommendations.labourDays || 0,
|
||||
mechanical_ventilation: defaultVentiliationRecommendations.labourDays || 0,
|
||||
sealing_open_fireplace: defaultFireplaceRecommendations.labourDays || 0,
|
||||
low_energy_lighting: defaultLightingRecommendations.labourDays || 0,
|
||||
});
|
||||
|
||||
const [co2SavingsMap, setCo2SavingsMap] = useState<RecommendationMetricMap>({
|
||||
wall_insulation: defaultWallsRecommendations?.co2EquivalentSavings || 0,
|
||||
floor_insulation: defaultFloorRecommendations.co2EquivalentSavings || 0,
|
||||
roof_insulation: defaultRoofRecommendations.co2EquivalentSavings || 0,
|
||||
mechanical_ventilation:
|
||||
defaultVentiliationRecommendations.co2EquivalentSavings || 0,
|
||||
sealing_open_fireplace:
|
||||
defaultFireplaceRecommendations.co2EquivalentSavings || 0,
|
||||
low_energy_lighting:
|
||||
defaultLightingRecommendations.co2EquivalentSavings || 0,
|
||||
});
|
||||
|
||||
const [energyCostSavingsMap, setEnergyCostSavingsMap] =
|
||||
useState<RecommendationMetricMap>({
|
||||
wall_insulation: defaultWallsRecommendations?.energyCostSavings || 0,
|
||||
floor_insulation: defaultFloorRecommendations.energyCostSavings || 0,
|
||||
roof_insulation: defaultRoofRecommendations.energyCostSavings || 0,
|
||||
mechanical_ventilation:
|
||||
defaultVentiliationRecommendations.energyCostSavings || 0,
|
||||
sealing_open_fireplace:
|
||||
defaultFireplaceRecommendations.energyCostSavings || 0,
|
||||
low_energy_lighting:
|
||||
defaultLightingRecommendations.energyCostSavings || 0,
|
||||
});
|
||||
|
||||
const [heatDemandMap, setHeatDemandMap] = useState<RecommendationMetricMap>({
|
||||
wall_insulation: defaultWallsRecommendations?.heatDemand || 0,
|
||||
floor_insulation: defaultFloorRecommendations.heatDemand || 0,
|
||||
roof_insulation: defaultRoofRecommendations.heatDemand || 0,
|
||||
mechanical_ventilation: defaultVentiliationRecommendations.heatDemand || 0,
|
||||
sealing_open_fireplace: defaultFireplaceRecommendations.heatDemand || 0,
|
||||
low_energy_lighting: defaultLightingRecommendations.heatDemand || 0,
|
||||
});
|
||||
|
||||
const [totalEstimatedCost, setTotalEstimatedCost] = useState(
|
||||
sumRecommendationMetricMap(costMap)
|
||||
);
|
||||
|
|
@ -107,10 +160,25 @@ export default function RecommendationContainer({
|
|||
sumRecommendationMetricMap(sapMap)
|
||||
);
|
||||
|
||||
const [totalLabourDays, setTotalLabourDays] = useState(
|
||||
sumRecommendationMetricMap(labourDaysMap)
|
||||
);
|
||||
|
||||
const [totalCo2Savings, setTotalCo2Savings] = useState(
|
||||
sumRecommendationMetricMap(co2SavingsMap)
|
||||
);
|
||||
|
||||
const [totalEnergyCostSavings, setTotalEnergyCostSavings] = useState(
|
||||
sumRecommendationMetricMap(energyCostSavingsMap)
|
||||
);
|
||||
|
||||
const [totalHeatDemandSavings, setTotalHeatDemandSavings] = useState(
|
||||
sumRecommendationMetricMap(heatDemandMap)
|
||||
);
|
||||
|
||||
const currentEpcRating = propertyMeta.currentEpcRating;
|
||||
const currentSapPoints = propertyMeta.currentSapPoints;
|
||||
|
||||
//TODO: Use Math.min while we have dummy SAP points
|
||||
const expectedSapPoints = Math.min(currentSapPoints + totalSapPoints, 100);
|
||||
const [expectedEpcRating, setExpectedEpcRating] = useState(
|
||||
sapToEpc(expectedSapPoints)
|
||||
|
|
@ -119,14 +187,23 @@ export default function RecommendationContainer({
|
|||
return (
|
||||
<>
|
||||
<div className="mb-4 flex-col grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 items-stretch">
|
||||
<RecommendationCostSummaryCard
|
||||
<WorksPackageCard
|
||||
totalEstimatedCost={totalEstimatedCost}
|
||||
totalLabourDays={totalLabourDays}
|
||||
/>
|
||||
|
||||
<EnergyEfficiencyImpactCard
|
||||
currentEpcRating={currentEpcRating}
|
||||
expectedEpcRating={expectedEpcRating}
|
||||
currentSapPoints={currentSapPoints}
|
||||
expectedSapPoints={expectedSapPoints}
|
||||
totalSapPoints={totalSapPoints}
|
||||
/>
|
||||
|
||||
<RecommendationEpcSummaryCard
|
||||
currentEpcRating={currentEpcRating}
|
||||
expectedEpcRating={expectedEpcRating}
|
||||
<SecondaryEnergyEfficiencyImpactCard
|
||||
TotalCo2Savings={totalCo2Savings}
|
||||
totalEnergyCostSavings={totalEnergyCostSavings}
|
||||
totalHeatDemandSavings={totalHeatDemandSavings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -141,14 +218,32 @@ export default function RecommendationContainer({
|
|||
// entires means we loose the typing on the key
|
||||
componentType={componentType as RecommendationType}
|
||||
recommendationData={recommendationData}
|
||||
// cost
|
||||
setCostMap={setCostMap}
|
||||
costMap={costMap}
|
||||
setTotalEstimatedCost={setTotalEstimatedCost}
|
||||
// Sap
|
||||
setSapMap={setSapMap}
|
||||
sapMap={sapMap}
|
||||
setTotalSapPoints={setTotalSapPoints}
|
||||
currentSapPoints={currentSapPoints}
|
||||
setExpectedEpcRating={setExpectedEpcRating}
|
||||
// Labour
|
||||
setTotalLabourDays={setTotalLabourDays}
|
||||
labourDaysMap={labourDaysMap}
|
||||
setLabourDaysMap={setLabourDaysMap}
|
||||
// Co2
|
||||
setCo2SavingsMap={setCo2SavingsMap}
|
||||
co2SavingsMap={co2SavingsMap}
|
||||
setTotalCo2Savings={setTotalCo2Savings}
|
||||
// Energy Cost
|
||||
setEnergyCostSavingsMap={setEnergyCostSavingsMap}
|
||||
energyCostSavingsMap={energyCostSavingsMap}
|
||||
setTotalEnergyCostSavings={setTotalEnergyCostSavings}
|
||||
// Heat Demand
|
||||
setHeatDemandMap={setHeatDemandMap}
|
||||
heatDemandMap={heatDemandMap}
|
||||
setTotalHeatDemandSavings={setTotalHeatDemandSavings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
"use client";
|
||||
import { formatNumber } from "@/app/utils";
|
||||
|
||||
export default function RecommendationCostSummaryCard({
|
||||
totalEstimatedCost,
|
||||
totalSapPoints,
|
||||
}: {
|
||||
totalEstimatedCost: number;
|
||||
totalSapPoints: number;
|
||||
}) {
|
||||
return (
|
||||
<table className="text-left bg-brandblue rounded-md text-gray-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Total Cost:</td>
|
||||
<td className="pr-2">{"£" + formatNumber(totalEstimatedCost)}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">
|
||||
Total SAP Points Improvement:
|
||||
</td>
|
||||
<td className="pr-2">
|
||||
{Math.round((totalSapPoints + Number.EPSILON) * 100) / 100}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
interface RecommendationEpcSummaryCardProps {
|
||||
currentEpcRating: string;
|
||||
expectedEpcRating: string;
|
||||
}
|
||||
|
||||
export default function RecommendationEpcSummaryCard({
|
||||
currentEpcRating,
|
||||
expectedEpcRating,
|
||||
}: RecommendationEpcSummaryCardProps) {
|
||||
return (
|
||||
<table className="text-left bg-brandblue rounded-md text-gray-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Current EPC Rating:</td>
|
||||
<td className="font-bold pr-2">{currentEpcRating}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Expected EPC Rating:</td>
|
||||
<td className="font-bold pr-2">{expectedEpcRating}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
|
@ -22,6 +22,18 @@ interface RecommendationModalProps {
|
|||
setTotalSapPoints: Dispatch<SetStateAction<number>>;
|
||||
currentSapPoints: number;
|
||||
setExpectedEpcRating: Dispatch<SetStateAction<string>>;
|
||||
setTotalLabourDays: Dispatch<SetStateAction<number>>;
|
||||
labourDaysMap: RecommendationMetricMap;
|
||||
setLabourDaysMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
setCo2SavingsMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
co2SavingsMap: RecommendationMetricMap;
|
||||
setTotalCo2Savings: Dispatch<SetStateAction<number>>;
|
||||
setEnergyCostSavingsMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
energyCostSavingsMap: RecommendationMetricMap;
|
||||
setTotalEnergyCostSavings: Dispatch<SetStateAction<number>>;
|
||||
setHeatDemandMap: Dispatch<SetStateAction<RecommendationMetricMap>>;
|
||||
heatDemandMap: RecommendationMetricMap;
|
||||
setTotalHeatDemandSavings: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export default function RecommendationModal({
|
||||
|
|
@ -38,6 +50,18 @@ export default function RecommendationModal({
|
|||
setTotalSapPoints,
|
||||
currentSapPoints,
|
||||
setExpectedEpcRating,
|
||||
setTotalLabourDays,
|
||||
labourDaysMap,
|
||||
setLabourDaysMap,
|
||||
setCo2SavingsMap,
|
||||
co2SavingsMap,
|
||||
setTotalCo2Savings,
|
||||
setEnergyCostSavingsMap,
|
||||
energyCostSavingsMap,
|
||||
setTotalEnergyCostSavings,
|
||||
setHeatDemandMap,
|
||||
heatDemandMap,
|
||||
setTotalHeatDemandSavings,
|
||||
}: RecommendationModalProps) {
|
||||
const [saveButtonDisabled, setSaveButtonDisabled] = useState(true);
|
||||
|
||||
|
|
@ -77,8 +101,6 @@ export default function RecommendationModal({
|
|||
// update the cost sum
|
||||
setTotalEstimatedCost(sumRecommendationMetricMap(newCostMap));
|
||||
|
||||
console.log("B4", sapMap);
|
||||
|
||||
// Update the sap map
|
||||
const newSapMap = {
|
||||
...sapMap,
|
||||
|
|
@ -86,11 +108,8 @@ export default function RecommendationModal({
|
|||
};
|
||||
setSapMap(newSapMap);
|
||||
|
||||
console.log("AFTER", newSapMap);
|
||||
|
||||
// update the sap sum
|
||||
const newSapImprovement = sumRecommendationMetricMap(newSapMap);
|
||||
console.log("newSapImprovement", newSapImprovement);
|
||||
setTotalSapPoints(newSapImprovement);
|
||||
|
||||
// TODO: While we have placeholder SAP points, constrain to 100
|
||||
|
|
@ -98,6 +117,52 @@ export default function RecommendationModal({
|
|||
|
||||
// update the expected EPC rating
|
||||
setExpectedEpcRating(sapToEpc(newSapPoints));
|
||||
|
||||
// Update the labour days map
|
||||
const newLabourDaysMap = {
|
||||
...labourDaysMap,
|
||||
[title]: recommendationData[newIndex]?.labourDays || 0,
|
||||
};
|
||||
|
||||
setLabourDaysMap(newLabourDaysMap);
|
||||
|
||||
// update the labour days sum
|
||||
setTotalLabourDays(sumRecommendationMetricMap(newLabourDaysMap));
|
||||
|
||||
// Update the co2 savings map
|
||||
const newCo2SavingsMap = {
|
||||
...co2SavingsMap,
|
||||
[title]: recommendationData[newIndex]?.co2EquivalentSavings || 0,
|
||||
};
|
||||
|
||||
setCo2SavingsMap(newCo2SavingsMap);
|
||||
|
||||
// update the co2 savings sum
|
||||
setTotalCo2Savings(sumRecommendationMetricMap(newCo2SavingsMap));
|
||||
|
||||
// Update the energy cost savings map
|
||||
const newEnergyCostSavingsMap = {
|
||||
...energyCostSavingsMap,
|
||||
[title]: recommendationData[newIndex]?.energyCostSavings || 0,
|
||||
};
|
||||
|
||||
setEnergyCostSavingsMap(newEnergyCostSavingsMap);
|
||||
|
||||
// update the energy cost savings sum
|
||||
setTotalEnergyCostSavings(
|
||||
sumRecommendationMetricMap(newEnergyCostSavingsMap)
|
||||
);
|
||||
|
||||
// Update the heat demand savings map
|
||||
const newHeatDemandMap = {
|
||||
...heatDemandMap,
|
||||
[title]: recommendationData[newIndex]?.heatDemand || 0,
|
||||
};
|
||||
|
||||
setHeatDemandMap(newHeatDemandMap);
|
||||
|
||||
// update the heat demand savings sum
|
||||
setTotalHeatDemandSavings(sumRecommendationMetricMap(newHeatDemandMap));
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
35
src/app/components/building-passport/WorksPackageCard.tsx
Normal file
35
src/app/components/building-passport/WorksPackageCard.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"use client";
|
||||
import { convertDaysToWorkingWeeks, formatNumber } from "@/app/utils";
|
||||
|
||||
export default function WorksPackageCard({
|
||||
totalEstimatedCost,
|
||||
totalLabourDays,
|
||||
}: {
|
||||
totalEstimatedCost: number;
|
||||
totalLabourDays: number;
|
||||
}) {
|
||||
return (
|
||||
<table className="text-left bg-brandblue rounded-md text-gray-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Works Package</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Total Cost:</td>
|
||||
<td className="pr-2">{"£" + formatNumber(totalEstimatedCost)}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Trades required:</td>
|
||||
<td className="pr-2">{"1-2"}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td className="font-medium pl-4 py-2">Estimated Duration:</td>
|
||||
<td className="pr-2">{convertDaysToWorkingWeeks(totalLabourDays)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
11
src/app/db/migrations/0054_sharp_mojo.sql
Normal file
11
src/app/db/migrations/0054_sharp_mojo.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE IF NOT EXISTS "property_details_spatial" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"uprn" bigint,
|
||||
"x_coordinate" real,
|
||||
"y_coordinate" real,
|
||||
"latitude" real,
|
||||
"longitude" real,
|
||||
"conservation_status" boolean,
|
||||
"is_listed_building" boolean,
|
||||
"is_heritage_building" boolean
|
||||
);
|
||||
1442
src/app/db/migrations/meta/0054_snapshot.json
Normal file
1442
src/app/db/migrations/meta/0054_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,7 @@ import { getPortfolio, getProperties } from "../utils";
|
|||
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 } from "@/app/utils";
|
||||
import { formatNumber, convertDaysToWorkingWeeks } from "@/app/utils";
|
||||
|
||||
// We enfore caching of data for 60 seconds
|
||||
export const revalidate = 60;
|
||||
|
|
@ -87,35 +87,6 @@ function SummaryBox({
|
|||
}
|
||||
}
|
||||
|
||||
function convertDaysToWorkingWeeks(days: number | null) {
|
||||
if (days === null) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const workingDaysPerWeek = 5;
|
||||
|
||||
// Convert days to working weeks
|
||||
const workingWeeks = days / workingDaysPerWeek;
|
||||
|
||||
// Determine the range
|
||||
let lowerBound = Math.floor(workingWeeks);
|
||||
let upperBound = Math.ceil(workingWeeks);
|
||||
|
||||
// Adjust if the fraction is very small, you might not count it as a full extra week
|
||||
if (workingWeeks - lowerBound < 0.2) {
|
||||
upperBound = lowerBound;
|
||||
}
|
||||
|
||||
if (lowerBound === 1 && upperBound === 1) {
|
||||
return "1-2 weeks";
|
||||
}
|
||||
|
||||
// Format the output
|
||||
return lowerBound === upperBound
|
||||
? `${lowerBound} weeks`
|
||||
: `${lowerBound}-${upperBound} weeks`;
|
||||
}
|
||||
|
||||
const budgetFormatted = formatBudget(budget);
|
||||
const totalCostFormatted = formatMoney(totalCost);
|
||||
const totalValueIncreaseFormatted = formatMoney(propertyValuationIncrease);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,38 @@
|
|||
import { Rating } from "./db/schema/property";
|
||||
|
||||
export function convertDaysToWorkingWeeks(days: number | null) {
|
||||
if (days === null) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const workingDaysPerWeek = 5;
|
||||
|
||||
// Convert days to working weeks
|
||||
const workingWeeks = days / workingDaysPerWeek;
|
||||
|
||||
// Determine the range
|
||||
let lowerBound = Math.floor(workingWeeks);
|
||||
let upperBound = Math.ceil(workingWeeks);
|
||||
|
||||
// Adjust if the fraction is very small, you might not count it as a full extra week
|
||||
if (workingWeeks - lowerBound < 0.2) {
|
||||
upperBound = lowerBound;
|
||||
}
|
||||
|
||||
if (lowerBound === 0 && upperBound === 1) {
|
||||
return "1 week";
|
||||
}
|
||||
|
||||
if (lowerBound === 1 && upperBound === 1) {
|
||||
return "1-2 weeks";
|
||||
}
|
||||
|
||||
// Format the output
|
||||
return lowerBound === upperBound
|
||||
? `${lowerBound} weeks`
|
||||
: `${lowerBound}-${upperBound} weeks`;
|
||||
}
|
||||
|
||||
export const getEpcColorClass = (letter: string) => {
|
||||
switch (letter.toUpperCase()) {
|
||||
case "A":
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue