added decent homes tab button

This commit is contained in:
Khalim Conn-Kowlessar 2025-09-23 17:21:28 +00:00
parent b40fe283ad
commit e6b0b70270
8 changed files with 129 additions and 111 deletions

View file

@ -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}

View file

@ -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",
};

View file

@ -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[];

View file

@ -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;

View file

@ -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>

View file

@ -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" />

View file

@ -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(
</>
);
}

View file

@ -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}