mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
added filtering + inspections to front end
This commit is contained in:
parent
95c2d8f3d7
commit
b6353cb2d2
11 changed files with 4557 additions and 84 deletions
|
|
@ -8,11 +8,13 @@ import {
|
|||
HoverCardTrigger,
|
||||
} from "@/app/shadcn_components/ui/hover-card";
|
||||
|
||||
type ExtendedStatus = (typeof PortfolioStatus)[number] | "ECO4" | "GBIS";
|
||||
|
||||
export default function StatusBadge({
|
||||
status,
|
||||
isProperty = false,
|
||||
}: {
|
||||
status: (typeof PortfolioStatus)[number];
|
||||
status: ExtendedStatus;
|
||||
isProperty?: boolean;
|
||||
}) {
|
||||
const statusConfig = statusColor[status];
|
||||
|
|
@ -45,7 +47,7 @@ export default function StatusBadge({
|
|||
}
|
||||
|
||||
const statusColor: {
|
||||
[key in (typeof PortfolioStatus)[number]]: {
|
||||
[key in ExtendedStatus]: {
|
||||
class: string;
|
||||
text: string;
|
||||
hoverText: string;
|
||||
|
|
@ -59,8 +61,9 @@ const statusColor: {
|
|||
propertyHoverText: "This property is currently in scoping",
|
||||
},
|
||||
assessment: {
|
||||
class: "bg-emerald-400 hover:bg-emerald-500 truncate text-overflow: ellipsis",
|
||||
text: "Non-invasive Assessment",
|
||||
class:
|
||||
"bg-emerald-400 hover:bg-emerald-500 truncate text-overflow: ellipsis",
|
||||
text: "Remote Assessment",
|
||||
hoverText: "This portfolio is currently in the assessment stage",
|
||||
propertyHoverText: "This property is currently in the assessment stage",
|
||||
},
|
||||
|
|
@ -114,4 +117,16 @@ const statusColor: {
|
|||
hoverText: "The works in this portfolio has been completed and need review",
|
||||
propertyHoverText: "The works on this property have been completed",
|
||||
},
|
||||
ECO4: {
|
||||
class: "bg-brandblue hover:bg-hoverblue",
|
||||
text: "ECO4",
|
||||
hoverText: "This property is funded under the ECO4 scheme",
|
||||
propertyHoverText: "This property is funded under the ECO4 scheme",
|
||||
},
|
||||
GBIS: {
|
||||
class: "bg-brandmidblue hover:bg-hoverblue",
|
||||
text: "GBIS",
|
||||
hoverText: "This property is funded under the GBIS scheme",
|
||||
propertyHoverText: "This property is funded under the GBIS scheme",
|
||||
},
|
||||
};
|
||||
|
|
|
|||
35
src/app/db/migrations/0122_yielding_morlocks.sql
Normal file
35
src/app/db/migrations/0122_yielding_morlocks.sql
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
CREATE TYPE "public"."inspection_archetype_2" AS ENUM('detached', 'mid-terrace', 'enclosed mid-terrace', 'end-terrace', 'enclosed end-terrace', 'semi-detached');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspection_archetype" AS ENUM('Bungalow', 'Flat', 'Maisonette', 'House', 'non-domestic');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspection_borescoped" AS ENUM('yes', 'no', 'refused');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspections_access_issues" AS ENUM('see notes', 'damp issues', 'foliage on walls', 'bushes against wall', 'trees around/anove property', 'high rise block flats/maisonettes', 'conservatory', 'lean-to', 'garage', 'extension', 'decking', 'shed against wall');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspections_cladding" AS ENUM('none', 'cladded with “sufficient space to fill the wall”', 'cladded with “insufficient space to fill the wall”');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspections_insulation_material" AS ENUM('empty 50-90', 'empty 100+', 'empty 30-40', 'empty less than 30', 'loose fibre/wool', 'eps/celo/king', 'fibre batts - with cavity', 'fibre batts - no cavity', 'loose bead', 'glued bead', 'formaldehyde', 'bubble wrap', 'poly chunks');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspections_rendered" AS ENUM('no render', 'rendered with “insufficient” space between dpc and render', 'rendered with “sufficient” space between dpc and render');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspections_roof_orientation" AS ENUM('north', 'east', 'south', 'west', 'north-east', 'north-west', 'south-east', 'south-west', 'n/s split', 'e/w split', 'ne/sw split', 'nw/se split', 'flat roof', 'no roof', 'roof too small', 'already has solar pv');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspections_tile_hung" AS ENUM('yes', 'no', 'first floor flats are tile hung');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspections_wall_construction" AS ENUM('cavity', 'solid', 'system built', 'timber framed', 'steel framed', 're-walled cavity', 'mansard pre-fab', 'mansard ewi', 'mansard re-walled');--> statement-breakpoint
|
||||
CREATE TYPE "public"."inspections_wall_insulation" AS ENUM('empty cavity', 'filled at build', 'partial', 'retro drilled', 'ewi', 'iwi', 'solid non-cavity', 'system built', 'timber framed', 'steel framed');--> statement-breakpoint
|
||||
CREATE TYPE "public"."plan_type" AS ENUM('solar_eco4', 'solar_hhrsh_eco4', 'empty_cavity_eco', 'partial_cavity_eco', 'extraction_eco');--> statement-breakpoint
|
||||
CREATE TABLE "inspections" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"property_id" bigint NOT NULL,
|
||||
"archetype" "inspection_archetype",
|
||||
"archetype_2" "inspection_archetype_2",
|
||||
"wall_construction" "inspections_wall_construction",
|
||||
"insulation" "inspections_wall_insulation",
|
||||
"insulation_material" "inspections_insulation_material",
|
||||
"borescoped" "inspection_borescoped",
|
||||
"roof_orientation" "inspections_roof_orientation",
|
||||
"tile_hung" "inspections_tile_hung",
|
||||
"rendered" "inspections_rendered",
|
||||
"cladding" "inspections_cladding",
|
||||
"access_issues" "inspections_access_issues",
|
||||
"notes" text,
|
||||
"created_at" timestamp NOT NULL,
|
||||
"surveyor_name" text,
|
||||
"uploaded_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "property" ADD COLUMN "landlord_property_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "plan" ADD COLUMN "plan_type" "plan_type";--> statement-breakpoint
|
||||
ALTER TABLE "inspections" ADD CONSTRAINT "inspections_property_id_property_id_fk" FOREIGN KEY ("property_id") REFERENCES "public"."property"("id") ON DELETE no action ON UPDATE no action;
|
||||
4189
src/app/db/migrations/meta/0122_snapshot.json
Normal file
4189
src/app/db/migrations/meta/0122_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -855,6 +855,13 @@
|
|||
"when": 1761218186670,
|
||||
"tag": "0121_chunky_tony_stark",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 122,
|
||||
"version": "7",
|
||||
"when": 1761587998488,
|
||||
"tag": "0122_yielding_morlocks",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
190
src/app/db/schema/inspections.ts
Normal file
190
src/app/db/schema/inspections.ts
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
import {
|
||||
bigserial,
|
||||
text,
|
||||
timestamp,
|
||||
pgTable,
|
||||
pgEnum,
|
||||
bigint,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { property } from "./property";
|
||||
|
||||
const inspection_archetypes: [string, ...string[]] = [
|
||||
"Bungalow",
|
||||
"Flat",
|
||||
"Maisonette",
|
||||
"House",
|
||||
"non-domestic",
|
||||
];
|
||||
export const inspectionArchetypeEnum = pgEnum(
|
||||
"inspection_archetype",
|
||||
inspection_archetypes
|
||||
);
|
||||
|
||||
const inspection_archetypes_2: [string, ...string[]] = [
|
||||
"detached",
|
||||
"mid-terrace",
|
||||
"enclosed mid-terrace",
|
||||
"end-terrace",
|
||||
"enclosed end-terrace",
|
||||
"semi-detached",
|
||||
];
|
||||
export const inspectionArchetype2Enum = pgEnum(
|
||||
"inspection_archetype_2",
|
||||
inspection_archetypes_2
|
||||
);
|
||||
|
||||
const inspections_wall_constructions: [string, ...string[]] = [
|
||||
"cavity",
|
||||
"solid",
|
||||
"system built",
|
||||
"timber framed",
|
||||
"steel framed",
|
||||
"re-walled cavity",
|
||||
"mansard pre-fab",
|
||||
"mansard ewi",
|
||||
"mansard re-walled",
|
||||
];
|
||||
export const inspectionsWallConstructionEnum = pgEnum(
|
||||
"inspections_wall_construction",
|
||||
inspections_wall_constructions
|
||||
);
|
||||
|
||||
const inspections_wall_insulation: [string, ...string[]] = [
|
||||
"empty cavity",
|
||||
"filled at build",
|
||||
"partial",
|
||||
"retro drilled",
|
||||
"ewi",
|
||||
"iwi",
|
||||
"solid non-cavity",
|
||||
"system built",
|
||||
"timber framed",
|
||||
"steel framed",
|
||||
];
|
||||
export const inspectionsWallInsulationEnum = pgEnum(
|
||||
"inspections_wall_insulation",
|
||||
inspections_wall_insulation
|
||||
);
|
||||
|
||||
const inspectionsInsulationMaterial: [string, ...string[]] = [
|
||||
"empty 50-90",
|
||||
"empty 100+",
|
||||
"empty 30-40",
|
||||
"empty less than 30",
|
||||
"loose fibre/wool",
|
||||
"eps/celo/king",
|
||||
"fibre batts - with cavity",
|
||||
"fibre batts - no cavity",
|
||||
"loose bead",
|
||||
"glued bead",
|
||||
"formaldehyde",
|
||||
"bubble wrap",
|
||||
"poly chunks",
|
||||
];
|
||||
export const inspectionsInsulationMaterialEnum = pgEnum(
|
||||
"inspections_insulation_material",
|
||||
inspectionsInsulationMaterial
|
||||
);
|
||||
|
||||
const inspectionBorescoped: [string, ...string[]] = ["yes", "no", "refused"];
|
||||
export const inspectionBorescopedEnum = pgEnum(
|
||||
"inspection_borescoped",
|
||||
inspectionBorescoped
|
||||
);
|
||||
|
||||
const inspectionsRoofOrientations: [string, ...string[]] = [
|
||||
"north",
|
||||
"east",
|
||||
"south",
|
||||
"west",
|
||||
"north-east",
|
||||
"north-west",
|
||||
"south-east",
|
||||
"south-west",
|
||||
"n/s split",
|
||||
"e/w split",
|
||||
"ne/sw split",
|
||||
"nw/se split",
|
||||
"flat roof",
|
||||
"no roof",
|
||||
"roof too small",
|
||||
"already has solar pv",
|
||||
];
|
||||
export const inspectionsRoofOrientationEnum = pgEnum(
|
||||
"inspections_roof_orientation",
|
||||
inspectionsRoofOrientations
|
||||
);
|
||||
|
||||
const inspectionsTileHung: [string, ...string[]] = [
|
||||
"yes",
|
||||
"no",
|
||||
"first floor flats are tile hung",
|
||||
];
|
||||
export const inspectionsTileHungEnum = pgEnum(
|
||||
"inspections_tile_hung",
|
||||
inspectionsTileHung
|
||||
);
|
||||
|
||||
const renderedOptions: [string, ...string[]] = [
|
||||
"no render",
|
||||
"rendered with “insufficient” space between dpc and render",
|
||||
"rendered with “sufficient” space between dpc and render",
|
||||
];
|
||||
export const inspectionsRenderedEnum = pgEnum(
|
||||
"inspections_rendered",
|
||||
renderedOptions
|
||||
);
|
||||
|
||||
const claddingOptions: [string, ...string[]] = [
|
||||
"none",
|
||||
"cladded with “sufficient space to fill the wall”",
|
||||
"cladded with “insufficient space to fill the wall”",
|
||||
];
|
||||
|
||||
export const inspectionsCladdingEnum = pgEnum(
|
||||
"inspections_cladding",
|
||||
claddingOptions
|
||||
);
|
||||
|
||||
const access_issuesOptions: [string, ...string[]] = [
|
||||
"see notes",
|
||||
"damp issues",
|
||||
"foliage on walls",
|
||||
"bushes against wall",
|
||||
"trees around/anove property",
|
||||
"high rise block flats/maisonettes",
|
||||
"conservatory",
|
||||
"lean-to",
|
||||
"garage",
|
||||
"extension",
|
||||
"decking",
|
||||
"shed against wall",
|
||||
];
|
||||
|
||||
export const inspectionsAccessIssuesEnum = pgEnum(
|
||||
"inspections_access_issues",
|
||||
access_issuesOptions
|
||||
);
|
||||
|
||||
export const inspections = pgTable("inspections", {
|
||||
id: bigserial("id", { mode: "bigint" }).primaryKey(),
|
||||
propertyId: bigint("property_id", { mode: "bigint" })
|
||||
.notNull()
|
||||
.references(() => property.id),
|
||||
// TODO Revise
|
||||
archetype: inspectionArchetypeEnum("archetype"),
|
||||
archetype2: inspectionArchetype2Enum("archetype_2"),
|
||||
wallConstruction: inspectionsWallConstructionEnum("wall_construction"),
|
||||
insulation: inspectionsWallInsulationEnum("insulation"),
|
||||
insulationMaterial: inspectionsInsulationMaterialEnum("insulation_material"),
|
||||
borescoped: inspectionBorescopedEnum("borescoped"),
|
||||
roofOrientation: inspectionsRoofOrientationEnum("roof_orientation"),
|
||||
tileHung: inspectionsTileHungEnum("tile_hung"),
|
||||
rendered: inspectionsRenderedEnum("rendered"),
|
||||
cladding: inspectionsCladdingEnum("cladding"),
|
||||
access_issues: inspectionsAccessIssuesEnum("access_issues"),
|
||||
notes: text("notes"),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
surveyorName: text("surveyor_name"),
|
||||
uploadedAt: timestamp("uploaded_at").defaultNow().notNull(),
|
||||
});
|
||||
|
|
@ -97,6 +97,7 @@ export const property = pgTable("property", {
|
|||
.references(() => portfolio.id),
|
||||
creationStatus: propertyCreationStatusEnum("creation_status").notNull(),
|
||||
uprn: bigint("uprn", { mode: "bigint" }),
|
||||
landlordPropertyId: text("landlord_property_id"), // Optional ID used by landlords
|
||||
buildingReferenceNumber: bigint("building_reference_number", {
|
||||
mode: "bigint",
|
||||
}),
|
||||
|
|
@ -268,6 +269,13 @@ export interface PropertyWithRelations {
|
|||
cost?: number | null;
|
||||
currentEpcRating: string | null;
|
||||
currentSapPoints: number | null;
|
||||
plans: {
|
||||
id: bigint;
|
||||
isDefault?: boolean;
|
||||
fundingPackage?: {
|
||||
scheme: string | null;
|
||||
} | null;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type NonIntrusiveSurveyNotes = InferModel<
|
||||
|
|
|
|||
|
|
@ -65,6 +65,16 @@ export const recommendationMaterials = pgTable("recommendation_materials", {
|
|||
estimatedCost: real("estimated_cost").notNull(),
|
||||
});
|
||||
|
||||
// We create a plan type, for common plan types that we produce for clients
|
||||
const PlanType: [string, ...string[]] = [
|
||||
"solar_eco4",
|
||||
"solar_hhrsh_eco4",
|
||||
"empty_cavity_eco",
|
||||
"partial_cavity_eco",
|
||||
"extraction_eco",
|
||||
];
|
||||
export const planTypeEnum = pgEnum("plan_type", PlanType);
|
||||
|
||||
export const plan = pgTable("plan", {
|
||||
id: bigserial("id", { mode: "bigint" }).primaryKey(),
|
||||
name: text("name"),
|
||||
|
|
@ -82,6 +92,7 @@ export const plan = pgTable("plan", {
|
|||
valuationIncreaseLowerBound: real("valuation_increase_lower_bound"),
|
||||
valuationIncreaseUpperBound: real("valuation_increase_upper_bound"),
|
||||
valuationIncreaseAverage: real("valuation_increase_average"),
|
||||
planType: planTypeEnum("plan_type"), // This may be null for custom plans, outside of our common plan types
|
||||
});
|
||||
|
||||
export const planRecommendations = pgTable("plan_recommendations", {
|
||||
|
|
@ -272,5 +283,5 @@ export const MeasureKeyEnum = z.enum([
|
|||
...Object.keys(measuresDisplayLabels),
|
||||
] as [
|
||||
MeasureKey, // Force at least one measure key
|
||||
...MeasureKey[]
|
||||
...MeasureKey[],
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -62,10 +62,19 @@ export const recommendationMaterialsRelations = relations(
|
|||
// create a one to many relation to map a plan to the details in the underlying recommendation
|
||||
// create a many to many map from a plan to a recommendation
|
||||
// A recommendation can be in multiple plans and therefore we have a many to many relationship between
|
||||
// plan and recommendations. This relationship is facilitated by the planRecommdnations table
|
||||
// plan and recommendations. This relationship is facilitated by the planRecommendations table.
|
||||
// We also need to be able to get from a plan to its property
|
||||
|
||||
export const planRelations = relations(plan, ({ many }) => ({
|
||||
export const planRelations = relations(plan, ({ one, many }) => ({
|
||||
property: one(property, {
|
||||
fields: [plan.propertyId],
|
||||
references: [property.id],
|
||||
}),
|
||||
planRecommendations: many(planRecommendations),
|
||||
fundingPackage: one(fundingPackage, {
|
||||
fields: [plan.id],
|
||||
references: [fundingPackage.planId],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const planRecommendationsRelations = relations(
|
||||
|
|
@ -83,6 +92,7 @@ export const planRecommendationsRelations = relations(
|
|||
);
|
||||
|
||||
// one to one relationship between property and propertyTargets, and also to the recommendations table
|
||||
// We also relate a property to it's many plans, from which we can take the default
|
||||
export const propertyRelations = relations(property, ({ one, many }) => ({
|
||||
target: one(propertyTargets, {
|
||||
fields: [property.id],
|
||||
|
|
@ -93,6 +103,7 @@ export const propertyRelations = relations(property, ({ one, many }) => ({
|
|||
fields: [property.id],
|
||||
references: [propertyDetailsEpc.propertyId],
|
||||
}),
|
||||
plans: many(plan),
|
||||
}));
|
||||
|
||||
// We have a many to many relationship between users and portfolios
|
||||
|
|
@ -159,15 +170,25 @@ export const energyAssessmentDocumentsRelations = relations(
|
|||
// Relation from a funding package to funding package measures
|
||||
// Define a relation from a EnergyAssessmentDocument to EnergyAssessmentScenario. This is a many to one
|
||||
|
||||
// funding package links to multiple funding package measures
|
||||
export const fundingPackageRelations = relations(fundingPackage, ({ many }) => ({
|
||||
fundingPackageMeasures: many(fundingPackageMeasures),
|
||||
}));
|
||||
// funding package links to multiple funding package measures. We also link to a plan
|
||||
export const fundingPackageRelations = relations(
|
||||
fundingPackage,
|
||||
({ one, many }) => ({
|
||||
plan: one(plan, {
|
||||
fields: [fundingPackage.planId],
|
||||
references: [plan.id],
|
||||
}),
|
||||
fundingPackageMeasures: many(fundingPackageMeasures),
|
||||
})
|
||||
);
|
||||
|
||||
// funding package measures belong to a funding package
|
||||
export const fundingPackageMeasuresRelations = relations(fundingPackageMeasures, ({ one }) => ({
|
||||
fundingPackage: one(fundingPackage, {
|
||||
fields: [fundingPackageMeasures.fundingPackageId],
|
||||
references: [fundingPackage.id],
|
||||
}),
|
||||
}));
|
||||
export const fundingPackageMeasuresRelations = relations(
|
||||
fundingPackageMeasures,
|
||||
({ one }) => ({
|
||||
fundingPackage: one(fundingPackage, {
|
||||
fields: [fundingPackageMeasures.fundingPackageId],
|
||||
references: [fundingPackage.id],
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ function EmptyPropertyState() {
|
|||
);
|
||||
}
|
||||
|
||||
export default async function Page(
|
||||
props: {
|
||||
params: Promise<{ slug: string }>;
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined | number }>;
|
||||
}
|
||||
) {
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug: string }>;
|
||||
searchParams: Promise<{
|
||||
[key: string]: string | string[] | undefined | number;
|
||||
}>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
// This page is served from the server so we can make calls to the database
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import {
|
|||
PropertyWithRelations,
|
||||
} from "@/app/db/schema/property";
|
||||
|
||||
|
||||
interface DataTableColumnHeaderProps<TData, TValue>
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
column: Column<TData, TValue>;
|
||||
|
|
@ -41,13 +40,11 @@ const EpcLetterBubble = ({ letter }: { letter: string }) => {
|
|||
);
|
||||
};
|
||||
|
||||
|
||||
export function DataTableFilterHeader<TData, TValue>({
|
||||
column,
|
||||
title,
|
||||
className,
|
||||
}: DataTableColumnHeaderProps<TData, TValue>) {
|
||||
|
||||
if (!column.getCanSort()) {
|
||||
return <div className={cn(className)}>{title}</div>;
|
||||
}
|
||||
|
|
@ -66,10 +63,11 @@ export function DataTableFilterHeader<TData, TValue>({
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
{PortfolioStatus.map((status) => (
|
||||
{[...PortfolioStatus, "ECO4", "GBIS"].map((status) => (
|
||||
<DropdownMenuItem
|
||||
key={status}
|
||||
onClick={() => {
|
||||
console.log("status filter:", status);
|
||||
column.setFilterValue(status);
|
||||
}}
|
||||
>
|
||||
|
|
@ -130,10 +128,22 @@ export const columns: ColumnDef<PropertyWithRelations>[] = [
|
|||
},
|
||||
cell: ({ row }) => {
|
||||
const status = row.getValue("status") ?? "";
|
||||
const plans = row.original.plans || [];
|
||||
// Check if any plan has an ECO4 or GBIS funding package
|
||||
const fundingScheme = plans.find((p) => {
|
||||
const scheme = p?.fundingPackage?.scheme;
|
||||
return scheme && ["ECO4", "GBIS"].includes(scheme.toUpperCase());
|
||||
})?.fundingPackage?.scheme;
|
||||
|
||||
const effectiveStatus = fundingScheme
|
||||
? fundingScheme.toUpperCase()
|
||||
: status;
|
||||
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
{status && <StatusBadge status={String(status)} isProperty={true} />}
|
||||
{effectiveStatus && (
|
||||
<StatusBadge status={String(effectiveStatus)} isProperty={true} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ export interface DataItem {
|
|||
scenarios: Scenario[];
|
||||
}
|
||||
|
||||
|
||||
export async function getOverviewPortfolioData(
|
||||
portfolioId: string
|
||||
): Promise<DataItem[]> {
|
||||
|
|
@ -313,7 +312,11 @@ export async function getOverviewPortfolioData(
|
|||
{ scenarioName: "Today", data: "" },
|
||||
{
|
||||
scenarioName: portfolioName || "Default",
|
||||
data: "£" + formatNumber((data[0].funding || 0) / (data[0].nUnitsToRetrofit || 1)),
|
||||
data:
|
||||
"£" +
|
||||
formatNumber(
|
||||
(data[0].funding || 0) / (data[0].nUnitsToRetrofit || 1)
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -333,7 +336,11 @@ export async function getOverviewPortfolioData(
|
|||
{ scenarioName: "Today", data: "" },
|
||||
{
|
||||
scenarioName: portfolioName || "Default",
|
||||
data: "£" + formatNumber((data[0].contingency || 0) / (data[0].nUnitsToRetrofit || 1)),
|
||||
data:
|
||||
"£" +
|
||||
formatNumber(
|
||||
(data[0].contingency || 0) / (data[0].nUnitsToRetrofit || 1)
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -410,56 +417,6 @@ export async function getProperties(
|
|||
limit: number = 1000,
|
||||
offset: number = 0
|
||||
): Promise<PropertyWithRelations[]> {
|
||||
// When pulling in the associated recommendations, we need to pull in the associated plan and take recommendations that are associated to the
|
||||
// default plans
|
||||
|
||||
// const data: PropertyWithRelations[] = await db.query.property.findMany({
|
||||
// limit: limit,
|
||||
// offset: offset,
|
||||
// columns: {
|
||||
// id: true,
|
||||
// portfolioId: true,
|
||||
// address: true,
|
||||
// postcode: true,
|
||||
// status: true,
|
||||
// creationStatus: true,
|
||||
// currentEpcRating: true,
|
||||
// currentSapPoints: true,
|
||||
// },
|
||||
// where: eq(property.portfolioId, BigInt(portfolioId)),
|
||||
// with: {
|
||||
// target: {
|
||||
// columns: {
|
||||
// epc: true,
|
||||
// },
|
||||
// },
|
||||
// recommendations: {
|
||||
// columns: {
|
||||
// id: true,
|
||||
// estimatedCost: true,
|
||||
// sapPoints: true,
|
||||
// },
|
||||
// where: eq(recommendation.default, true),
|
||||
// with: {
|
||||
// planRecommendations: {
|
||||
// columns: {
|
||||
// planId: true,
|
||||
// },
|
||||
// with: {
|
||||
// plan: {
|
||||
// columns: {
|
||||
// id: true,
|
||||
// isDefault: true,
|
||||
// },
|
||||
// where: eq(plan.isDefault, true),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
// We need to perform the query like this because the nested query is not supported in the ORM right now
|
||||
const data: PropertyWithRelations[] = await db.query.property.findMany({
|
||||
limit: limit,
|
||||
|
|
@ -501,10 +458,37 @@ export async function getProperties(
|
|||
)
|
||||
),
|
||||
},
|
||||
plans: {
|
||||
columns: {
|
||||
id: true,
|
||||
},
|
||||
where: eq(plan.isDefault, true),
|
||||
// Associate the funding information
|
||||
with: {
|
||||
fundingPackage: {
|
||||
columns: {
|
||||
scheme: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
// override status to reflect ECO4/GBIS if present
|
||||
const updated = data.map((p) => {
|
||||
const fundingScheme = p.plans.find((pl) => {
|
||||
const scheme = pl?.fundingPackage?.scheme;
|
||||
return scheme && ["ECO4", "GBIS"].includes(scheme.toUpperCase());
|
||||
})?.fundingPackage?.scheme;
|
||||
|
||||
return {
|
||||
...p,
|
||||
status: fundingScheme ? fundingScheme.toUpperCase() : p.status,
|
||||
};
|
||||
});
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
interface UnaggregatedPortfolioPlanRecommendation {
|
||||
|
|
@ -545,7 +529,8 @@ function aggregateRecommendations(
|
|||
};
|
||||
} else {
|
||||
// if quantity is null previously, set to 0
|
||||
grouped[key].quantity = (grouped[key].quantity ?? 0) + (item.quantity ?? 0);
|
||||
grouped[key].quantity =
|
||||
(grouped[key].quantity ?? 0) + (item.quantity ?? 0);
|
||||
grouped[key].estimatedCost += item.estimatedCost;
|
||||
|
||||
if (item.propertyId) {
|
||||
|
|
@ -560,7 +545,9 @@ function aggregateRecommendations(
|
|||
|
||||
// Round the results to 2 decimal places, compute uniquePropertyCount, and count unique measureTypes
|
||||
for (const key in grouped) {
|
||||
grouped[key].quantity = parseFloat(grouped[key].quantity?.toFixed(2) || "0.00");
|
||||
grouped[key].quantity = parseFloat(
|
||||
grouped[key].quantity?.toFixed(2) || "0.00"
|
||||
);
|
||||
grouped[key].estimatedCost = parseFloat(
|
||||
grouped[key].estimatedCost.toFixed(2)
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue