Implemented button disabled logic

This commit is contained in:
Khalim Conn-Kowlessar 2023-07-13 12:29:18 +01:00
parent 1bb8406f2e
commit efea3662ea
3 changed files with 317 additions and 38 deletions

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

View file

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

View 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,
}