mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
styling energy assessment page
This commit is contained in:
parent
22aea5ec6e
commit
fd0417f3e3
10 changed files with 203 additions and 21 deletions
|
|
@ -19,18 +19,27 @@ export function TanButton({
|
|||
);
|
||||
}
|
||||
|
||||
export function BrandBlueButton({
|
||||
export function BrandButton({
|
||||
label,
|
||||
onClick,
|
||||
backgroundColor,
|
||||
}: {
|
||||
label: string;
|
||||
onClick: Dispatch<SetStateAction<any>>;
|
||||
backgroundColor: "brandblue" | "brandgold"; // Restrict backgroundColor to these two options
|
||||
}) {
|
||||
// General tan colored button
|
||||
// Dictionary to map background colors to hover colors
|
||||
const hoverColors = {
|
||||
brandblue: "hover:bg-hoverblue",
|
||||
brandgold: "hover:bg-hovergold",
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-brandblue px-4 py-2 text-sm font-medium text-white hover:bg-hoverblue focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
className={`inline-flex justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2
|
||||
${backgroundColor === "brandblue" ? "bg-brandblue" : "bg-brandgold"}
|
||||
${hoverColors[backgroundColor]}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{label}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ export default function StatusBadge({
|
|||
return (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger>
|
||||
<Badge className={statusConfig.class}>{statusConfig.text}</Badge>
|
||||
<Badge
|
||||
className={`truncate overflow-hidden whitespace-nowrap ${statusConfig.class}`}
|
||||
>
|
||||
{statusConfig.text}
|
||||
</Badge>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ export default function EpcCard({
|
|||
expected = false,
|
||||
kwh = null,
|
||||
carbon = null,
|
||||
sap = null,
|
||||
}: {
|
||||
epcRating: string;
|
||||
fullMargin: boolean;
|
||||
expected?: boolean;
|
||||
kwh?: number | null;
|
||||
carbon?: number | null;
|
||||
sap?: string | null;
|
||||
}) {
|
||||
let marginClass = "";
|
||||
if (fullMargin) {
|
||||
|
|
@ -30,28 +32,35 @@ export default function EpcCard({
|
|||
return (
|
||||
<div
|
||||
className={
|
||||
"flex flex-col items-center p-4 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 " +
|
||||
bgColour
|
||||
}
|
||||
>
|
||||
<div className="text-xl font-bold mb-4 text-center">{title}</div>
|
||||
<div className="text-6xl font-bold ">{epcRating}</div>
|
||||
|
||||
{/* EPC Rating and SAP Rating */}
|
||||
<div className="flex items-baseline justify-center">
|
||||
{/* EPC Rating */}
|
||||
<div className="text-6xl font-bold">{epcRating}</div>
|
||||
|
||||
{/* SAP Rating (only if sap is provided) */}
|
||||
{sap !== null && (
|
||||
<div className="text-lg font-medium ml-2 text-gray-100">{sap}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(kwh || carbon) && (
|
||||
<table className="mt-6">
|
||||
<tbody>
|
||||
{kwh && (
|
||||
<tr>
|
||||
{" "}
|
||||
{/* Added vertical padding to each row */}
|
||||
<td className="text-gray-50">{kwh.toFixed(0)} kWh</td>
|
||||
</tr>
|
||||
)}
|
||||
{carbon && (
|
||||
<tr>
|
||||
{" "}
|
||||
<td className="text-gray-50 py-2">
|
||||
{carbon}t CO <sub>2</sub>
|
||||
{carbon}t CO<sub>2</sub>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -203,3 +203,9 @@ export type EnergyAssessmentDocument = InferModel<
|
|||
typeof energyAssessmentDocuments,
|
||||
"select"
|
||||
>;
|
||||
|
||||
// We define a type for the energyassessment docments that embeds a scenario in
|
||||
// the document
|
||||
export type EnergyAssessmentDocumentWithScenario = EnergyAssessmentDocument & {
|
||||
scenario: EnergyAssessmentScenario | null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import {
|
||||
energyAssessmentDocuments,
|
||||
energyAssessmentScenarios,
|
||||
} from "./energy_assessments";
|
||||
// This script contains ALL relations for the database, used by drizzle-orm
|
||||
|
||||
import { relations } from "drizzle-orm";
|
||||
|
|
@ -139,3 +143,14 @@ export const nonIntrusiveSurveyNotesRelations = relations(
|
|||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// Define a relation from a EnergyAssessmentDocument to EnergyAssessmentScenario. This is a many to one
|
||||
export const energyAssessmentDocumentsRelations = relations(
|
||||
energyAssessmentDocuments,
|
||||
({ one }) => ({
|
||||
scenario: one(energyAssessmentScenarios, {
|
||||
fields: [energyAssessmentDocuments.scenarioId],
|
||||
references: [energyAssessmentScenarios.id],
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { Inter } from "next/font/google";
|
|||
// If loading a variable font, you don't need to specify the font weight
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata = {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
TableCell,
|
||||
TableRow,
|
||||
} from "@/app/shadcn_components/ui/table";
|
||||
import { BrandBlueButton } from "@/app/components/Buttons";
|
||||
import { BrandButton } from "@/app/components/Buttons";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { DocumentType } from "@/app/db/schema/energy_assessments";
|
||||
|
||||
|
|
@ -88,9 +88,10 @@ export const DocumentsTable: React.FC<Props> = ({
|
|||
{descriptions[doc.documentType] || ""}
|
||||
</TableCell>
|
||||
<TableCell className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<BrandBlueButton
|
||||
<BrandButton
|
||||
label="Download"
|
||||
onClick={() => handleDownload(doc.documentLocation)} // Call the download handler
|
||||
backgroundColor="brandgold"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,67 @@
|
|||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/app/shadcn_components/ui/card";
|
||||
import EpcCard from "@/app/components/building-passport/EpcCard";
|
||||
import {
|
||||
getEnergyAssessment,
|
||||
getEnergyAssessmentDocuments,
|
||||
getPropertyMeta,
|
||||
} from "../utils";
|
||||
|
||||
// EnergyAssessmentsPage.tsx
|
||||
import { DocumentsTable } from "./DocumentsTable";
|
||||
import { getEpcColorClass } from "@/app/utils";
|
||||
|
||||
// Helper function to clean scenario names
|
||||
function cleanScenarioName(scenarioName: string | undefined) {
|
||||
if (!scenarioName) {
|
||||
return scenarioName;
|
||||
}
|
||||
|
||||
return scenarioName.startsWith("Scenario")
|
||||
? scenarioName.replace(/^Scenario\s*/i, "").trim()
|
||||
: scenarioName;
|
||||
}
|
||||
|
||||
type InfoCardProps = {
|
||||
title: string;
|
||||
value: number | string;
|
||||
unit: string;
|
||||
};
|
||||
|
||||
export const InfoCard: React.FC<InfoCardProps> = ({ title, value, unit }) => {
|
||||
const isEnergyRating = title === "Energy Rating";
|
||||
const bgColorClass = isEnergyRating ? getEpcColorClass(value.toString()) : ""; // Get the EPC color if it's Energy Rating
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={`flex-1 max-w-xs ${isEnergyRating ? bgColorClass : ""} ${
|
||||
isEnergyRating ? "text-white" : ""
|
||||
}`}
|
||||
>
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle
|
||||
className={`${
|
||||
isEnergyRating ? "text-white" : "text-gray-700"
|
||||
} text-sm`}
|
||||
>
|
||||
{title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-center p-6">
|
||||
<div
|
||||
className={`flex justify-center items-baseline ${
|
||||
isEnergyRating ? "text-white" : "text-gray-800"
|
||||
}`}
|
||||
>
|
||||
<span className="text-4xl font-bold">{value}</span>
|
||||
<span className="ml-1 text-sm">{unit}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default async function EnergyAssessmentsPage({
|
||||
params,
|
||||
|
|
@ -38,15 +94,93 @@ export default async function EnergyAssessmentsPage({
|
|||
"Floor Plan",
|
||||
];
|
||||
|
||||
const scenarioAllowedTypes = ["Scenario Draft EPC", "Scenario Site Notes"];
|
||||
|
||||
// Separate core documents and scenario-specific documents
|
||||
const coreDocuments = documents.filter(
|
||||
(doc) =>
|
||||
doc.scenarioId === null && table1AllowedTypes.includes(doc.documentType)
|
||||
);
|
||||
|
||||
const scenarioDocuments = documents.filter(
|
||||
(doc) =>
|
||||
doc.scenarioId !== null && scenarioAllowedTypes.includes(doc.documentType)
|
||||
);
|
||||
|
||||
// Extract unique scenarios from the documents
|
||||
const scenarios = Array.from(
|
||||
new Set(scenarioDocuments.map((doc) => doc.scenario?.scenarioName))
|
||||
).filter(Boolean); // Filter out any null or undefined scenario names
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex pt-8 text-lg">Core Survey Documents</div>
|
||||
<div className="py-4 max-w-8xl">
|
||||
<DocumentsTable
|
||||
documents={documents}
|
||||
allowedTypes={table1AllowedTypes}
|
||||
{/* EPC Card and Summary Information */}
|
||||
<div className="flex flex-row justify-between my-8 space-x-6 w-full">
|
||||
{/* EPC Rating Card */}
|
||||
|
||||
<InfoCard
|
||||
title="Energy Rating"
|
||||
value={ea.currentEnergyRating}
|
||||
unit={ea.currentEnergyEfficiency}
|
||||
/>
|
||||
|
||||
<InfoCard
|
||||
title="Carbon Emissions"
|
||||
value={ea.co2EmissionsCurrent}
|
||||
unit="tCO2/year"
|
||||
/>
|
||||
|
||||
<InfoCard
|
||||
title="Heat Demand"
|
||||
value={ea.energyConsumptionCurrent}
|
||||
unit="kWh/m²/year"
|
||||
/>
|
||||
|
||||
<InfoCard
|
||||
title="Est. Heating kWh"
|
||||
value={ea.spaceHeatingKwh}
|
||||
unit="kWh"
|
||||
/>
|
||||
|
||||
{/* Estimated Hot Water kWh */}
|
||||
<InfoCard
|
||||
title="Est. Hot Water kWh"
|
||||
value={ea.waterHeatingKwh}
|
||||
unit="kWh"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Core Survey Documents */}
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center justify-between py-4 px-6 bg-brandblue text-white font-semibold text-lg rounded-md">
|
||||
Core Survey Documents
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<DocumentsTable
|
||||
documents={coreDocuments}
|
||||
allowedTypes={table1AllowedTypes}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scenario-Specific Documents */}
|
||||
{scenarios.map((scenarioName) => {
|
||||
const scenarioDocs = scenarioDocuments.filter(
|
||||
(doc) => doc.scenario?.scenarioName === scenarioName
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={scenarioName} className="py-4">
|
||||
<div className="flex items-center justify-between py-4 px-6 bg-brandblue text-white font-semibold text-lg rounded-md">
|
||||
Scenario: {cleanScenarioName(scenarioName)}
|
||||
</div>
|
||||
<DocumentsTable
|
||||
documents={scenarioDocs}
|
||||
allowedTypes={scenarioAllowedTypes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
EnergyAssessment,
|
||||
energyAssessmentDocuments,
|
||||
EnergyAssessmentDocument,
|
||||
EnergyAssessmentDocumentWithScenario,
|
||||
} from "@/app/db/schema/energy_assessments";
|
||||
|
||||
type RecommendationList = {
|
||||
|
|
@ -45,12 +46,15 @@ export async function getEnergyAssessment(
|
|||
|
||||
export async function getEnergyAssessmentDocuments(
|
||||
energyAssessmentId: bigint
|
||||
): Promise<EnergyAssessmentDocument[]> {
|
||||
): Promise<EnergyAssessmentDocumentWithScenario[]> {
|
||||
const data = await db.query.energyAssessmentDocuments.findMany({
|
||||
where: eq(
|
||||
energyAssessmentDocuments.energyAssessmentId,
|
||||
BigInt(energyAssessmentId)
|
||||
),
|
||||
with: {
|
||||
scenario: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ module.exports = {
|
|||
brandtan: "#d3b488",
|
||||
hovertan: "#947750",
|
||||
brandgold: "#f1bb06",
|
||||
hovergold: "#c79d12",
|
||||
brandbrown: "#3d1e05",
|
||||
brandmidblue: "#3943b7",
|
||||
border: "hsl(var(--border))",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue