mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
working on buulding passport ui
This commit is contained in:
parent
adc93a6012
commit
7ecf3fd9d5
6 changed files with 347 additions and 28 deletions
19
src/app/components/building-passport/EpcCard.tsx
Normal file
19
src/app/components/building-passport/EpcCard.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
export default function EpcCard({
|
||||
epcRating,
|
||||
fullMargin = true,
|
||||
}: {
|
||||
epcRating: string;
|
||||
fullMargin: boolean;
|
||||
}) {
|
||||
let marginClass = "";
|
||||
if (fullMargin) {
|
||||
marginClass = "mx-auto";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center p-8 shadow rounded-md max-w-xl justify-start text-gray-100 bg-brandblue">
|
||||
<div className="text-xl font-bold mb-4 text-center">Energy Rating</div>
|
||||
<div className="text-6xl font-bold">{epcRating}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
113
src/app/components/building-passport/FeatureTable.tsx
Normal file
113
src/app/components/building-passport/FeatureTable.tsx
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/app/shadcn_components/ui/table";
|
||||
|
||||
import { Badge } from "@/app/shadcn_components/ui/badge";
|
||||
import { Feature, Rating } from "@/app/db/schema/property";
|
||||
|
||||
function RatingBadge({ rating }: { rating: Rating }) {
|
||||
const colourMap = {
|
||||
"Very good": "bg-green-500",
|
||||
Good: "bg-green-300",
|
||||
Poor: "bg-yellow-300",
|
||||
"Very poor": "bg-red-500",
|
||||
};
|
||||
const ratingConfig = colourMap[rating];
|
||||
return <Badge className={ratingConfig}>{rating}</Badge>;
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<Feature>[] = [
|
||||
{
|
||||
accessorKey: "feature",
|
||||
header: "Feature",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
},
|
||||
{
|
||||
accessorKey: "rating",
|
||||
header: "Rating",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="">
|
||||
<RatingBadge rating={row.getValue("rating")} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
interface DataTableProps {
|
||||
data: Feature[];
|
||||
}
|
||||
|
||||
export default function FeatureTable({ data }: DataTableProps) {
|
||||
// Initialise the table
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -15,3 +15,26 @@ export interface PropertyMeta {
|
|||
tenure: string;
|
||||
currentEpcRating: string;
|
||||
}
|
||||
|
||||
export type Rating = "Very good" | "Good" | "Poor" | "Very poor";
|
||||
|
||||
export interface Feature {
|
||||
feature: string;
|
||||
description: string;
|
||||
rating: Rating;
|
||||
}
|
||||
|
||||
export interface ConditionReportData {
|
||||
id: number;
|
||||
lastUpdated: string;
|
||||
fullAddress: string;
|
||||
postcode: string;
|
||||
currentEpcRating: string;
|
||||
inConservationArea: "Yes" | "No" | "Unknown";
|
||||
propertyType: string;
|
||||
builtForm: string;
|
||||
totalFloorArea: number;
|
||||
tenure: string;
|
||||
features: Feature[];
|
||||
yearBuilt: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import EpcCard from "@/app/components/building-passport/EpcCard";
|
||||
import { PropertyMeta } from "@/app/db/schema/property";
|
||||
import { formatDateTime } from "@/app/utils";
|
||||
import {
|
||||
HomeIcon,
|
||||
BuildingOfficeIcon,
|
||||
|
|
@ -8,30 +10,6 @@ import {
|
|||
UserGroupIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
|
||||
function formatDateTime(dateTimeString: string): string {
|
||||
// Create a new Date object
|
||||
const dateTime = new Date(dateTimeString);
|
||||
|
||||
// Get the various parts of the date
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
};
|
||||
return dateTime.toLocaleDateString("en-GB", options);
|
||||
}
|
||||
|
||||
function EpcCard({ epcRating }: { epcRating: string }) {
|
||||
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="text-xl font-bold mb-4">Energy Rating</div>
|
||||
<div className="text-6xl font-bold">{epcRating}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BuildingPassportHome() {
|
||||
const propertyMeta: PropertyMeta = {
|
||||
id: 1,
|
||||
|
|
@ -53,7 +31,7 @@ export default function BuildingPassportHome() {
|
|||
return (
|
||||
<div className="flex flex-col items-center mt-4">
|
||||
<div className="flex justify-center mt-4 space-x-2">
|
||||
<EpcCard epcRating={propertyMeta.currentEpcRating} />
|
||||
<EpcCard epcRating={propertyMeta.currentEpcRating} fullMargin={false} />
|
||||
<div className="flex flex-col p-8 bg-white shadow rounded-md max-w-2xl mx-auto justify-start text-gray-700">
|
||||
<div className="text-2xl font-bold mb-4">Your property</div>
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,178 @@
|
|||
export default function PreAssessmentReport() {
|
||||
import EpcCard from "@/app/components/building-passport/EpcCard";
|
||||
import FeatureTable from "@/app/components/building-passport/FeatureTable";
|
||||
import { ConditionReportData, PropertyMeta } from "@/app/db/schema/property";
|
||||
import { formatDateTime } from "@/app/utils";
|
||||
|
||||
function AddressCard({ address }: { address: string }) {
|
||||
// In the future, we might want to use react-wrap-balancer for some of this text
|
||||
return (
|
||||
<div>
|
||||
<div className="flex p-8">Pre Assessment Report</div>
|
||||
<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="text-2xl font-bold max-w-l">{address}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface PropertyDetailsCardProps {
|
||||
conditionReportData: ConditionReportData;
|
||||
propertyMeta: PropertyMeta;
|
||||
}
|
||||
|
||||
function PropertyDetailsCard({
|
||||
conditionReportData,
|
||||
propertyMeta,
|
||||
}: PropertyDetailsCardProps) {
|
||||
return (
|
||||
<div className="w-full flex flex-col items-center p-8 shadow rounded-md justify-start text-gray-100 bg-brandblue">
|
||||
<div className="grid grid-cols-2 gap-8 text-lg w-full h-full">
|
||||
<div className="border-r">
|
||||
<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">
|
||||
{conditionReportData.yearBuilt}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Property Type:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">{`${conditionReportData.builtForm} ${conditionReportData.propertyType}`}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Total floor area:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">{`${conditionReportData.totalFloorArea} square meters`}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">In conservation area:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
{conditionReportData.inConservationArea}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<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>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Constituency:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
{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>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-gray-100 ">Number of rooms:</td>
|
||||
<td className="text-gray-100 text-end pr-8 py-1">
|
||||
{propertyMeta.numberOfRooms}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// This type is used to define the shape of our data.
|
||||
// You can use a Zod schema here if you want.
|
||||
|
||||
export default async function PreAssessmentReport() {
|
||||
const propertyMeta: PropertyMeta = {
|
||||
id: 1,
|
||||
address: "123 Fake Street",
|
||||
postcode: "AB1 2CD",
|
||||
hasPreConditionReport: true,
|
||||
hasRecommendations: true,
|
||||
createdAt: "2023-07-12 11:51:31.000 +0100",
|
||||
propertyType: "House",
|
||||
builtForm: "Detached",
|
||||
localAuthority: "Birmingham",
|
||||
constituency: "Birmingham",
|
||||
numberOfRooms: 5,
|
||||
yearBuilt: 1990,
|
||||
tenure: "Rented (social)",
|
||||
currentEpcRating: "C",
|
||||
};
|
||||
|
||||
const conditionReportData: ConditionReportData = {
|
||||
id: 1,
|
||||
lastUpdated: "2023-07-12 11:51:31.000 +0100",
|
||||
fullAddress: "123 Fake Street, Fake Town",
|
||||
postcode: "AB1 2CD",
|
||||
currentEpcRating: "C",
|
||||
inConservationArea: "Yes",
|
||||
propertyType: "House",
|
||||
builtForm: "Detached",
|
||||
totalFloorArea: 60,
|
||||
tenure: "Rented (social)",
|
||||
yearBuilt: "1990",
|
||||
features: [
|
||||
{ feature: "Wall", description: "Cavity wall", rating: "Poor" },
|
||||
{
|
||||
feature: "Roof",
|
||||
description: "Flat, limited insulation (assumed)",
|
||||
rating: "Very poor",
|
||||
},
|
||||
{ feature: "Windows", description: "Double glazing", rating: "Good" },
|
||||
{ feature: "Heating", description: "Gas boiler", rating: "Good" },
|
||||
{
|
||||
feature: "Heating Control",
|
||||
description: "Programmer and appliance thermostats",
|
||||
rating: "Good",
|
||||
},
|
||||
{
|
||||
feature: "Hot Water",
|
||||
description: "Electric immersion, standard tariff",
|
||||
rating: "Good",
|
||||
},
|
||||
{
|
||||
feature: "Lighting",
|
||||
description: "Low energy lighting in all fixed outlets",
|
||||
rating: "Very good",
|
||||
},
|
||||
{
|
||||
feature: "Floor",
|
||||
description: "Suspended timber",
|
||||
rating: "Poor",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const features = conditionReportData.features;
|
||||
|
||||
return (
|
||||
<div className="leading-loose tracking-wider">
|
||||
<div className="flex py-8 text-lg">Pre Assessment Report</div>
|
||||
<div className="text-gray-700 text-sm">
|
||||
Last updated: {formatDateTime(conditionReportData.lastUpdated)}
|
||||
</div>
|
||||
<div className="flex flex-col items-stretch mb-4">
|
||||
<div className="flex flex-row justify-start mt-4 space-x-4">
|
||||
<EpcCard
|
||||
epcRating={conditionReportData.currentEpcRating}
|
||||
fullMargin={false}
|
||||
/>
|
||||
<AddressCard address={conditionReportData.fullAddress} />
|
||||
|
||||
<PropertyDetailsCard
|
||||
conditionReportData={conditionReportData}
|
||||
propertyMeta={propertyMeta}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex py-8 text-lg">Property Features</div>
|
||||
<FeatureTable data={features} />
|
||||
<div className="flex py-8 text-lg">Heating Demand</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
export function formatDateTime(dateTimeString: string): string {
|
||||
// Create a new Date object
|
||||
const dateTime = new Date(dateTimeString);
|
||||
|
||||
// Get the various parts of the date
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
};
|
||||
return dateTime.toLocaleDateString("en-GB", options);
|
||||
}
|
||||
|
||||
export function formatNumber(number: number): string {
|
||||
const suffixes: string[] = ["", "k", "m", "b", "t"];
|
||||
const suffixIndex: number = Math.floor(Math.log10(Math.abs(number)) / 3);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue