mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Working on recommendations ui
This commit is contained in:
parent
7b814e5365
commit
b4ea1ab69b
5 changed files with 301 additions and 5 deletions
31
package-lock.json
generated
31
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
84
src/app/components/building-passport/RecommendationTable.tsx
Normal file
84
src/app/components/building-passport/RecommendationTable.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
30
src/app/shadcn_components/ui/checkbox.tsx
Normal file
30
src/app/shadcn_components/ui/checkbox.tsx
Normal 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 }
|
||||
Loading…
Add table
Reference in a new issue