From cf821c9150081f69ab54940f4e22dcb5b4f250ac Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 27 Feb 2026 17:04:56 +0000 Subject: [PATCH 01/16] 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 */} From 2ae1eba6dbb61645736b00f52adea6da1b06ef91 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 27 Feb 2026 17:06:17 +0000 Subject: [PATCH 02/16] delete commented out code --- .../reporting/RecommendationsOptions.tsx | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx index 0d106d1f..0e404cab 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -1,54 +1,3 @@ -// "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, From 29541ebb21ca451c64260007866d28879e7a6c4b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 2 Mar 2026 09:29:52 +0000 Subject: [PATCH 03/16] use real scenarios in select --- .../reporting/RecommendationsOptions.tsx | 16 +++++++--------- .../reporting/ReportingClientArea.tsx | 1 + 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx index 0e404cab..e4632525 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -27,6 +27,7 @@ import { arrayMove, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; +import { ScenarioSummary } from "./types"; export interface RecommendationsOptionsProps { onApply: (value: { @@ -34,13 +35,9 @@ export interface RecommendationsOptionsProps { prioritisedScenarios: number[] | null; }) => Promise | void; disabled?: boolean; + scenarios: ScenarioSummary[] } -const fakeScenarios = [ - { id: 1, name: "EPC C" }, - { id: 2, name: "EPC C - Minor Works" }, -]; - function SortableScenarioItem({ id, name, @@ -84,6 +81,7 @@ function SortableScenarioItem({ export function RecommendationsOptions({ onApply, disabled = false, + scenarios }: RecommendationsOptionsProps) { const [isApplying, setIsApplying] = useState(false); const [open, setOpen] = useState(false); @@ -98,7 +96,7 @@ export function RecommendationsOptions({ }; const handleSelectAll = () => { - setSelectedScenarios(fakeScenarios.map((s) => s.id)); + setSelectedScenarios(scenarios.map((s) => s.id)); }; const handleDeselectAll = () => { @@ -136,7 +134,7 @@ export function RecommendationsOptions({ }; const selectedScenarioObjects = selectedScenarios.map( - (id) => fakeScenarios.find((s) => s.id === id)! + (id) => scenarios.find((s) => s.id === id)! ); return ( @@ -170,9 +168,9 @@ export function RecommendationsOptions({
-

Select scenarios

+

Select scenarios to consider

- {fakeScenarios.map((scenario) => ( + {scenarios.map((scenario) => (
{console.log('Generat Recommendations')}} disabled={scenarioBusy} + scenarios={scenarios} /> }
From d4b9c23a015fa55d6f9e2bcd5139c2cb67d1209d Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 2 Mar 2026 09:42:50 +0000 Subject: [PATCH 04/16] prevent single scenario being selected --- .../reporting/RecommendationsOptions.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx index e4632525..e67246a6 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -86,8 +86,10 @@ export function RecommendationsOptions({ const [isApplying, setIsApplying] = useState(false); const [open, setOpen] = useState(false); const [selectedScenarios, setSelectedScenarios] = useState([]); + const [warning, setWarning] = useState(null); const toggleScenario = (id: number) => { + setWarning("") setSelectedScenarios((prev) => prev.includes(id) ? prev.filter((s) => s !== id) @@ -96,10 +98,12 @@ export function RecommendationsOptions({ }; const handleSelectAll = () => { + setWarning("") setSelectedScenarios(scenarios.map((s) => s.id)); }; const handleDeselectAll = () => { + setWarning("") setSelectedScenarios([]); }; @@ -115,6 +119,13 @@ export function RecommendationsOptions({ }; const handleSubmit = async () => { + console.log('handleSubmit', selectedScenarios, selectedScenarios.length); + if (selectedScenarios.length === 1) { + setWarning("Cannot generate recommendations for a single scenario"); + return; + } + + setWarning(null); setIsApplying(true); await onApply({ @@ -127,8 +138,8 @@ export function RecommendationsOptions({ setIsApplying(false); setOpen(false); }; - const handleCancel = () => { + setWarning("") setSelectedScenarios([]); setOpen(false); }; @@ -222,6 +233,10 @@ export function RecommendationsOptions({
)} + {warning && ( +

{warning}

+ )} +
From 9299d2a76f15db4eec72f8a5845c62e12b6022bd Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 3 Mar 2026 11:16:05 +0000 Subject: [PATCH 08/16] Only show button if no scenario selected --- .../(portfolio)/reporting/RecommendationsOptions.tsx | 8 -------- .../[slug]/(portfolio)/reporting/ReportingClientArea.tsx | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx index 2180caa6..67ea5f65 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -30,7 +30,6 @@ import { CSS } from "@dnd-kit/utilities"; import { ScenarioSummary } from "./types"; export interface RecommendationsOptionsProps { - onApply: (value: CategorisationTriggerRequest) => void | Promise; disabled?: boolean; scenarios: ScenarioSummary[] portfolioId: number @@ -111,7 +110,6 @@ function SortableScenarioItem({ } export function RecommendationsOptions({ - onApply, disabled = false, scenarios, portfolioId @@ -182,12 +180,6 @@ export function RecommendationsOptions({ body: JSON.stringify(payload) }); - await onApply(payload.scenarios_to_consider ? payload : { - portfolio_id: 123, - scenarios_to_consider: null, - scenario_priority_order: null, - }); - setIsApplying(false); setOpen(false); }; diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx index e8b2ad43..f22ffa73 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx @@ -267,9 +267,8 @@ export function ReportingClientArea({
)} - { + { !selectedScenarioId && {console.log('Generat Recommendations')}} disabled={scenarioBusy} scenarios={scenarios} portfolioId={portfolioId} From 12fed5ae82b992885533ef4c2e7fc3137a2c9647 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 3 Mar 2026 11:30:57 +0000 Subject: [PATCH 09/16] add loader to button text when form is submitted --- .../[slug]/(portfolio)/reporting/RecommendationsOptions.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx index 67ea5f65..bc4aad32 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -9,6 +9,7 @@ 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 { Loader2 } from "lucide-react"; import { Tooltip, TooltipContent, @@ -290,7 +291,10 @@ export function RecommendationsOptions({ Cancel From cbf2d1705b295aa0f955485cb02282ccc79b89d2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 3 Mar 2026 12:23:17 +0000 Subject: [PATCH 10/16] Add success toast --- .../components/building-passport/Toolbar.tsx | 5 +++-- .../reporting/RecommendationsOptions.tsx | 6 ++++- .../reporting/ReportingClientArea.tsx | 12 ++++++++++ ...okingSuccessToast.tsx => SuccessToast.tsx} | 22 +++++++++++-------- 4 files changed, 33 insertions(+), 12 deletions(-) rename src/app/portfolio/[slug]/components/{BookingSuccessToast.tsx => SuccessToast.tsx} (84%) diff --git a/src/app/components/building-passport/Toolbar.tsx b/src/app/components/building-passport/Toolbar.tsx index aef25052..66670d04 100644 --- a/src/app/components/building-passport/Toolbar.tsx +++ b/src/app/components/building-passport/Toolbar.tsx @@ -21,7 +21,7 @@ import { Button } from "@/app/shadcn_components/ui/button"; import { cva } from "class-variance-authority"; import { getUploadedFile } from "@/app/db/surveyDB/schema/surveyDB"; import BookSurveyModal from "@/app/portfolio/[slug]/components/BookSurveyModal"; -import BookingSuccessToast from "@/app/portfolio/[slug]/components/BookingSuccessToast"; +import SuccessToast from "@/app/portfolio/[slug]/components/SuccessToast"; import { PropertyMeta } from "@/app/db/schema/property"; interface ToolbarProps { @@ -179,8 +179,9 @@ export function Toolbar({ )} {/* βœ… Toast */} - setShowToast(false)} message="Survey Request Recieved!" subtext="We'll be in contact soon. πŸŽ‰" diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx index bc4aad32..5d740bc8 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -34,6 +34,7 @@ export interface RecommendationsOptionsProps { disabled?: boolean; scenarios: ScenarioSummary[] portfolioId: number + onSuccess: () => void; } interface ScenarioWithPriority { @@ -113,7 +114,8 @@ function SortableScenarioItem({ export function RecommendationsOptions({ disabled = false, scenarios, - portfolioId + portfolioId, + onSuccess }: RecommendationsOptionsProps) { const [isApplying, setIsApplying] = useState(false); const [open, setOpen] = useState(false); @@ -181,6 +183,8 @@ export function RecommendationsOptions({ body: JSON.stringify(payload) }); + onSuccess(); + setIsApplying(false); setOpen(false); }; diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx index f22ffa73..ee0198d7 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx @@ -23,6 +23,7 @@ import type { } from "./types"; import { ReportingFunctionalityButtons } from "./ReportingFunctionalityButtons"; import { RecommendationsOptions } from "./RecommendationsOptions"; +import SuccessToast from "../../components/SuccessToast"; interface ReportingClientAreaProps { baseline: BaselineMetrics; @@ -88,6 +89,7 @@ export function ReportingClientArea({ const [measuresOpen, setMeasuresOpen] = useState(false); const [appliedHideNonCompliant, setAppliedHideNonCompliant] = useState(false); + const [showToast, setShowToast] = useState(false); const drawerOpen = Boolean(selectedScenarioId); @@ -272,6 +274,7 @@ export function ReportingClientArea({ disabled={scenarioBusy} scenarios={scenarios} portfolioId={portfolioId} + onSuccess={() => setShowToast(true)} /> } @@ -346,6 +349,15 @@ export function ReportingClientArea({ data={measuresData ?? null} error={measuresError} /> + + setShowToast(true)} + message="Recommendation process triggered" + subtext="Recommendations might take a few minutes to update" + timeoutMs={6000} + /> ); } diff --git a/src/app/portfolio/[slug]/components/BookingSuccessToast.tsx b/src/app/portfolio/[slug]/components/SuccessToast.tsx similarity index 84% rename from src/app/portfolio/[slug]/components/BookingSuccessToast.tsx rename to src/app/portfolio/[slug]/components/SuccessToast.tsx index 7b9410d8..20567a5b 100644 --- a/src/app/portfolio/[slug]/components/BookingSuccessToast.tsx +++ b/src/app/portfolio/[slug]/components/SuccessToast.tsx @@ -5,28 +5,32 @@ import { motion, AnimatePresence } from "framer-motion"; import Confetti from "react-confetti"; import { CheckCircle } from "lucide-react"; -interface BookingSuccessToastProps { +interface SuccessToastProps { show: boolean; + showConfetti: boolean; onClose: () => void; - message?: string; - subtext?: string; + message: string; + subtext: string; + timeoutMs?: number; } -export default function BookingSuccessToast({ +export default function SuccessToast({ show, + showConfetti, onClose, - message = "Booking Confirmed!", - subtext = "You’re all set. πŸŽ‰", -}: BookingSuccessToastProps) { + message, + subtext, + timeoutMs = 4000 +}: SuccessToastProps) { const [confetti, setConfetti] = useState(false); useEffect(() => { if (show) { - setConfetti(true); + setConfetti(showConfetti); const timer = setTimeout(() => { setConfetti(false); onClose(); - }, 4000); + }, timeoutMs); return () => clearTimeout(timer); } }, [show, onClose]); From 40c0674d71e0d70305469f296e68ca4e2d59a73b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 4 Mar 2026 11:38:17 +0000 Subject: [PATCH 11/16] readme commit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 081be45a..6937a3e0 100644 --- a/README.md +++ b/README.md @@ -132,4 +132,4 @@ the permission set to access the bucket, `rerofit-plan-inputs-`. The name Quick wins: -- [] Frequently asked questions page +- [] Frequently asked questions page. From 3d4d3c0392e331028d337c77da62dcb48da904da Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 4 Mar 2026 14:52:24 +0000 Subject: [PATCH 12/16] turn off confetti on success toast --- .../[slug]/(portfolio)/reporting/ReportingClientArea.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx index ee0198d7..1148404a 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx @@ -352,7 +352,7 @@ export function ReportingClientArea({ setShowToast(true)} message="Recommendation process triggered" subtext="Recommendations might take a few minutes to update" From cd438569da4ef72fac165c99d00f4e12d5a258d6 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 4 Mar 2026 15:01:17 +0000 Subject: [PATCH 13/16] hide toast after timeout completes --- .../[slug]/(portfolio)/reporting/ReportingClientArea.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx index 1148404a..47dfeaf8 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/ReportingClientArea.tsx @@ -353,7 +353,7 @@ export function ReportingClientArea({ setShowToast(true)} + onClose={() => setShowToast(false)} message="Recommendation process triggered" subtext="Recommendations might take a few minutes to update" timeoutMs={6000} From c69d00250ac80694428b7cba3a0806d5d0c3acdd Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 4 Mar 2026 15:07:09 +0000 Subject: [PATCH 14/16] add max height and overflow to dropdown form --- .../[slug]/(portfolio)/reporting/RecommendationsOptions.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx index 5d740bc8..7e89d1db 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/RecommendationsOptions.tsx @@ -164,7 +164,6 @@ export function RecommendationsOptions({ }; const handleSubmit = async () => { - console.log('handleSubmit', selectedScenarios); if (selectedScenarios.length === 1) { setWarning("Cannot generate recommendations for a single scenario"); return; @@ -175,8 +174,6 @@ export function RecommendationsOptions({ const payload = mapScenariosToPayload(selectedScenarios, portfolioId); - console.log('API payload', JSON.stringify(payload)); - const response = await fetch("/api/plan/categorisation", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -221,7 +218,7 @@ export function RecommendationsOptions({ - +