working on buulding passport ui

This commit is contained in:
Khalim Conn-Kowlessar 2023-07-25 18:10:01 +01:00
parent adc93a6012
commit 7ecf3fd9d5
6 changed files with 347 additions and 28 deletions

View 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>
);
}

View 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>
);
}

View file

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

View file

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

View file

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

View file

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