From cf821c9150081f69ab54940f4e22dcb5b4f250ac Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 27 Feb 2026 17:04:56 +0000 Subject: [PATCH] Add button and scenario selection / prioritisation --- package-lock.json | 56 ++++ package.json | 3 + .../reporting/RecommendationsOptions.tsx | 289 ++++++++++++++++++ .../reporting/ReportingClientArea.tsx | 7 + 4 files changed, 355 insertions(+) create mode 100644 src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx diff --git a/package-lock.json b/package-lock.json index b309af78..115a8d52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "@aws-sdk/client-s3": "^3.971.0", "@aws-sdk/client-sqs": "^3.864.0", "@aws-sdk/s3-request-presigner": "^3.927.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@headlessui/react": "^2.2.7", "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^3.9.1", @@ -1242,6 +1245,59 @@ "ms": "^2.1.1" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", diff --git a/package.json b/package.json index 4de599f4..e5fabc67 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "@aws-sdk/client-s3": "^3.971.0", "@aws-sdk/client-sqs": "^3.864.0", "@aws-sdk/s3-request-presigner": "^3.927.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@headlessui/react": "^2.2.7", "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^3.9.1", diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx new file mode 100644 index 00000000..0d106d1f --- /dev/null +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -0,0 +1,289 @@ +// "use client" + +// import { useState } from "react"; +// import { +// DropdownMenu, +// DropdownMenuContent, +// DropdownMenuTrigger, +// } from "@/app/shadcn_components/ui/dropdown-menu"; +// import { Button } from "@/app/shadcn_components/ui/button"; +// import { Checkbox } from "@/app/shadcn_components/ui/checkbox"; + +// export interface RecommendationsOptionsProps { +// onApply: (value: boolean) => Promise | void; +// disabled?: boolean; +// } + +// export function RecommendationsOptions({ +// onApply, disabled = false +// }: RecommendationsOptionsProps) { +// console.log("Generating Recommendations button"); + +// const [isApplying, setIsApplying] = useState(false); + +// return ( +// { +// if (open) { +// console.log("dropdown menu is open"); +// } +// }} +// > +// +// +// +// +// ) +// } + +import { useState } from "react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/app/shadcn_components/ui/dropdown-menu"; +import { Button } from "@/app/shadcn_components/ui/button"; +import { Checkbox } from "@/app/shadcn_components/ui/checkbox"; +import { Label } from "@/app/shadcn_components/ui/label"; +import { GripVertical } from "lucide-react"; +import { HelpCircle } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/app/shadcn_components/ui/tooltip"; + +import { + DndContext, + closestCenter, +} from "@dnd-kit/core"; +import { + SortableContext, + useSortable, + verticalListSortingStrategy, + arrayMove, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; + +export interface RecommendationsOptionsProps { + onApply: (value: { + selectedScenarios: number[] | null; + prioritisedScenarios: number[] | null; + }) => Promise | void; + disabled?: boolean; +} + +const fakeScenarios = [ + { id: 1, name: "EPC C" }, + { id: 2, name: "EPC C - Minor Works" }, +]; + +function SortableScenarioItem({ + id, + name, + index, +}: { + id: number; + name: string; + index: number; +}) { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = + useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + + return ( +
+ + + {name} + + + Priority {index + 1} + +
+ ); +} + +export function RecommendationsOptions({ + onApply, + disabled = false, +}: RecommendationsOptionsProps) { + const [isApplying, setIsApplying] = useState(false); + const [open, setOpen] = useState(false); + const [selectedScenarios, setSelectedScenarios] = useState([]); + + const toggleScenario = (id: number) => { + setSelectedScenarios((prev) => + prev.includes(id) + ? prev.filter((s) => s !== id) + : [...prev, id] + ); + }; + + const handleSelectAll = () => { + setSelectedScenarios(fakeScenarios.map((s) => s.id)); + }; + + const handleDeselectAll = () => { + setSelectedScenarios([]); + }; + + const handleDragEnd = (event: any) => { + const { active, over } = event; + if (!over || active.id === over.id) return; + + setSelectedScenarios((items) => { + const oldIndex = items.indexOf(active.id); + const newIndex = items.indexOf(over.id); + return arrayMove(items, oldIndex, newIndex); + }); + }; + + const handleSubmit = async () => { + setIsApplying(true); + + await onApply({ + selectedScenarios: + selectedScenarios.length > 0 ? selectedScenarios : null, + prioritisedScenarios: + selectedScenarios.length > 0 ? selectedScenarios : null, + }); + + setIsApplying(false); + setOpen(false); + }; + + const handleCancel = () => { + setSelectedScenarios([]); + setOpen(false); + }; + + const selectedScenarioObjects = selectedScenarios.map( + (id) => fakeScenarios.find((s) => s.id === id)! + ); + + return ( + + + + + + +
+ + +
+ +
+

Select scenarios

+ + {fakeScenarios.map((scenario) => ( +
+ toggleScenario(scenario.id)} + /> + +
+ ))} +
+ + {selectedScenarioObjects.length > 0 && ( +
+
+

Drag to prioritise

+ + + + + + + +

+ This decides the order of selection if multiple scenarios have equal + outputs. +

+
+
+
+
+ + + + {selectedScenarioObjects.map((scenario, index) => ( + + ))} + + +
+ )} + +
+ + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx index c0ec45a5..046c9db3 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx @@ -22,6 +22,7 @@ import type { ScenarioSummary, } from "./types"; import { ReportingFunctionalityButtons } from "./ReportingFunctionalityButtons"; +import { RecommendationsOptions } from "./RecommendationsOptions"; interface ReportingClientAreaProps { baseline: BaselineMetrics; @@ -266,6 +267,12 @@ export function ReportingClientArea({ )} + { + {console.log('Generat Recommendations')}} + disabled={scenarioBusy} + /> + } {/* LOADING + ERROR STATES */}