mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Implemented button disabled logic
This commit is contained in:
parent
1bb8406f2e
commit
efea3662ea
3 changed files with 317 additions and 38 deletions
58
src/app/api/portfolio/plan/route.ts
Normal file
58
src/app/api/portfolio/plan/route.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import {
|
||||
portfolio,
|
||||
portfolioUsers,
|
||||
PortfolioGoal,
|
||||
PortfolioStatus,
|
||||
PortfolioRole,
|
||||
} from "@/app/db/schema/portfolio";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { db } from "@/app/db/db";
|
||||
import { z } from "zod";
|
||||
|
||||
const createPortfolioSchema = z.object({
|
||||
userId: z.number(),
|
||||
portfolioName: z.string(),
|
||||
budget: z.number().optional(),
|
||||
goal: z.enum(PortfolioGoal),
|
||||
status: z.enum(PortfolioStatus),
|
||||
role: z.enum(PortfolioRole),
|
||||
});
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
console.log("Triggering plan");
|
||||
return new NextResponse(JSON.stringify({ msg: "plan triggered" }), {
|
||||
status: 201,
|
||||
});
|
||||
|
||||
// try {
|
||||
// const validatedBody = createPortfolioSchema.parse(body);
|
||||
// const { userId, portfolioName, budget, goal, status, role } = validatedBody;
|
||||
|
||||
// const creationDate = new Date();
|
||||
|
||||
// const newPortfolio = await db
|
||||
// .insert(portfolio)
|
||||
// .values({
|
||||
// name: portfolioName,
|
||||
// budget: budget,
|
||||
// goal: goal,
|
||||
// status: status,
|
||||
// createdAt: creationDate,
|
||||
// updatedAt: creationDate,
|
||||
// })
|
||||
// .returning({ portfolioId: portfolio.id });
|
||||
|
||||
// const newPortfolioId = newPortfolio[0].portfolioId;
|
||||
|
||||
// const response = await db
|
||||
// .insert(portfolioUsers)
|
||||
// .values({ userId: userId, portfolioId: newPortfolioId, role: role })
|
||||
// .returning();
|
||||
|
||||
// return new NextResponse(JSON.stringify(response), { status: 201 });
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// throw error;
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,19 +1,82 @@
|
|||
"use client";
|
||||
|
||||
import { Menu, Dialog, Transition } from "@headlessui/react";
|
||||
import { Fragment, useState } from "react";
|
||||
import { ChevronDownIcon } from "@heroicons/react/20/solid";
|
||||
import { Float } from "@headlessui-float/react";
|
||||
|
||||
import ModalSubmit from "@/app/components/home/ModalSubmit";
|
||||
|
||||
import { Input } from "@/app/shadcn_components/ui/input";
|
||||
import { Label } from "@/app/shadcn_components/ui/label";
|
||||
import { set } from "cypress/types/lodash";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
export const SubmitPlan = ({
|
||||
buttonDisabled,
|
||||
goal,
|
||||
fundingScheme,
|
||||
goalValue,
|
||||
}: {
|
||||
buttonDisabled: boolean;
|
||||
goal: string;
|
||||
fundingScheme: string;
|
||||
goalValue: string;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
async function triggerPlanBuild() {
|
||||
const requestBody = JSON.stringify({
|
||||
goal: goal,
|
||||
goalValue: goalValue,
|
||||
fundingScheme: fundingScheme,
|
||||
});
|
||||
|
||||
const response = await fetch("/api/portfolio/plan", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: requestBody,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
const { mutate, isLoading } = useMutation(triggerPlanBuild, {
|
||||
onSuccess: (data) => {
|
||||
console.log("uploading csv");
|
||||
// router.push(`/portfolio/${data.id}`);
|
||||
},
|
||||
onError: (error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = () => {
|
||||
mutate();
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-brandtan px-4 py-2 text-sm font-medium text-grey-900 hover:bg-hovertan focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:bg-gray-300 disabled:opacity-50"
|
||||
onClick={handleSubmit}
|
||||
disabled={buttonDisabled || isLoading}
|
||||
>
|
||||
{isLoading ? "Creating..." : "Create"}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export function InputFile() {
|
||||
return (
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5 text-sm font-semibold text-gray-600">
|
||||
<Label htmlFor="csv-uploader">Upload your csv</Label>
|
||||
<Input id="csv-uploader" type="file" />
|
||||
<Input id="csv-uploader" type="file" className="cursor-pointer" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -21,6 +84,7 @@ export function InputFile() {
|
|||
type Option = {
|
||||
label: string;
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
type DropdownProps = {
|
||||
|
|
@ -55,7 +119,7 @@ export function SelectDropdown({
|
|||
>
|
||||
<Menu.Items className=" origin-bottom left-0 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
{options.map((option) => (
|
||||
<Menu.Item key={option.value}>
|
||||
<Menu.Item key={option.value} disabled={option.disabled}>
|
||||
{({ active }) => (
|
||||
<button
|
||||
className={`${
|
||||
|
|
@ -84,14 +148,17 @@ const selectFundingSchemeOptions = [
|
|||
{
|
||||
label: "None",
|
||||
value: "None",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "SHDF",
|
||||
value: "SHDF",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "ECO4",
|
||||
value: "ECO4",
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
const selectGoalOptions = [
|
||||
|
|
@ -102,10 +169,12 @@ const selectGoalOptions = [
|
|||
{
|
||||
label: "Increase EPC",
|
||||
value: "Increase EPC",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "Reduce energy consumption",
|
||||
value: "Reduce energy consumption",
|
||||
disabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -113,14 +182,17 @@ const goalValueOptions = [
|
|||
{
|
||||
label: "C",
|
||||
value: "C",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "B",
|
||||
value: "B",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "A",
|
||||
value: "A",
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -138,18 +210,24 @@ export default function UploadCsvModal({
|
|||
const [fundingScheme, setFundingScheme] = useState<string>("");
|
||||
const [goalValue, setGoalValue] = useState<string>("");
|
||||
|
||||
function handleGoalChange(value: string) {
|
||||
setSelectedGoal(value);
|
||||
}
|
||||
|
||||
function handleFundingSchemeChange(value: string) {
|
||||
setFundingScheme(value);
|
||||
}
|
||||
|
||||
function handleBudgeChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
setBudget(e.target.valueAsNumber);
|
||||
}
|
||||
|
||||
function handleButtonDisabled(
|
||||
goal?: string,
|
||||
scheme?: string,
|
||||
value?: string
|
||||
) {
|
||||
if (
|
||||
(goal || selectedGoal) &&
|
||||
(scheme || fundingScheme) &&
|
||||
(value || goalValue)
|
||||
) {
|
||||
setButtonDisabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
|
|
@ -184,19 +262,25 @@ export default function UploadCsvModal({
|
|||
<Dialog.Panel className="w-full 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"
|
||||
className="flex justify-center text-lg font-medium leading-6 text-brandblue mb-3"
|
||||
>
|
||||
Upload a CSV of properties
|
||||
Upload Properties
|
||||
</Dialog.Title>
|
||||
|
||||
<div className="text-gray-700 mb-7 mt-7 leading-relaxed tracking-wider">
|
||||
Upload a csv of properties and input some details about how
|
||||
you want to retrofit your properties
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<label
|
||||
htmlFor="csv-upload-budget"
|
||||
className="text-sm font-semibold text-gray-600 mb-1 relative"
|
||||
className="text-sm font-semibold text-gray-600 mb-1 relative leading-relaxed tracking-wider"
|
||||
>
|
||||
Budget
|
||||
<span className="text-red-500">*</span>
|
||||
</label>
|
||||
<p className="text-sm text-gray-500 mb-1">
|
||||
<p className="text-sm text-gray-500 mb-2 leading-relaxed tracking-wider">
|
||||
If you don't set a budget, we will aim to minimise cost
|
||||
anyway
|
||||
</p>
|
||||
|
|
@ -205,16 +289,16 @@ export default function UploadCsvModal({
|
|||
type="number"
|
||||
placeholder="Set a budget"
|
||||
required
|
||||
// value={portfolioName}
|
||||
value={budget}
|
||||
onChange={(e) => handleBudgeChange(e)}
|
||||
className="p-2 border border-gray-200 rounded-md focus:outline-none bg-gray-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col mt-6">
|
||||
<label
|
||||
htmlFor="portfolio-name"
|
||||
className="text-sm font-semibold text-gray-600 mb-1 relative"
|
||||
className="text-sm font-semibold text-gray-600 mb-1 relative leading-relaxed tracking-wider"
|
||||
>
|
||||
Funding Scheme
|
||||
<span className="text-red-500">*</span>
|
||||
|
|
@ -222,27 +306,39 @@ export default function UploadCsvModal({
|
|||
<SelectDropdown
|
||||
options={selectFundingSchemeOptions}
|
||||
selectedOption={fundingScheme}
|
||||
onSelectOption={(option) =>
|
||||
setFundingScheme(option.value)
|
||||
}
|
||||
onSelectOption={(option) => {
|
||||
setFundingScheme(option.value);
|
||||
handleButtonDisabled(
|
||||
selectedGoal,
|
||||
option.value,
|
||||
goalValue
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<label className="text-sm font-semibold text-gray-600 mb-1 relative">
|
||||
<div className="flex flex-col mt-6">
|
||||
<label className="text-sm font-semibold text-gray-600 relative leading-relaxed tracking-wider">
|
||||
Select your goal
|
||||
<span className="text-red-500">*</span>
|
||||
</label>
|
||||
<SelectDropdown
|
||||
options={selectGoalOptions}
|
||||
selectedOption={selectedGoal}
|
||||
onSelectOption={(option) => setSelectedGoal(option.value)}
|
||||
onSelectOption={(option) => {
|
||||
setSelectedGoal(option.value);
|
||||
handleButtonDisabled(
|
||||
option.value,
|
||||
fundingScheme,
|
||||
goalValue
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{selectedGoal && (
|
||||
<div className="mt-4">
|
||||
<div className="flex flex-col mt-6">
|
||||
<label
|
||||
htmlFor="csv-upload-epc"
|
||||
className="text-sm font-semibold text-gray-600 mb-1 relative"
|
||||
className="text-sm font-semibold text-gray-600 relative leading-relaxed tracking-wider"
|
||||
>
|
||||
Choose a target EPC value
|
||||
<span className="text-red-500">*</span>
|
||||
|
|
@ -250,15 +346,20 @@ export default function UploadCsvModal({
|
|||
<SelectDropdown
|
||||
options={goalValueOptions}
|
||||
selectedOption={goalValue}
|
||||
onSelectOption={(option) =>
|
||||
setGoalValue(option.value)
|
||||
}
|
||||
onSelectOption={(option) => {
|
||||
setGoalValue(option.value);
|
||||
handleButtonDisabled(
|
||||
selectedGoal,
|
||||
fundingScheme,
|
||||
option.value
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div className="flex flex-col space-y-2 mt-7">
|
||||
<div className="flex space-x-2">
|
||||
<InputFile />
|
||||
</div>
|
||||
|
|
@ -272,13 +373,12 @@ export default function UploadCsvModal({
|
|||
>
|
||||
Cancel
|
||||
</button>
|
||||
{"Submit button goes here"}
|
||||
{/* <ModalSubmit
|
||||
<SubmitPlan
|
||||
buttonDisabled={buttonDisabled}
|
||||
// portfolioName={portfolioName}
|
||||
budget={budget}
|
||||
objective={selectedOutcome}
|
||||
/> */}
|
||||
goal={goalValue}
|
||||
fundingScheme={fundingScheme}
|
||||
goalValue={goalValue}
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
|
|
|
|||
121
src/app/shadcn_components/ui/select.tsx
Normal file
121
src/app/shadcn_components/ui/select.tsx
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { Check, ChevronDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue