mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
Added basic structure for plan page
This commit is contained in:
parent
170d00791e
commit
4d6a38d163
8 changed files with 201 additions and 55 deletions
17
src/app/components/portfolio/BackToPortfolio.tsx
Normal file
17
src/app/components/portfolio/BackToPortfolio.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function BackToPortfolio({
|
||||
portfolioId,
|
||||
}: {
|
||||
portfolioId: string;
|
||||
}) {
|
||||
return (
|
||||
<Link href={`/portfolio/${portfolioId}`}>
|
||||
<div className="ml-2 px-4 py-1 inline-flex items-center bg-brandtan hover:bg-hovertan text-white rounded-md transition-colors duration-200 cursor-pointer">
|
||||
<ArrowLeftIcon className="w-5 h-5 mr-1" />
|
||||
Back to portfolio
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Dispatch, Fragment, SetStateAction, useState } from "react";
|
||||
import { Dispatch, Fragment, SetStateAction } from "react";
|
||||
import { TanButton } from "../Button";
|
||||
import { EpcRating } from "@/types/epc";
|
||||
|
||||
|
|
@ -7,45 +7,19 @@ export default function PartModal({
|
|||
isOpen,
|
||||
setIsOpen,
|
||||
title,
|
||||
targetEpcRating,
|
||||
currentEpcRating,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
title: string;
|
||||
targetEpcRating: EpcRating | "";
|
||||
currentEpcRating: EpcRating;
|
||||
}) {
|
||||
function handleEditModalClose() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
const [modalEpcTarget, setModalEpcTarget] = useState<EpcRating | "">(
|
||||
targetEpcRating
|
||||
);
|
||||
|
||||
function handleModalSubmit() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
function getEpcOptions(
|
||||
epc: EpcRating
|
||||
): { label: EpcRating; value: EpcRating }[] {
|
||||
const alphabet = "ABCDEFG";
|
||||
const index = alphabet.indexOf(epc.toUpperCase());
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error("Invalid letter input");
|
||||
}
|
||||
|
||||
const epcOptions = alphabet.slice(0, index + 1).split("");
|
||||
|
||||
return epcOptions.map((opt) => ({
|
||||
label: opt as EpcRating,
|
||||
value: opt as EpcRating,
|
||||
}));
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ export default function PartCard({
|
|||
isOpen={isDetailModalOpen}
|
||||
setIsOpen={setIsDetailModalOpen}
|
||||
title={title}
|
||||
targetEpcRating="C"
|
||||
currentEpcRating="D"
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
|
|
|
|||
21
src/app/portfolio/[slug]/property/[lmkKey]/layout.tsx
Normal file
21
src/app/portfolio/[slug]/property/[lmkKey]/layout.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import BackToPortfolio from "@/app/components/portfolio/BackToPortfolio";
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: { slug: string; lmkKey: string };
|
||||
}) {
|
||||
const portfolioId = params.slug;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="mt-2">
|
||||
<BackToPortfolio portfolioId={portfolioId} />
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeftIcon, PencilSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { PencilSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { SearchData, EpcRating, EpcKey } from "@/types/epc";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import EditEpcTargetModal from "../../../../components/property/EditEpcTargetModal";
|
||||
import PartCard from "@/app/components/property/PartCard";
|
||||
import { TanButton } from "@/app/components/Button";
|
||||
import { fetchData } from "./utils";
|
||||
import BackToPortfolio from "@/app/components/portfolio/BackToPortfolio";
|
||||
|
||||
const EpcDefaults: { [key in EpcRating]: EpcRating } = {
|
||||
G: "C",
|
||||
|
|
@ -73,19 +75,6 @@ const partConfig: PartConfig = [
|
|||
},
|
||||
];
|
||||
|
||||
async function fetchData(postcode: string): Promise<SearchData> {
|
||||
// TODO - add strict typing to the api response
|
||||
|
||||
const response = await fetch(`/api/search?postcode=${postcode}`);
|
||||
|
||||
if (!response.ok) {
|
||||
// This will activate the closest `error.js` Error Boundary
|
||||
throw new Error("Failed to fetch data");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export default function PropertyPage({
|
||||
params,
|
||||
searchParams,
|
||||
|
|
@ -137,13 +126,6 @@ export default function PropertyPage({
|
|||
|
||||
return (
|
||||
<section>
|
||||
<div className="h-2"></div>
|
||||
<Link href={`/portfolio/${portfolioId}`}>
|
||||
<div className="ml-2 px-4 py-1 inline-flex items-center bg-brandtan hover:bg-hovertan text-white rounded-md transition-colors duration-200 cursor-pointer">
|
||||
<ArrowLeftIcon className="w-5 h-5 mr-1" />
|
||||
Back to portfolio
|
||||
</div>
|
||||
</Link>
|
||||
<div className="max-w-3xl mx-auto p-6">
|
||||
<h1 className="text-2xl text-center font-bold mb-4">Your Property</h1>
|
||||
|
||||
|
|
@ -173,9 +155,11 @@ export default function PropertyPage({
|
|||
</div>
|
||||
<div className="flex justify-center">
|
||||
<TanButton
|
||||
label="Build My Plan"
|
||||
label="Build My Retrofit Plan"
|
||||
onClick={() => {
|
||||
router.push(`/portfolio/${portfolioId}/property/${lmkKey}/plan`);
|
||||
router.push(
|
||||
`/portfolio/${portfolioId}/property/${lmkKey}/plan?postcode=${postcode}`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,125 @@
|
|||
export default function Plan() {
|
||||
"use client";
|
||||
|
||||
import { SearchData } from "@/types/epc";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { fetchData } from "../utils";
|
||||
import { useState } from "react";
|
||||
|
||||
import React from "react";
|
||||
import { PencilSquareIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
type PlanPartProps = {
|
||||
title: string;
|
||||
cost: number;
|
||||
co2Emissions: number;
|
||||
workHours: number;
|
||||
};
|
||||
|
||||
const PlanPart: React.FC<PlanPartProps> = ({
|
||||
title,
|
||||
cost,
|
||||
co2Emissions,
|
||||
workHours,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Plan</h1>
|
||||
<div className="flex bg-white p-4 rounded-lg shadow mb-4 items-center border-l-4 border-l-brandmidblue hover:bg-brandmidblue hover:text-white hover:border-l-brandtan cursor-pointer">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-lg font-bold mb-2">{title}</h2>
|
||||
</div>
|
||||
<div className="flex-1 text-center">
|
||||
<p>Cost: {cost}</p>
|
||||
</div>
|
||||
<div className="flex-1 text-center">
|
||||
<p>CO2 Emissions: {co2Emissions}</p>
|
||||
</div>
|
||||
<div className="flex-1 text-center">
|
||||
<p>Work Hours: {workHours}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Plan({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: { slug: string; lmkKey: string };
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
const portfolioId = params.slug;
|
||||
const lmkKey = params.lmkKey;
|
||||
const postcode = searchParams.postcode;
|
||||
const targetEpcRating = searchParams.targetEpcRating ?? "C";
|
||||
|
||||
const [budget, setBudget] = useState("Not set");
|
||||
const [totalCost, setTotalCost] = useState("Not set");
|
||||
const [installTime, setInstallTime] = useState("Not set");
|
||||
|
||||
if (postcode === undefined) {
|
||||
router.push(`/portfolio/${portfolioId}/error`);
|
||||
}
|
||||
|
||||
const { data, error, isLoading } = useQuery<SearchData, Error>({
|
||||
queryKey: ["search", postcode],
|
||||
queryFn: async () => fetchData(postcode as string),
|
||||
});
|
||||
|
||||
// TODO: Add a loading state and error handling
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>Error fetching data: {error.message}</div>;
|
||||
}
|
||||
|
||||
const propertyData = data.rows.filter((row) => row["lmk-key"] === lmkKey)[0];
|
||||
return (
|
||||
<section>
|
||||
<div className="max-w-5xl mx-auto p-6 flex flex-col items-center">
|
||||
<div className="text-center mb-4">
|
||||
<h1 className="text-2xl font-bold">Your Retrofit Plan</h1>
|
||||
<p>{propertyData.address}</p>
|
||||
</div>
|
||||
<div className="flex w-full">
|
||||
<div className="w-3/4 pr-4">
|
||||
{/* Clickable Cards */}
|
||||
{/* Replace this with your actual clickable cards */}
|
||||
<PlanPart
|
||||
title="Plan Part 1"
|
||||
cost={1000}
|
||||
co2Emissions={50}
|
||||
workHours={20}
|
||||
/>
|
||||
<div className="bg-white p-4 rounded-lg shadow mb-4">
|
||||
Clickable Card 2
|
||||
</div>
|
||||
{/* Add more clickable cards as needed */}
|
||||
</div>
|
||||
<div className="w-1/4 ">
|
||||
<div className="bg-brandmidblue p-4 rounded-lg shadow text-white ">
|
||||
<h2 className="text-lg font-bold mb-4">Summary</h2>
|
||||
<ul>
|
||||
<li className="mb-2 mb-2 flex items-center cursor-pointer">
|
||||
Target EPC: {targetEpcRating}
|
||||
<PencilSquareIcon className="h-6 w-6 ml-2" />
|
||||
</li>
|
||||
<li className="mb-2">Total cost: {totalCost}</li>
|
||||
<li className="mb-2">Budget: {budget}</li>
|
||||
<li className="mb-2">Installation Time: {installTime}</li>
|
||||
{/* flex items-center text-brandmidblue hover:text-brandblue transition-colors duration-200 cursor-pointer */}
|
||||
<li className="mb-2 flex items-center cursor-pointer">
|
||||
Edit Property Details
|
||||
<PencilSquareIcon className="h-6 w-6 ml-2" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
14
src/app/portfolio/[slug]/property/[lmkKey]/utils.tsx
Normal file
14
src/app/portfolio/[slug]/property/[lmkKey]/utils.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { SearchData } from "@/types/epc";
|
||||
|
||||
export async function fetchData(postcode: string): Promise<SearchData> {
|
||||
// TODO - add strict typing to the api response
|
||||
|
||||
const response = await fetch(`/api/search?postcode=${postcode}`);
|
||||
|
||||
if (!response.ok) {
|
||||
// This will activate the closest `error.js` Error Boundary
|
||||
throw new Error("Failed to fetch data");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
20
src/app/portfolio/[slug]/search/layout.tsx
Normal file
20
src/app/portfolio/[slug]/search/layout.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import BackToPortfolio from "@/app/components/portfolio/BackToPortfolio";
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: { slug: string; lmkKey: string };
|
||||
}) {
|
||||
const portfolioId = params.slug;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="mt-2">
|
||||
<BackToPortfolio portfolioId={portfolioId} />
|
||||
</div>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue