Working on recommendations ui

This commit is contained in:
Khalim Conn-Kowlessar 2023-07-27 11:38:25 +01:00
parent 7b814e5365
commit b4ea1ab69b
5 changed files with 301 additions and 5 deletions

31
package-lock.json generated
View file

@ -11,6 +11,7 @@
"@headlessui-float/react": "^0.11.2",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.18",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-hover-card": "^1.0.6",
@ -1533,6 +1534,36 @@
}
}
},
"node_modules/@radix-ui/react-checkbox": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz",
"integrity": "sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-controllable-state": "1.0.1",
"@radix-ui/react-use-previous": "1.0.1",
"@radix-ui/react-use-size": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",

View file

@ -17,6 +17,7 @@
"@headlessui-float/react": "^0.11.2",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.18",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-hover-card": "^1.0.6",

View file

@ -1,7 +1,150 @@
"use client";
import { ComponentRecommendation } from "@/app/db/schema/recommendations";
import { useState } from "react";
import { Fragment, useState } from "react";
import { formatNumber } from "@/app/utils";
import { Dialog, Transition } from "@headlessui/react";
import RecommendationTable from "@/app/components/building-passport/RecommendationTable";
import { ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@/app/shadcn_components/ui/checkbox";
export const uvalueColumns: ColumnDef<ComponentRecommendation>[] = [
{
accessorKey: "description",
header: "Description",
},
{
accessorKey: "estimatedCost",
header: "Estimated Cost",
},
{
accessorKey: "newUValue",
header: "New U-Value",
},
{
accessorKey: "default",
id: "default",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
];
export function RecommendationModal({
title,
isOpen = false,
setIsOpen,
recommendationData,
}: {
title: string;
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
recommendationData: ComponentRecommendation[];
}) {
const [portfolioName, setPortfolioName] = useState("");
const [budget, setBudget] = useState<undefined | number>(undefined);
const [selectedOutcome, setSelectedOutcome] = useState<string>("None");
const [buttonDisabled, setButtonDisabled] = useState(true);
function closeModal() {
setIsOpen(false);
}
function handlePortfolioNameChange(e: React.ChangeEvent<HTMLInputElement>) {
if (e.target.value.length > 0) {
setButtonDisabled(false);
} else {
setButtonDisabled(true);
}
setPortfolioName(e.target.value);
}
return (
<>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<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-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"
>
{title}
</Dialog.Title>
<RecommendationTable
data={recommendationData}
columns={uvalueColumns}
/>
<div className="mt-4 flex justify-end gap-2">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent hover:text-red-600 bg-gray-200 px-4 py-2 text-sm font-medium text-red-600 hover:bg-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:none focus-visible:ring-offset-2"
onClick={() => {
console.log("Remove recommendation");
}}
>
Remove Recommendation
</button>
<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={closeModal}
>
Cancel
</button>
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-brandblue px-4 py-2 text-sm font-medium text-gray-100 hover:bg-hoverblue focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-brandblue"
onClick={closeModal}
disabled={buttonDisabled}
>
Save
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</>
);
}
export default function RecommendationCard({
componentType,
@ -17,12 +160,13 @@ export default function RecommendationCard({
const [cardComponent, setCardComponent] =
useState<ComponentRecommendation>(defaultComponent);
const [modalIsOpen, setModalIsOpen] = useState(false);
return (
<div
className="active:shadow active:bg-brandmidblue w-full border rounded p-4 cursor-pointer text-gray-900 bg-gray-50 hover:bg-hoverblue hover:text-gray-100 transition-colors rounded-md flex flex-col justify-start"
onClick={() => {
console.log("clicked");
// replace with your modal opening logic
setModalIsOpen(true);
}}
>
<h2 className="font-bold mb-4 text-lg">{componentType}</h2>
@ -30,17 +174,23 @@ export default function RecommendationCard({
<table className="w-full text-left">
<tbody>
<tr>
<td>Estimated Cost:</td>
<td className="font-medium">Estimated Cost:</td>
<td>{"£" + formatNumber(cardComponent.estimatedCost)}</td>
</tr>
{cardComponent.newUValue && (
<tr>
<td>New U-Value:</td>
<td className="font-medium">New U-Value:</td>
<td>{cardComponent.newUValue}</td>
</tr>
)}
</tbody>
</table>
<RecommendationModal
title={componentType}
isOpen={modalIsOpen}
setIsOpen={setModalIsOpen}
recommendationData={recommendationData}
/>
</div>
);
}

View file

@ -0,0 +1,84 @@
"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 { ComponentRecommendation } from "@/app/db/schema/recommendations";
interface DataTableProps<T extends ComponentRecommendation> {
columns: ColumnDef<T>[];
data: T[];
}
export default function RecommendationTable<T extends ComponentRecommendation>({
data,
columns,
}: DataTableProps<T>) {
// 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

@ -0,0 +1,30 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }