mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Added core layout for the plan table
This commit is contained in:
parent
4d6a38d163
commit
bb1eff91d0
4 changed files with 256 additions and 46 deletions
58
src/app/components/plan/PartDropdown.tsx
Normal file
58
src/app/components/plan/PartDropdown.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { Fragment } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
type Option = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type DropdownProps = {
|
||||
options: Option[];
|
||||
onSelectOption: (option: Option) => void;
|
||||
selectedOption: string;
|
||||
};
|
||||
|
||||
export default function PartDropdown({
|
||||
options,
|
||||
onSelectOption,
|
||||
selectedOption,
|
||||
}: DropdownProps) {
|
||||
return (
|
||||
<Menu as="div" className="relative inline-block text-left w-full">
|
||||
<Menu.Button className="inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-white bg-brandblue rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||
Selected option: {selectedOption ?? ""}{" "}
|
||||
<ChevronDownIcon
|
||||
className="ml-2 -mr-1 h-5 w-5 text-violet-200 hover:text-violet-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="origin-bottom left-0 w-full rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
{options.map((option) => (
|
||||
<Menu.Item key={option.value}>
|
||||
{({ active }) => (
|
||||
<button
|
||||
className={`${
|
||||
active ? "bg-brandmidblue text-white" : "text-gray-900"
|
||||
} group flex items-center w-full px-4 py-2 text-sm`}
|
||||
onClick={() => onSelectOption(option)}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
96
src/app/components/plan/PartModal.tsx
Normal file
96
src/app/components/plan/PartModal.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Dispatch, Fragment, SetStateAction, useState } from "react";
|
||||
import { TanButton } from "../Button";
|
||||
import PartDropdown from "./PartDropdown";
|
||||
|
||||
export default function PartModal({
|
||||
title,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
options,
|
||||
selectedOption,
|
||||
setSelectedOption,
|
||||
}: {
|
||||
title: string;
|
||||
isOpen: boolean;
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
options: { label: string; value: string }[];
|
||||
selectedOption: string;
|
||||
setSelectedOption: Dispatch<SetStateAction<string>>;
|
||||
}) {
|
||||
function handleModalSubmit() {
|
||||
// setTargetEpcRating(modalEpcTarget);
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={() => setIsOpen(false)}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-1/2 max-w-screen-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-brandblue mb-3"
|
||||
>
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
<div className="flex justify-center">
|
||||
<div>
|
||||
You can select different options for this part - this is
|
||||
example functionality for demo
|
||||
</div>
|
||||
<PartDropdown
|
||||
options={options}
|
||||
onSelectOption={(option) =>
|
||||
setSelectedOption(option.value)
|
||||
}
|
||||
selectedOption={selectedOption}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<TanButton label={"Save"} onClick={handleModalSubmit} />
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
58
src/app/components/plan/PlanPart.tsx
Normal file
58
src/app/components/plan/PlanPart.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { useState } from "react";
|
||||
import PartModal from "./PartModal";
|
||||
|
||||
type PlanPartProps = {
|
||||
title: string;
|
||||
cost: number;
|
||||
co2Emissions: number;
|
||||
workHours: number;
|
||||
};
|
||||
|
||||
export default function PlanPart({
|
||||
title,
|
||||
cost,
|
||||
co2Emissions,
|
||||
workHours,
|
||||
}: PlanPartProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
// These are temporary options for the demo
|
||||
const options = [
|
||||
{ label: "option 1", value: "option 1" },
|
||||
{ label: "option 2", value: "option 2" },
|
||||
{ label: "option 3", value: "option 3" },
|
||||
{ label: "option 4", value: "option 4" },
|
||||
{ label: "option 5", value: "option 5" },
|
||||
];
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState(options[0].value);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
setIsOpen(true);
|
||||
}}
|
||||
className="active:bg-blue-700 focus:outline-none transition-colors duration-200 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"
|
||||
>
|
||||
<h2 className="flex-1 text-lg font-bold mb-2 text-left">{title}</h2>
|
||||
<div>{selectedOption}</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>
|
||||
<PartModal
|
||||
title={title}
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
options={options}
|
||||
selectedOption={selectedOption}
|
||||
setSelectedOption={setSelectedOption}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -5,40 +5,8 @@ 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 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>
|
||||
);
|
||||
};
|
||||
import PlanPart from "@/app/components/plan/PlanPart";
|
||||
|
||||
export default function Plan({
|
||||
params,
|
||||
|
|
@ -58,6 +26,37 @@ export default function Plan({
|
|||
const [totalCost, setTotalCost] = useState("Not set");
|
||||
const [installTime, setInstallTime] = useState("Not set");
|
||||
|
||||
const partsConfig = [
|
||||
{ part: "Roof", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{ part: "Walls", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{ part: "Floors", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{ part: "Window Glazing", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{
|
||||
part: "Window Draughproofing",
|
||||
cost: 1000,
|
||||
co2Emissions: 50,
|
||||
workHours: 20,
|
||||
},
|
||||
{ part: "Door Insulation", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{
|
||||
part: "Door Draughproofing",
|
||||
cost: 1000,
|
||||
co2Emissions: 50,
|
||||
workHours: 20,
|
||||
},
|
||||
{ part: "Heating", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{ part: "Hot Water", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{ part: "Solar Panels", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{ part: "Solar Hot Water", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{ part: "Lighting", cost: 1000, co2Emissions: 50, workHours: 20 },
|
||||
{
|
||||
part: "Chimneys/ Open Fire Places",
|
||||
cost: 1000,
|
||||
co2Emissions: 50,
|
||||
workHours: 20,
|
||||
},
|
||||
];
|
||||
|
||||
if (postcode === undefined) {
|
||||
router.push(`/portfolio/${portfolioId}/error`);
|
||||
}
|
||||
|
|
@ -79,25 +78,24 @@ export default function Plan({
|
|||
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="max-w-6xl 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 */}
|
||||
{partsConfig.map((part, index) => {
|
||||
return (
|
||||
<PlanPart
|
||||
key={index}
|
||||
title={part.part}
|
||||
cost={part.cost}
|
||||
co2Emissions={part.co2Emissions}
|
||||
workHours={part.workHours}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="w-1/4 ">
|
||||
<div className="bg-brandmidblue p-4 rounded-lg shadow text-white ">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue