mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
added decent homes tab button
This commit is contained in:
parent
b40fe283ad
commit
e6b0b70270
8 changed files with 129 additions and 111 deletions
|
|
@ -7,7 +7,7 @@ import {
|
|||
WrenchScrewdriverIcon,
|
||||
SunIcon,
|
||||
CircleStackIcon,
|
||||
BoltIcon,
|
||||
HeartIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import {
|
||||
NavigationMenu,
|
||||
|
|
@ -21,7 +21,7 @@ import { getUploadedFile } from "@/app/db/surveyDB/schema/surveyDB";
|
|||
interface ToolbarProps {
|
||||
propertyId: string;
|
||||
portfolioId: string;
|
||||
conditionReport: getUploadedFile;
|
||||
decentHomes: getUploadedFile;
|
||||
}
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
|
|
@ -55,7 +55,11 @@ const navigationMenuTriggerStyle = cva(
|
|||
].join(" ")
|
||||
);
|
||||
|
||||
export function Toolbar({ propertyId, portfolioId, conditionReport }: ToolbarProps) {
|
||||
export function Toolbar({
|
||||
propertyId,
|
||||
portfolioId,
|
||||
decentHomes,
|
||||
}: ToolbarProps) {
|
||||
function handleClickSettings() {
|
||||
console.log("Settings were clicked, implement me");
|
||||
}
|
||||
|
|
@ -70,16 +74,6 @@ export function Toolbar({ propertyId, portfolioId, conditionReport }: ToolbarPro
|
|||
</NavigationMenuLink>
|
||||
);
|
||||
|
||||
// const energyAssessmentsReportButton = (
|
||||
// <NavigationMenuLink
|
||||
// className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
// href={`/portfolio/${portfolioId}/building-passport/${propertyId}/energy-assessment`}
|
||||
// >
|
||||
// <BoltIcon className="h-4 w-4 mr-2" />
|
||||
// Energy Assessment
|
||||
// </NavigationMenuLink>
|
||||
// );
|
||||
|
||||
const documentsButton = (
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
|
|
@ -110,6 +104,16 @@ export function Toolbar({ propertyId, portfolioId, conditionReport }: ToolbarPro
|
|||
</NavigationMenuLink>
|
||||
);
|
||||
|
||||
const decentHomesButton = (
|
||||
<NavigationMenuLink
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/decent-homes`}
|
||||
>
|
||||
<HeartIcon className="h-4 w-4 mr-2" />
|
||||
Decent Homes
|
||||
</NavigationMenuLink>
|
||||
);
|
||||
|
||||
return (
|
||||
<NavigationMenu>
|
||||
<NavigationMenuLink
|
||||
|
|
@ -122,10 +126,14 @@ export function Toolbar({ propertyId, portfolioId, conditionReport }: ToolbarPro
|
|||
|
||||
<NavigationMenuList>
|
||||
{preAssessmentReportButton}
|
||||
{/* We only show decent homes button if decent homes is not an empty object */}
|
||||
{Object.keys(decentHomes).length > 0 &&
|
||||
decentHomes.uprn &&
|
||||
decentHomesButton}
|
||||
{solarAnalysisButton}
|
||||
{recommendationsButton}
|
||||
{documentsButton}
|
||||
{/* {energyAssessmentsReportButton} */}
|
||||
|
||||
<NavigationMenuItem
|
||||
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
|
||||
onClick={handleClickSettings}
|
||||
|
|
|
|||
|
|
@ -2,72 +2,92 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const REPORT_TYPES = [
|
||||
// "quidos_presite_note",
|
||||
// "charted_surveyor_report",
|
||||
// "u_value_calculator_report",
|
||||
// "overwriting_u_value_declaration_form",
|
||||
// "quidos_presite_note",
|
||||
// "charted_surveyor_report",
|
||||
// "u_value_calculator_report",
|
||||
// "overwriting_u_value_declaration_form",
|
||||
"osmosis_condition_pas_2035_report",
|
||||
// "warm_homes_condition_pas_2035_report",
|
||||
// "energy_performance_report_with_data",
|
||||
// "warm_homes_condition_pas_2035_report",
|
||||
// "energy_performance_report_with_data",
|
||||
"energy_performance_report_summary_information",
|
||||
"lodgement_xml_needed_for_lodgement_to_like_trademark",
|
||||
"reduce_xml_needed_to_generate_full_sap_xml",
|
||||
"full_xml_needed_for_co_ordination",
|
||||
// "floor_plan",
|
||||
// "occupancy_assessment",
|
||||
"decent_homes_summary",
|
||||
"decent_homes_property_meta",
|
||||
// "decent_homes_energy_performance_report",
|
||||
// "decent_homes_energy_performance_report_summary_information",
|
||||
// "floor_plan",
|
||||
// "occupancy_assessment",
|
||||
] as const;
|
||||
|
||||
export type ReportType = (typeof REPORT_TYPES)[number];
|
||||
|
||||
// Map reportType → title for UI
|
||||
export const documentTypeTitles: Record<ReportType, string> = {
|
||||
// quidos_presite_note: "RdSAP Summary Report",
|
||||
// charted_surveyor_report: "Chartered Surveyor Report",
|
||||
// u_value_calculator_report: "U-Value Calculator Report",
|
||||
// overwriting_u_value_declaration_form: "Overwriting U-Value Declaration Form",
|
||||
// quidos_presite_note: "RdSAP Summary Report",
|
||||
// charted_surveyor_report: "Chartered Surveyor Report",
|
||||
// u_value_calculator_report: "U-Value Calculator Report",
|
||||
// overwriting_u_value_declaration_form: "Overwriting U-Value Declaration Form",
|
||||
osmosis_condition_pas_2035_report: "Osmosis Condition Report (PAS 2035)",
|
||||
// warm_homes_condition_pas_2035_report: "Warm Homes PAS 2035 Report",
|
||||
// energy_performance_report_with_data: "EPC Report With Data",
|
||||
// warm_homes_condition_pas_2035_report: "Warm Homes PAS 2035 Report",
|
||||
// energy_performance_report_with_data: "EPC Report With Data",
|
||||
energy_performance_report_summary_information: "EPC Summary Report",
|
||||
lodgement_xml_needed_for_lodgement_to_like_trademark: "LIG XML",
|
||||
reduce_xml_needed_to_generate_full_sap_xml: "RdSAP XML",
|
||||
full_xml_needed_for_co_ordination: "Full SAP XML",
|
||||
// floor_plan: "Floor Plan",
|
||||
// occupancy_assessment: "Occupancy Assessment",
|
||||
decent_homes_summary: "Decent Homes Summary",
|
||||
decent_homes_property_meta: "Decent Homes Property Meta",
|
||||
// decent_homes_energy_performance_report: "Decent Homes Energy Performance Report",
|
||||
// decent_homes_energy_performance_report_summary_information:
|
||||
// "Decent Homes Energy Performance Report Summary Information",
|
||||
// floor_plan: "Floor Plan",
|
||||
// occupancy_assessment: "Occupancy Assessment",
|
||||
};
|
||||
|
||||
// Map reportType → accepted file extensions
|
||||
export const documentTypeFileTypes: Record<ReportType, ".pdf" | ".xml" | ".xml,.pdf"> = {
|
||||
// quidos_presite_note: ".pdf",
|
||||
// charted_surveyor_report: ".pdf",
|
||||
// u_value_calculator_report: ".pdf",
|
||||
// overwriting_u_value_declaration_form: ".pdf",
|
||||
export const documentTypeFileTypes: Record<
|
||||
ReportType,
|
||||
".pdf" | ".xml" | ".xml,.pdf" | ".json"
|
||||
> = {
|
||||
// quidos_presite_note: ".pdf",
|
||||
// charted_surveyor_report: ".pdf",
|
||||
// u_value_calculator_report: ".pdf",
|
||||
// overwriting_u_value_declaration_form: ".pdf",
|
||||
osmosis_condition_pas_2035_report: ".pdf",
|
||||
// warm_homes_condition_pas_2035_report: ".pdf",
|
||||
// energy_performance_report_with_data: ".pdf",
|
||||
// warm_homes_condition_pas_2035_report: ".pdf",
|
||||
// energy_performance_report_with_data: ".pdf",
|
||||
energy_performance_report_summary_information: ".pdf",
|
||||
lodgement_xml_needed_for_lodgement_to_like_trademark: ".xml",
|
||||
reduce_xml_needed_to_generate_full_sap_xml: ".xml",
|
||||
full_xml_needed_for_co_ordination: ".xml",
|
||||
// floor_plan: ".pdf",
|
||||
// occupancy_assessment: ".pdf",
|
||||
decent_homes_property_meta: ".json",
|
||||
decent_homes_summary: ".json",
|
||||
// floor_plan: ".pdf",
|
||||
// occupancy_assessment: ".pdf",
|
||||
};
|
||||
export const ReportTypeSchema = z.enum(REPORT_TYPES);
|
||||
|
||||
// Map UI value -> DB enum NAME
|
||||
export const reportTypeToDbLabel: Record<ReportType, string> = {
|
||||
osmosis_condition_pas_2035_report: "ECO_CONDITION_REPORT",
|
||||
energy_performance_report_summary_information: "ENERGY_PERFORMANCE_REPORT_SUMMARY_INFORMATION",
|
||||
energy_performance_report_summary_information:
|
||||
"ENERGY_PERFORMANCE_REPORT_SUMMARY_INFORMATION",
|
||||
lodgement_xml_needed_for_lodgement_to_like_trademark: "LIG_XML",
|
||||
reduce_xml_needed_to_generate_full_sap_xml: "RDSAP_XML",
|
||||
full_xml_needed_for_co_ordination: "FULLSAP_XML",
|
||||
decent_homes_summary: "DECENT_HOMES_SUMMARY",
|
||||
decent_homes_property_meta: "DECENT_HOMES_PROPERTY_META",
|
||||
};
|
||||
|
||||
// Optional reverse map (for reading from API):
|
||||
export const dbLabelToReportType: Record<string, ReportType> = {
|
||||
ECO_CONDITION_REPORT: "osmosis_condition_pas_2035_report",
|
||||
ENERGY_PERFORMANCE_REPORT_SUMMARY_INFORMATION: "energy_performance_report_summary_information",
|
||||
ENERGY_PERFORMANCE_REPORT_SUMMARY_INFORMATION:
|
||||
"energy_performance_report_summary_information",
|
||||
LIG_XML: "lodgement_xml_needed_for_lodgement_to_like_trademark",
|
||||
RDSAP_XML: "reduce_xml_needed_to_generate_full_sap_xml",
|
||||
FULLSAP_XML: "full_xml_needed_for_co_ordination",
|
||||
};
|
||||
DECENT_HOMES_SUMMARY: "decent_homes_summary",
|
||||
DECENT_HOMES_PROPERTY_META: "decent_homes_property_meta",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { pgEnum } from "drizzle-orm/pg-core";
|
||||
|
||||
|
||||
export const DB_REPORT_TYPES = [
|
||||
"ECO_CONDITION_REPORT",
|
||||
"ENERGY_PERFORMANCE_REPORT_SUMMARY_INFORMATION",
|
||||
"LIG_XML",
|
||||
"RDSAP_XML",
|
||||
"FULLSAP_XML",
|
||||
"ECO_CONDITION_REPORT",
|
||||
"ENERGY_PERFORMANCE_REPORT_SUMMARY_INFORMATION",
|
||||
"LIG_XML",
|
||||
"RDSAP_XML",
|
||||
"FULLSAP_XML",
|
||||
"DECENT_HOMES_RAW_DATA",
|
||||
"DECENT_HOMES_PROPERTY_META",
|
||||
"DECENT_HOMES_SUMMARY",
|
||||
] as const;
|
||||
|
||||
export const docTypeEnum = pgEnum("reporttype", DB_REPORT_TYPES);
|
||||
|
|
@ -18,14 +20,18 @@ export const uploadedFiles = pgTable("uploaded_files", {
|
|||
s3JsonUri: text("s3_json_uri"),
|
||||
s3FileUri: text("s3_file_uri").notNull(),
|
||||
|
||||
docType: docTypeEnum("doc_type").notNull(), // enum used here ✅
|
||||
docType: docTypeEnum("doc_type").notNull(), // enum used here ✅
|
||||
|
||||
s3FileUploadTimestamp: timestamp("s3_file_upload_timestamp", { withTimezone: true }).notNull(),
|
||||
s3JsonUploadTimestamp: timestamp("s3_json_upload_timestamp", { withTimezone: true }),
|
||||
s3FileUploadTimestamp: timestamp("s3_file_upload_timestamp", {
|
||||
withTimezone: true,
|
||||
}).notNull(),
|
||||
s3JsonUploadTimestamp: timestamp("s3_json_upload_timestamp", {
|
||||
withTimezone: true,
|
||||
}),
|
||||
|
||||
uprn: text("uprn").notNull(),
|
||||
});
|
||||
|
||||
export type getUploadedFile = typeof uploadedFiles.$inferSelect
|
||||
export type getUploadedFile = typeof uploadedFiles.$inferSelect;
|
||||
|
||||
export type getUploadedFiles = getUploadedFile[];
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
import { Toolbar } from "@/app/components/portfolio/Toolbar";
|
||||
import { getPortfolio, getPortfolioScenarios } from "../utils";
|
||||
|
||||
export default async function PortfolioLayout(
|
||||
props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ slug: string; propertyId: string }>;
|
||||
}
|
||||
) {
|
||||
export default async function PortfolioLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ slug: string; propertyId: string }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
|
||||
const {
|
||||
// will be a page or nested layout
|
||||
children
|
||||
children,
|
||||
} = props;
|
||||
|
||||
const portfolioId = params.slug;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import EpcCard from "@/app/components/building-passport/EpcCard";
|
||||
import FeatureTable from "@/app/components/building-passport/FeatureTable";
|
||||
import {
|
||||
ConditionReportData,
|
||||
PropertyDetailsEpc,
|
||||
PropertyDetailsSpatial,
|
||||
PropertyMeta,
|
||||
|
|
@ -23,7 +22,6 @@ import {
|
|||
getDocument,
|
||||
getEnergyAssessmentFromS3,
|
||||
} from "../utils";
|
||||
import ConditionReport from "@/app/portfolio/[slug]/building-passport/[propertyId]/assessment/ConditionReport";
|
||||
|
||||
interface PropertyDetailsCardProps {
|
||||
conditionReportData: PropertyDetailsEpc;
|
||||
|
|
@ -133,18 +131,6 @@ export default async function PreAssessmentReport(props: {
|
|||
conditionReportData,
|
||||
propertyMeta.propertyType
|
||||
);
|
||||
const conditionReportMeta = await getDocument({
|
||||
uprn: String(propertyMeta.uprn),
|
||||
documentType: "ECO_CONDITION_REPORT",
|
||||
});
|
||||
let conditionReport = { rooms: {} };
|
||||
if (conditionReportMeta && conditionReportMeta.s3JsonUri) {
|
||||
conditionReport = await getEnergyAssessmentFromS3(
|
||||
conditionReportMeta.s3JsonUri
|
||||
);
|
||||
}
|
||||
|
||||
console.log("conditionReport", conditionReport);
|
||||
|
||||
const nonIntrusiveSurvey = await getNonIntrusiveSurvey(propertyMeta.uprn);
|
||||
|
||||
|
|
@ -179,15 +165,6 @@ export default async function PreAssessmentReport(props: {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{Object.keys(conditionReportMeta).length > 0 && (
|
||||
<ConditionReport
|
||||
conditionReport={conditionReport}
|
||||
totalFloorArea={conditionReportData.totalFloorArea}
|
||||
currentSapPoints={propertyMeta.currentSapPoints}
|
||||
conditionData={conditionReportData}
|
||||
/>
|
||||
)}
|
||||
|
||||
{nonIntrusiveSurvey && (
|
||||
<div>
|
||||
<div className="flex py-8 text-lg">Non-Intrusive Survey</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import { Table, TableBody, TableRow, TableCell } from "@/app/shadcn_components/ui/table";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableRow,
|
||||
TableCell,
|
||||
} from "@/app/shadcn_components/ui/table";
|
||||
import { DocumentSection } from "./DocumentSection";
|
||||
import {
|
||||
type ReportType,
|
||||
REPORT_TYPES,
|
||||
dbLabelToReportType, // <-- import the map
|
||||
dbLabelToReportType, // <-- import the map
|
||||
} from "@/app/db/surveyDB/schema/documents";
|
||||
import type { getUploadedFile } from "@/app/db/surveyDB/schema/surveyDB";
|
||||
|
||||
|
|
@ -14,7 +19,10 @@ type Props = {
|
|||
uploadedFilesData: getUploadedFile[];
|
||||
};
|
||||
|
||||
export const DocumentsTable: React.FC<Props> = ({ uprn, uploadedFilesData }) => {
|
||||
export const DocumentsTable: React.FC<Props> = ({
|
||||
uprn,
|
||||
uploadedFilesData,
|
||||
}) => {
|
||||
const filesByType = React.useMemo(() => {
|
||||
const map: Partial<Record<ReportType, getUploadedFile[]>> = {};
|
||||
|
||||
|
|
@ -26,7 +34,7 @@ export const DocumentsTable: React.FC<Props> = ({ uprn, uploadedFilesData }) =>
|
|||
}
|
||||
|
||||
// newest first within each group
|
||||
Object.values(map).forEach(arr =>
|
||||
Object.values(map).forEach((arr) =>
|
||||
arr!.sort(
|
||||
(a, b) =>
|
||||
new Date(b.s3FileUploadTimestamp as any).getTime() -
|
||||
|
|
@ -37,18 +45,20 @@ export const DocumentsTable: React.FC<Props> = ({ uprn, uploadedFilesData }) =>
|
|||
return map;
|
||||
}, [uploadedFilesData]);
|
||||
|
||||
console.log("filesByType", filesByType);
|
||||
|
||||
return (
|
||||
<Table className="min-w-full table-fixed divide-y divide-gray-200 shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
|
||||
<TableBody className="bg-white divide-y divide-gray-200">
|
||||
{REPORT_TYPES.map((reportType) => {
|
||||
const filesForType = filesByType[reportType] ?? [];
|
||||
console.log("reportType", reportType)
|
||||
console.log("reportType", reportType);
|
||||
return (
|
||||
<React.Fragment key={reportType}>
|
||||
<DocumentSection
|
||||
reportType={reportType}
|
||||
uprn={uprn}
|
||||
files={filesForType} // array of rows
|
||||
files={filesForType} // array of rows
|
||||
/>
|
||||
<TableRow className="hover:bg-transparent">
|
||||
<TableCell colSpan={3} className="h-3 p-0" />
|
||||
|
|
|
|||
|
|
@ -5,22 +5,17 @@ import { surveyDB } from "@/app/db/surveyDB/connection";
|
|||
import { uploadedFiles } from "@/app/db/surveyDB/schema/surveyDB";
|
||||
import { type getUploadedFiles } from "@/app/db/surveyDB/schema/surveyDB";
|
||||
|
||||
|
||||
async function getDocuments(
|
||||
uprn: number
|
||||
): Promise< getUploadedFiles> {
|
||||
async function getDocuments(uprn: number): Promise<getUploadedFiles> {
|
||||
const result = surveyDB.query.uploadedFiles.findMany({
|
||||
where: eq(uploadedFiles.uprn, String(uprn)),
|
||||
});
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default async function DocumentsPage(
|
||||
props: {
|
||||
params: Promise<{ slug: string; propertyId: string }>;
|
||||
}
|
||||
) {
|
||||
export default async function DocumentsPage(props: {
|
||||
params: Promise<{ slug: string; propertyId: string }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
// Get the property UPRN
|
||||
const propertyId = params.propertyId;
|
||||
|
|
@ -31,6 +26,8 @@ export default async function DocumentsPage(
|
|||
const propertyMeta = await getPropertyMeta(propertyId);
|
||||
const uploadedFiles = await getDocuments(propertyMeta.uprn);
|
||||
|
||||
console.log("Uploaded files:", uploadedFiles);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-6">
|
||||
|
|
@ -38,7 +35,7 @@ export default async function DocumentsPage(
|
|||
Core Survey Documents
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<DocumentsTable
|
||||
<DocumentsTable
|
||||
uprn={propertyMeta.uprn.toString()}
|
||||
uploadedFilesData={uploadedFiles}
|
||||
/>
|
||||
|
|
@ -51,4 +48,3 @@ export default async function DocumentsPage(
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,17 +12,15 @@ function EstimatedDataNotification() {
|
|||
);
|
||||
}
|
||||
|
||||
export default async function DashboardLayout(
|
||||
props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ slug: string; propertyId: string }>;
|
||||
}
|
||||
) {
|
||||
export default async function DashboardLayout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ slug: string; propertyId: string }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
|
||||
const {
|
||||
// will be a page or nested layout
|
||||
children
|
||||
children,
|
||||
} = props;
|
||||
|
||||
const propertyId = params.propertyId ?? "";
|
||||
|
|
@ -32,9 +30,10 @@ export default async function DashboardLayout(
|
|||
const propertyMeta = await getPropertyMeta(params.propertyId);
|
||||
// We check if we have an uploaded condition report and if so, we show the condition tab. Otherwise, we
|
||||
// don't show it
|
||||
const conditionReport = await getDocument(
|
||||
{ uprn: String(propertyMeta.uprn), documentType: "ECO_CONDITION_REPORT" }
|
||||
);
|
||||
const decentHomes = await getDocument({
|
||||
uprn: String(propertyMeta.uprn),
|
||||
documentType: "DECENT_HOMES_SUMMARY",
|
||||
});
|
||||
|
||||
if (!propertyId && propertyId !== "0") {
|
||||
throw Error("Invalid propertyId");
|
||||
|
|
@ -58,7 +57,11 @@ export default async function DashboardLayout(
|
|||
<p className="text-xl text-gray-700">{propertyMeta.postcode}</p>
|
||||
</div>
|
||||
<div className="col-span-12 justify-center bg-gray-50 py-2 rounded-md">
|
||||
<Toolbar propertyId={propertyId} portfolioId={portfolioId} conditionReport={conditionReport}/>
|
||||
<Toolbar
|
||||
propertyId={propertyId}
|
||||
portfolioId={portfolioId}
|
||||
decentHomes={decentHomes}
|
||||
/>
|
||||
</div>
|
||||
{propertyMeta.detailsEpc.estimated && <EstimatedDataNotification />}
|
||||
{children}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue