mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
Setting up plans page
This commit is contained in:
parent
c64969bd07
commit
3b9602b293
8 changed files with 30 additions and 233 deletions
|
|
@ -25,7 +25,7 @@ export default function EpcCard({
|
|||
return (
|
||||
<div
|
||||
className={
|
||||
"flex flex-col items-center p-8 shadow rounded-md max-w-xl justify-start text-gray-100 " +
|
||||
"flex flex-col items-center p-4 shadow rounded-md max-w-xl justify-start text-gray-100 " +
|
||||
bgStyling
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ 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]/recommendations/utils";
|
||||
import { sumRecommendationMetricMap } from "@/app/portfolio/[slug]/building-passport/[propertyId]/plans/utils";
|
||||
import { RecommendationMetricMap } from "@/types/recommendations";
|
||||
import RecommendationEpcSummaryCard from "./RecommendationEpcSummaryCard";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Dispatch, Fragment, SetStateAction, useState } from "react";
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import RecommendationTable from "@/app/components/building-passport/RecommendationTable";
|
||||
import { RecommendationMetricMap } from "@/types/recommendations";
|
||||
import { sumRecommendationMetricMap } from "@/app/portfolio/[slug]/building-passport/[propertyId]/recommendations/utils";
|
||||
import { sumRecommendationMetricMap } from "@/app/portfolio/[slug]/building-passport/[propertyId]/plans/utils";
|
||||
import uvalueColumns from "./RecommendationTableColumns";
|
||||
import { sapToEpc } from "@/app/utils";
|
||||
|
||||
|
|
|
|||
|
|
@ -46,10 +46,10 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
|||
const recommendationsButton = (
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/recommendations`}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/plans`}
|
||||
>
|
||||
<WrenchScrewdriverIcon className="h-4 w-4 mr-2" />
|
||||
Retrofit Recommendations
|
||||
Retrofit Plans
|
||||
</NavigationMenuLink>
|
||||
);
|
||||
|
||||
|
|
@ -66,13 +66,13 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) {
|
|||
<NavigationMenuList>
|
||||
{preAssessmentReportButton}
|
||||
{recommendationsButton}
|
||||
<NavigationMenuLink
|
||||
{/* <NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/plan-optimiser`}
|
||||
>
|
||||
<LightBulbIcon className="h-4 w-4 mr-2" />
|
||||
Plan optimiser
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuLink> */}
|
||||
<NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
onClick={handleClickSettings}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
function AddressCard({ address }: { address: string | null }) {
|
||||
// In the future, we might want to use react-wrap-balancer for some of this text
|
||||
return (
|
||||
<div className="flex flex-col items-center p-8 shadow rounded-md max-w-xl mx-auto justify-start text-gray-100 bg-brandblue">
|
||||
<div className="flex flex-col items-center p-4 shadow rounded-md max-w-xl mx-auto justify-start text-gray-100 bg-brandblue">
|
||||
<div className="text-2xl font-bold max-w-l">{address}</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -35,6 +35,9 @@ interface PropertyDetailsCardProps {
|
|||
};
|
||||
}
|
||||
|
||||
const rowTitleStyle = "text-gray-100 align-top pb-3";
|
||||
const rowValueStyle = "text-gray-100 text-end pr-8 pt-1 align-top pb-3";
|
||||
|
||||
function PropertyDetailsCard({
|
||||
conditionReportData,
|
||||
propertyMeta,
|
||||
|
|
@ -45,33 +48,29 @@ function PropertyDetailsCard({
|
|||
.join(" ");
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col items-center p-5 shadow rounded-md justify-start text-gray-100 bg-brandblue">
|
||||
<div className="grid grid-cols-2 gap-8 text-m w-full h-full">
|
||||
<div className="w-full flex flex-col items-center p-4 shadow rounded-md justify-start text-gray-100 bg-brandblue">
|
||||
<div className="grid grid-cols-2 gap-8 text-m w-full h-full text-sm">
|
||||
<div className="border-r">
|
||||
<table className="w-full">
|
||||
<table className="w-full ">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Year built:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
{propertyMeta.yearBuilt}
|
||||
</td>
|
||||
<td className={rowTitleStyle}>Year built:</td>
|
||||
<td className={rowValueStyle}>{propertyMeta.yearBuilt}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Property Type:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
{propertyText}
|
||||
</td>
|
||||
<td className={rowTitleStyle}>Property Type:</td>
|
||||
<td className={rowValueStyle}>{propertyText}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Total floor area:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
<td className={rowTitleStyle}>Total floor area:</td>
|
||||
<td className={rowValueStyle}>
|
||||
{`${conditionReportData.totalFloorArea} m`}
|
||||
<sup>2</sup>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">In conservation area:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
<td className={rowTitleStyle}>In conservation area:</td>
|
||||
<td className={rowValueStyle}>
|
||||
{propertyDetailsSpatial.inConservationArea}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -81,26 +80,20 @@ function PropertyDetailsCard({
|
|||
<table className="w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Local Authority:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
{propertyMeta.localAuthority}
|
||||
</td>
|
||||
<td className={rowTitleStyle}>Local Authority:</td>
|
||||
<td className={rowValueStyle}>{propertyMeta.localAuthority}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Constituency:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
{propertyMeta.constituency}
|
||||
</td>
|
||||
<td className={rowTitleStyle}>Constituency:</td>
|
||||
<td className={rowValueStyle}>{propertyMeta.constituency}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Tenure</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
{propertyMeta.tenure}
|
||||
</td>
|
||||
<td className={rowTitleStyle}>Tenure</td>
|
||||
<td className={rowValueStyle}>{propertyMeta.tenure}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Number of rooms:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
<td className={rowTitleStyle}>Number of rooms:</td>
|
||||
<td className={rowValueStyle}>
|
||||
{propertyMeta.numberOfRooms || "unkown"}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -1,182 +0,0 @@
|
|||
import { Recommendation } from "@/app/db/schema/recommendations";
|
||||
import { PropertyMeta } from "@/app/db/schema/property";
|
||||
import RecommendationContainer from "@/app/components/building-passport/RecommendationContainer";
|
||||
import { getPlans, getPropertyMeta } from "../utils";
|
||||
import { formatDateTime, formatNumber, sapToEpc } from "@/app/utils";
|
||||
import EpcCard from "@/app/components/building-passport/EpcCard";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/app/shadcn_components/ui/card";
|
||||
import { Button } from "@/app/shadcn_components/ui/button";
|
||||
|
||||
function PlanCard({
|
||||
expectedEpcRating,
|
||||
createdAt,
|
||||
totalEstimatedCost,
|
||||
totalSapPoints,
|
||||
}: {
|
||||
expectedEpcRating: string;
|
||||
createdAt: Date;
|
||||
totalEstimatedCost: number;
|
||||
totalSapPoints: number;
|
||||
}) {
|
||||
return (
|
||||
<Card className="flex items-start">
|
||||
<div className="flex-none w-1/5">
|
||||
<EpcCard
|
||||
epcRating={expectedEpcRating}
|
||||
fullMargin={true}
|
||||
expected={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-grow pl-4 flex flex-col justify-between">
|
||||
<CardHeader className="flex justify-end items-center">
|
||||
<CardTitle>
|
||||
<Button className="bg-brandblue hover:bg-hoverblue">
|
||||
Go to plan
|
||||
<ChevronRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardDescription>Created: {formatDateTime(createdAt)}</CardDescription>
|
||||
<CardContent>
|
||||
<div className="flex justify-between mb-2">
|
||||
<span>Total cost:</span>
|
||||
<span>£{formatNumber(totalEstimatedCost)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Total SAP points:</span>
|
||||
<span>{totalSapPoints}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function Recommendations({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string; propertyId: string };
|
||||
}) {
|
||||
const propertyMeta = await getPropertyMeta(params.propertyId);
|
||||
const plans = await getPlans(params.propertyId);
|
||||
console.log("PLANS", plans);
|
||||
console.log(plans[0].planRecommendations);
|
||||
|
||||
return (
|
||||
<div className="leading-loose tracking-wider">
|
||||
<div className="flex py-8 text-lg">Retrofit Plans</div>
|
||||
|
||||
<div className="max-w-3xl">
|
||||
{plans.map((plan, index) => {
|
||||
const totalEstimatedCost = plan.planRecommendations.reduce(
|
||||
(acc, rec) => acc + rec.recommendation.estimatedCost,
|
||||
0
|
||||
);
|
||||
const totalSapPoints = plan.planRecommendations.reduce(
|
||||
(acc, rec) => acc + rec.recommendation.sapPoints,
|
||||
0
|
||||
);
|
||||
|
||||
// Placeholder while we return 999 for all sap points
|
||||
const expectedSapPoints = Math.min(
|
||||
propertyMeta.currentSapPoints + totalSapPoints,
|
||||
100
|
||||
);
|
||||
|
||||
const expectedEpcRating = sapToEpc(expectedSapPoints);
|
||||
|
||||
return (
|
||||
<PlanCard
|
||||
key={index}
|
||||
expectedEpcRating={expectedEpcRating}
|
||||
createdAt={plan.createdAt}
|
||||
totalEstimatedCost={totalEstimatedCost}
|
||||
totalSapPoints={totalSapPoints}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// const recommendations: Recommendation = {
|
||||
// Walls: [
|
||||
// {
|
||||
// id: 1,
|
||||
// type: "internal_wall_insulation",
|
||||
// description: "140mm Mineral Wool internal wall insulation",
|
||||
// estimatedCost: 9_450,
|
||||
// default: true,
|
||||
// newUValue: 0.29,
|
||||
// sapPoints: 4,
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// type: "internal_wall_insulation",
|
||||
// description: "30mm Vacuum Insulation Panels wall insulation",
|
||||
// estimatedCost: 10_135,
|
||||
// default: false,
|
||||
// newUValue: 0.28,
|
||||
// sapPoints: 12,
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// type: "internal_external_wall_insulation",
|
||||
// description:
|
||||
// "80mm Mineral Wool External Wall Insulation and 30mm rigid insulation internal wall insulation",
|
||||
// estimatedCost: 13_450,
|
||||
// default: false,
|
||||
// newUValue: 0.25,
|
||||
// sapPoints: 14,
|
||||
// },
|
||||
// ],
|
||||
// Ventilation: [
|
||||
// {
|
||||
// id: 4,
|
||||
// type: "mechanical_ventilation",
|
||||
// description: "Two decentralised mechanical ventilation units",
|
||||
// estimatedCost: 750,
|
||||
// default: true,
|
||||
// sapPoints: -2,
|
||||
// },
|
||||
// ],
|
||||
// Floor: [
|
||||
// {
|
||||
// id: 5,
|
||||
// type: "suspended_floor_insulation",
|
||||
// description: "70mm Rigid insulation foam boards with floor screed",
|
||||
// estimatedCost: 3_450,
|
||||
// default: true,
|
||||
// newUValue: 0.24,
|
||||
// sapPoints: 7,
|
||||
// },
|
||||
// {
|
||||
// id: 5,
|
||||
// type: "suspended_floor_insulation",
|
||||
// description: "90mm Rigid insulation foam boards with floor screed",
|
||||
// estimatedCost: 4_120,
|
||||
// default: true,
|
||||
// newUValue: 0.24,
|
||||
// sapPoints: 7,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <div className="leading-loose tracking-wider">
|
||||
// <div className="flex py-8 text-lg">Recommendations</div>
|
||||
// <RecommendationContainer
|
||||
// recommendations={recommendations}
|
||||
// propertyMeta={propertyMeta}
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { RecommendationMetricMap } from "@/types/recommendations";
|
||||
|
||||
export function sumRecommendationMetricMap(
|
||||
obj: RecommendationMetricMap
|
||||
): number {
|
||||
// In the recommendations section of the building passport we have the cost map which
|
||||
// contains the costs of the recommendations. We need to sum these costs to display
|
||||
// the total cost of the recommendations
|
||||
return Object.values(obj).reduce((sum, current) => sum + current, 0);
|
||||
}
|
||||
|
|
@ -12,10 +12,6 @@ import { plan, Plan } from "@/app/db/schema/recommendations";
|
|||
import { getRating } from "@/app/utils";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
// type PlanRelation = Plan & {
|
||||
// planRecommendations: PlanRecommendations;
|
||||
// };
|
||||
|
||||
type PlanRelation = Plan & {
|
||||
planRecommendations: {
|
||||
recommendation: { estimatedCost: number; sapPoints: number };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue