From 9ee41c9b8a2b471d5357ab901471b8185b7b3b96 Mon Sep 17 00:00:00 2001 From: vkuchenik Date: Tue, 21 Jul 2026 16:07:59 -0700 Subject: [PATCH 01/22] Added an Alert Level Change Quad Plot Widget --- .../Widgets/LlmAlChangesQuadPlot.jsx | 84 +++++++++++++++++++ .../Widgets/LlmAlChangesQuadPlot.stories.jsx | 73 ++++++++++++++++ client/src/constants/widgetRegistry.jsx | 11 +++ 3 files changed, 168 insertions(+) create mode 100644 client/src/components/Widgets/LlmAlChangesQuadPlot.jsx create mode 100644 client/src/components/Widgets/LlmAlChangesQuadPlot.stories.jsx diff --git a/client/src/components/Widgets/LlmAlChangesQuadPlot.jsx b/client/src/components/Widgets/LlmAlChangesQuadPlot.jsx new file mode 100644 index 0000000..f8ff839 --- /dev/null +++ b/client/src/components/Widgets/LlmAlChangesQuadPlot.jsx @@ -0,0 +1,84 @@ +import VdapApi from '../../hooks/useVdapApi.js'; +import {Flex, Spinner, Text, Box, Center} from "@chakra-ui/react"; // Adjust path as needed + + +export default function LlmAlChangesQuadPlot(props) { + console.log("3. Quad Plot loaded"); + let tp_percent = 0; + let tn_percent = 0; + let fp_percent = 0; + let fn_percent = 0; + + // Clean, single-line data fetching + const { data: apiData, isLoading, error } = VdapApi('/api/v2/llm_quadrant_plot'); + + if (isLoading) return ( + + + + ); + + if (error) return ( + + {error} + + ); + + if (apiData.length === 0) return ( + + No alert level quad plot data available. + + ); + + if (apiData) { + console.log(apiData); + tp_percent = ((apiData[0]['tp_count']/apiData[0]['tot_count'])*100).toFixed(2); + tn_percent = ((apiData[0]['tn_count']/apiData[0]['tot_count'])*100).toFixed(2); + fp_percent = ((apiData[0]['fp_count']/apiData[0]['tot_count'])*100).toFixed(2); + fn_percent = ((apiData[0]['fn_count']/apiData[0]['tot_count'])*100).toFixed(2); + } + + return ( + + + {/*Alert Level Changes Quad Plot*/} + All time + + + +
+ + True Positive + {tp_percent}% + {apiData[0]['tp_count']}/{apiData[0]['tot_count']} + +
+
+ + True Negative + {tn_percent}% + {apiData[0]['tn_count']}/{apiData[0]['tot_count']} + +
+
+ +
+ + False Positive + {fp_percent}% + {apiData[0]['fp_count']}/{apiData[0]['tot_count']} + +
+
+ + False Negative + {fn_percent}% + {apiData[0]['fn_count']}/{apiData[0]['tot_count']} + +
+
+
+
+ ) + +} \ No newline at end of file diff --git a/client/src/components/Widgets/LlmAlChangesQuadPlot.stories.jsx b/client/src/components/Widgets/LlmAlChangesQuadPlot.stories.jsx new file mode 100644 index 0000000..42ba8fc --- /dev/null +++ b/client/src/components/Widgets/LlmAlChangesQuadPlot.stories.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { Box } from '@chakra-ui/react'; +import { http, HttpResponse } from 'msw'; // Import MSW +import LlmAlChangesQuadPlot from './LlmAlChangesQuadPlot'; + +export default { + title: 'Dashboard Widgets/LLM AL Changes Quad Plot', + component: LlmAlChangesQuadPlot, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +const mockData = [ + { tp: 50 }, + { tn: 250 }, + { fp: 10 }, + { fn: 5 }, + { tot_count: 315} +]; + +// Scenario LIVE: call our API for real +export const LivePopulated = {}; + +// Scenario 1: Successful API Call +export const Populated = { + parameters: { + msw: { + handlers: [ + // Intercept the exact URL your component is trying to fetch + http.get('*/api/v2/llm_quadrant_plot', () => { + // Return a 200 OK with the mock JSON + return HttpResponse.json(mockData); + }), + ], + }, + }, +}; + +// Scenario 2: The 500 Server Crash +export const ServerCrash = { + parameters: { + msw: { + handlers: [ + http.get('*/api/v2/llm_quad_plot', () => { + // Return a 500 Error + return new HttpResponse(null, { status: 500 }); + }), + ], + }, + }, +}; + +// Scenario 3: Empty State (API works, but no data returned) +export const EmptyData = { + parameters: { + msw: { + handlers: [ + http.get('*/api/v2/llm_quad_plot', () => { + return HttpResponse.json([]); + }), + ], + }, + }, +}; \ No newline at end of file diff --git a/client/src/constants/widgetRegistry.jsx b/client/src/constants/widgetRegistry.jsx index 3e763da..5b547bd 100644 --- a/client/src/constants/widgetRegistry.jsx +++ b/client/src/constants/widgetRegistry.jsx @@ -85,4 +85,15 @@ export const WIDGET_REGISTRY = { } }, + "llm_al_changes_quad_plot": { + name: "Alert Level Changes Quad Plot", + category: "Computer Science", + description: "Percentage of True-Positive, True-Negative, False-Positive, and False-Negative changes in all " + + "alert levels.", + component: lazy(() => import('../components/Widgets/LlmAlChangesQuadPlot.jsx')), + allowedSizes: { + small: { w: 3, h: 12 } + } + } + }; \ No newline at end of file From 764bf07ac8d04bbd016dd047297c8898129787dc Mon Sep 17 00:00:00 2001 From: vkuchenik Date: Wed, 22 Jul 2026 08:46:58 -0700 Subject: [PATCH 02/22] Fixed weird menu positioning on open, now opens below settings button --- .../components/dashboard/SettingsButton.jsx | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index fa3aff3..d1fbfb0 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from "react"; +import { useState, useMemo, useRef } from "react"; import { Button, IconButton, @@ -46,12 +46,20 @@ export function SettingsButton() { const categories = Object.keys(categorizedWidgets); + // use ref to capture html node for placing menu accordingly + const menuRef = useRef(null); + const getAnchorRect = () => { + if (!menuRef.current) return null; + return menuRef.current.getBoundingClientRect(); + }; + return ( <> {/* THE STATIC DROPDOWN MENU */} - + + + + {isEditing ? ( + <> + {/* WIRE DIRECTLY TO saveEdit */} + + Save Layout + - - {isEditing ? ( - <> - {/* WIRE DIRECTLY TO saveEdit */} - - Save Layout + + Discard Changes + + + ) : ( + + Edit Layout + )} - - Discard Changes - - - ) : ( - - Edit Layout + + + setIsLibraryOpen(true)} cursor="pointer" _hover={{ bg: "gray.100" }}> + Widget Library - )} - - - - setIsLibraryOpen(true)} cursor="pointer" _hover={{ bg: "gray.100" }}> - Widget Library - - + + {/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} From 35eea905a3b5faf7e06bb3299d18d1b9fe86633f Mon Sep 17 00:00:00 2001 From: vkuchenik Date: Wed, 22 Jul 2026 09:40:41 -0700 Subject: [PATCH 03/22] Fixed the tabs in the canvas not transitioning colors to black when active and Updated canvas coloring in 'coming soon' pages --- client/src/App.jsx | 6 +++--- client/src/pages/home/Home.jsx | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/client/src/App.jsx b/client/src/App.jsx index 9ab481b..95e89c7 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -48,8 +48,8 @@ function AppFoundation() { } const Placeholder = ({ name }) => ( - - {name} Dashboard - Coming Soon + + {name} Dashboard - Coming Soon ); @@ -75,7 +75,7 @@ function App() { }> } /> - } /> + } /> } /> } /> } /> diff --git a/client/src/pages/home/Home.jsx b/client/src/pages/home/Home.jsx index 8816dbf..ebbc3d1 100644 --- a/client/src/pages/home/Home.jsx +++ b/client/src/pages/home/Home.jsx @@ -10,17 +10,17 @@ export default function Home() { // 1. CSS making this look like how we want it, TODO turn to theme? const tabStyles = { fontSize: "lg", - fontWeight: "normal", + fontWeight: "bold", color: "fg.muted", - px: 4, // Horizontal padding - py: 2, // Vertical padding - borderBottom: "2px solid transparent", - mb: "-2px", // πŸ”₯ The trick: Pulls the active border down to overlap the container's line + px: 2, // Horizontal padding + // py: 2, // Vertical padding + borderBottom: "1px solid transparent", transition: "all 0.2s", _hover: { color: "fg" }, - _activeLink: { - color: "color", // Or whatever your primary text color is - borderColor: "currentColor", // The active underline + + "&.active": { // Target the active class applied by React Router NavLink + color: "fg", + borderColor: "fg", } }; @@ -31,12 +31,12 @@ export default function Home() { {/* TABS CONTAINER - Replicates the Tabs.List line container */} - + My Dashboard - Gas - Seismic - Remote Sensing Daily Activity + Gas + Remote Sensing + Seismic {/* EDIT MODE BUTTONS & CONTROLS */} From 022d4169b5043620d0e885b704d927bf01341e2d Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Wed, 22 Jul 2026 10:37:01 -0700 Subject: [PATCH 04/22] Reformat starting changes on layout for better UI visuals --- client/src/App.jsx | 10 +++--- client/src/components/layout/Header.jsx | 8 ++--- client/src/components/layout/Sidebar.jsx | 39 ++++++++++++++++++++-- client/src/pages/home/tabs/MyDashboard.jsx | 2 +- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/client/src/App.jsx b/client/src/App.jsx index 9ab481b..287e35d 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -33,12 +33,12 @@ function AppFoundation() { height="100vh" width="100vw" > - -
- - + + +
+ @@ -75,7 +75,7 @@ function App() { }> } /> - } /> + } /> } /> } /> } /> diff --git a/client/src/components/layout/Header.jsx b/client/src/components/layout/Header.jsx index 47adb32..42077af 100644 --- a/client/src/components/layout/Header.jsx +++ b/client/src/components/layout/Header.jsx @@ -19,10 +19,10 @@ export default function Header() { return ( - {/* Home Link Logo */} - + {/*/!* Home Link Logo *!/*/} + {/**/} {/* User Profile Section */} diff --git a/client/src/components/layout/Sidebar.jsx b/client/src/components/layout/Sidebar.jsx index 1ca6b77..b1f1c8c 100644 --- a/client/src/components/layout/Sidebar.jsx +++ b/client/src/components/layout/Sidebar.jsx @@ -24,10 +24,42 @@ export default function Sidebar() { const toggleSidebar = () => setIsOpen((prev) => !prev); return ( - + {/* Home Link Logo */} + + {/* Menu Trigger */} - @@ -67,7 +99,8 @@ export default function Sidebar() { ); diff --git a/client/src/pages/home/tabs/MyDashboard.jsx b/client/src/pages/home/tabs/MyDashboard.jsx index 05f2c24..e56f765 100644 --- a/client/src/pages/home/tabs/MyDashboard.jsx +++ b/client/src/pages/home/tabs/MyDashboard.jsx @@ -15,7 +15,7 @@ const ResponsiveGridLayout = WidthProvider(Responsive); export default function MyDashboard() { // πŸŒ— Let's adjust for our light and dark mode - const frameBg = useColorModeValue("gray.100", "gray.800"); + const frameBg = useColorModeValue("gray.100", "#1E1E2B"); const titleColor = useColorModeValue("gray.800", "gray.100"); const iconColor = useColorModeValue("gray.600", "gray.400"); const iconHoverBg = useColorModeValue("gray.100", "gray.700"); From 9fbf22bc32c4a751ed54b011ef4e05dc4b5f58a8 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Wed, 22 Jul 2026 14:58:59 -0700 Subject: [PATCH 05/22] Refactor still working on UI changes --- client/src/components/layout/Header.jsx | 16 +++++---- client/src/components/layout/Sidebar.jsx | 6 ++-- client/src/pages/home/Home.jsx | 4 +-- client/src/theme.js | 41 ++++++++++++++++-------- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/client/src/components/layout/Header.jsx b/client/src/components/layout/Header.jsx index 42077af..5259c35 100644 --- a/client/src/components/layout/Header.jsx +++ b/client/src/components/layout/Header.jsx @@ -2,6 +2,7 @@ import { useRecipe, Button, Stack, Text, Flex } from "@chakra-ui/react" import UserAvatarMenu from "./UserAvatarMenu.jsx"; import { NavLink } from "react-router-dom"; import useAuthStore from "@/store/authStore"; +import {SearchBar} from "@/components/dashboard/SearchBar.jsx"; // Import the asset properly so Vite can bundle it for any server // import defaultAvatar from "@/assets/user_profile.svg"; @@ -13,7 +14,8 @@ export default function Header() { const { user } = useAuthStore(); console.log(user); - const displayName = user ? (user.fullName || user.username) : "Loading..."; + const displayName = user ? (user.firstName || user.username) : "Loading..."; + const avatarname = user ? (user.fullName || user.username) : "Loading..."; const displayEmail = user?.email || "LDAP User"; @@ -23,15 +25,17 @@ export default function Header() { {/**/} + {/* User Profile Section */} - - {displayName} - {displayEmail} - + {displayName} + {/**/} + {/* {displayName}*/} + {/* {displayEmail}*/} + {/**/} diff --git a/client/src/components/layout/Sidebar.jsx b/client/src/components/layout/Sidebar.jsx index b1f1c8c..4a513af 100644 --- a/client/src/components/layout/Sidebar.jsx +++ b/client/src/components/layout/Sidebar.jsx @@ -40,15 +40,15 @@ export default function Sidebar() { to="/home" color="color" variant="plain" - textStyle={isOpen ? "5xl" : "4xl"} // Scale down slightly when closed fontWeight="bold" height="auto" // Prevents the large text from squashing vertically overflow="hidden" px={0} - justifyContent={isOpen ? "flex-start" : "center"} // Center the 'V' when collapsed + justifyContent={isOpen ? "center" : "center"} // Center the 'V' when collapsed ml={isOpen ? 3 : 0} // Align with other buttons when open + textStyle={isOpen ? "4xl" : "3xl"} // Scale down slightly when closed > - {isOpen ? "VDAP" : "V"} + {isOpen ? "πŸŒ‹ VDAP" : "πŸŒ‹"} {/* Menu Trigger */} diff --git a/client/src/pages/home/Home.jsx b/client/src/pages/home/Home.jsx index ebbc3d1..fab657a 100644 --- a/client/src/pages/home/Home.jsx +++ b/client/src/pages/home/Home.jsx @@ -28,7 +28,7 @@ export default function Home() { {/* 2. πŸ› οΈ SUB HEADER: Matches your original padding and background */} - + {/* TABS CONTAINER - Replicates the Tabs.List line container */} @@ -48,7 +48,7 @@ export default function Home() { ) : ( - + )} diff --git a/client/src/theme.js b/client/src/theme.js index c0200ba..a565b37 100644 --- a/client/src/theme.js +++ b/client/src/theme.js @@ -3,12 +3,10 @@ import { defineSlotRecipe, createSystem, defaultConfig, defineConfig, defineReci // RECIPES const headerRecipe = defineRecipe({ base: { - bg: "{base200}", + bg: "{transparent}", color: "text", width: "100%", height: "100%", - borderBottom: "1px solid", - borderBottomColor:"{border}", display: "flex", flexDirection: "row", justifyContent: "space-between", @@ -18,13 +16,13 @@ const headerRecipe = defineRecipe({ const dashboardRecipe = defineRecipe({ base: { - bg: "{base200}" + bg: "{testBackground}" } }); const canvasRecipe = defineRecipe({ base: { - bg: "{base200}", + bg: "{transparent}", zIndex: "-1", color: "{text}" } @@ -34,22 +32,35 @@ const sidebarRecipe = defineSlotRecipe({ slots: ["root", "close_button", "buttons", "dark_mode_button"], base: { root: { - bg: "{base200}", - borderRight: "1px solid", - borderColor: "{border}", + // 1. Semi-transparent backgrounds + bg: { base: "linear-gradient(to bottom right, #BBBBBB4C, #FFFFFF4C)", _dark: "rgba(20, 20, 20, 0.4)" }, + + // 2. The glass blur effect + backdropFilter: "blur(12px)", + WebkitBackdropFilter: "blur(12px)", // For Safari compatibility + + // 3. Subtle semi-transparent borders to catch the light + border: "1px solid", + borderColor: { base: "rgba(255, 255, 255, 0.3)", _dark: "rgba(255, 255, 255, 0.1)" }, + + // Your existing layout properties + borderRadius: "2xl", + boxShadow: "0 4px 30px rgba(0, 0, 0, 0.1)", // A softer shadow helps the glass pop width: "100%", - height: "100%", + height: "calc(100% - 16px)", + margin: "8px", display: "flex", flexDirection: "column", - justifyContent: "start", + justifyContent: "start", alignItems: "start", gap: 2, p: 2, + zIndex: 10, }, buttons: { bg: "transparent", - _hover: {bg: "{primaryLt}", color: "{textInverted}"}, - "&.active": {bg: "{primary}", color: "{textInverted}"}, + _hover: {bg: "{testButton}", color: "{textInverted}"}, + "&.active": {bg: "{testButton}", color: "{textInverted}"}, fontSize: "xl", color: {base: "{text}"}, variant: "ghost", @@ -179,8 +190,12 @@ const config = defineConfig({ secondaryBG: { value: {base: "#EDF1F1", _dark: "#121212"}}, border: {value: {base: "#000000", _dark: "#cbcbcb84"}}, text: {value: {base: "#000000", _dark: "lightgrey"}}, - textInverted: {value: "white"}, + textInverted: {value: "#000000"}, icon: {value: {base: "#000000", _dark: "lightgrey"}}, + testBackground: { value: {base: "linear-gradient(to bottom right, #BBBBBB, #FFFFFF)", _dark: "#191613"} }, + testSidebar: { value: {base: "#FBFBFB", _dark: "#272421"} }, + testButton: { value: {base: "#FFFFFF", _dark: "#C09754"} }, + }, }, tokens: { From 3697575f548140611751246302207d0081f0e3fc Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Thu, 23 Jul 2026 15:47:51 -0700 Subject: [PATCH 06/22] Refactor the UI for light mode is at an acceptable point --- client/src/components/layout/Sidebar.jsx | 54 ++++++++------ client/src/constants/widgetRegistry.jsx | 2 +- client/src/theme.js | 93 +++++++++++++++++++++--- 3 files changed, 112 insertions(+), 37 deletions(-) diff --git a/client/src/components/layout/Sidebar.jsx b/client/src/components/layout/Sidebar.jsx index 4a513af..68d2dbf 100644 --- a/client/src/components/layout/Sidebar.jsx +++ b/client/src/components/layout/Sidebar.jsx @@ -3,7 +3,7 @@ import { ColorModeButton } from "@/components/ui/color-mode.jsx"; import { useState } from "react"; import { NavLink } from "react-router-dom"; import Tooltip from "../ui/Tooltip.jsx"; -import { Mountain, House, Earth, MapPin, ShieldUser, Menu } from 'lucide-react'; +import { Mountain, House, Earth, MapPin, ShieldUser, Menu, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; const SIDEBAR_DEFAULT_BUTTONS = [ { icon: House, name: "Home", url: "/home" }, @@ -26,7 +26,7 @@ export default function Sidebar() { return ( - {/* Menu Trigger */} - - - + {/* Navigation Buttons */} {visibleButtons.map((button) => { const Icon = button.icon; return ( - + // - + // ); })} + + {/* Menu Trigger */} + + {isOpen ? : } + - {/* Theme Toggler */} - + {/* Theme Toggler */} + + ); } \ No newline at end of file diff --git a/client/src/constants/widgetRegistry.jsx b/client/src/constants/widgetRegistry.jsx index 5b547bd..3ee9d66 100644 --- a/client/src/constants/widgetRegistry.jsx +++ b/client/src/constants/widgetRegistry.jsx @@ -78,7 +78,7 @@ export const WIDGET_REGISTRY = { // The baseline data every new VAA widget starts with defaultSettings: { - yAxisScale: 'normal' // Options: 'normal' | 'symlog' + yAxisScale: 'log' // Options: 'normal' | 'symlog' }, allowedSizes: { medium: { w: 6, h: 12 } diff --git a/client/src/theme.js b/client/src/theme.js index a565b37..58ac230 100644 --- a/client/src/theme.js +++ b/client/src/theme.js @@ -33,38 +33,107 @@ const sidebarRecipe = defineSlotRecipe({ base: { root: { // 1. Semi-transparent backgrounds - bg: { base: "linear-gradient(to bottom right, #BBBBBB4C, #FFFFFF4C)", _dark: "rgba(20, 20, 20, 0.4)" }, + bg: { base: "linear-gradient(to bottom right, #99000040 20%, #FF660040 60%, #EEEE0040 100%)", _dark: "rgba(20, 20, 20, 0.4)" }, // 2. The glass blur effect backdropFilter: "blur(12px)", - WebkitBackdropFilter: "blur(12px)", // For Safari compatibility + WebkitBackdropFilter: "blur(12px)", - // 3. Subtle semi-transparent borders to catch the light + // 3. Subtle semi-transparent borders border: "1px solid", - borderColor: { base: "rgba(255, 255, 255, 0.3)", _dark: "rgba(255, 255, 255, 0.1)" }, + borderColor: { base: "rgba(255, 255, 255, 0.8)", _dark: "rgba(255, 255, 255, 0.1)" }, + + // CRITICAL: Remove the right border so it doesn't draw a line through your cutout + borderRight: "none", - // Your existing layout properties borderRadius: "2xl", - boxShadow: "0 4px 30px rgba(0, 0, 0, 0.1)", // A softer shadow helps the glass pop + // boxShadow: "0 4px 30px rgba(0, 0, 0, 0.1)", + // boxShadow: "-10px 4px 30px rgba(0, 0, 0, 0.1)", width: "100%", height: "calc(100% - 16px)", margin: "8px", display: "flex", flexDirection: "column", justifyContent: "start", - alignItems: "start", + alignItems: "stretch", gap: 2, - p: 2, + + // CRITICAL: Adjust padding and overflow to allow the button to touch the right edge + pl: 2, // Keep left padding + py: 2, // Keep top/bottom padding + pr: 0, // Remove right padding + overflow: "visible", // Allows the curved corner patches to render outside the button zIndex: 10, }, buttons: { + position: "relative", // Required to anchor the corner patches bg: "transparent", - _hover: {bg: "{testButton}", color: "{textInverted}"}, - "&.active": {bg: "{testButton}", color: "{textInverted}"}, - fontSize: "xl", + + transition: "background-color 0.4s ease, color 0.4s ease", + + "& svg": { + transition: "stroke-width 0.4s ease" + }, + + _hover: { + bg: "rgba(255, 255, 255, 0.1)", + color: "{text}", + fontWeight: "semibold", + "& svg": { + strokeWidth: "3" + } + }, + + fontSize: "l", color: {base: "{text}"}, variant: "ghost", width: "100%", + + // Flat on the right, rounded on the left + borderRightRadius: "0", + borderLeftRadius: "4xl", + + "&.active": { + bg: "{testBackground}", + color: "{textInverted}", + + _hover: { + bg: "{testBackground}", + color: "{textInverted}", + fontWeight: "normal", + "& svg": { + strokeWidth: "2" // Keeps the active icon at normal thickness on hover + } + }, + + // The Top Inverted Curve + _before: { + content: '""', + position: "absolute", + right: "-1px", + top: "-25px", + width: "25px", + height: "25px", + bg: "inherit", // This forces the curve to match {testButton} + WebkitMaskImage: "radial-gradient(circle at top left, transparent 25px, black 25.5px)", + maskImage: "radial-gradient(circle at top left, transparent 25px, black 25.5px)", + pointerEvents: "none", + }, + + // The Bottom Inverted Curve + _after: { + content: '""', + position: "absolute", + right: "-1px", + bottom: "-25px", + width: "25px", + height: "25px", + bg: "inherit", + WebkitMaskImage: "radial-gradient(circle at bottom left, transparent 25px, black 25.5px)", + maskImage: "radial-gradient(circle at bottom left, transparent 25px, black 25.5px)", + pointerEvents: "none", + } + }, }, } }); @@ -192,7 +261,7 @@ const config = defineConfig({ text: {value: {base: "#000000", _dark: "lightgrey"}}, textInverted: {value: "#000000"}, icon: {value: {base: "#000000", _dark: "lightgrey"}}, - testBackground: { value: {base: "linear-gradient(to bottom right, #BBBBBB, #FFFFFF)", _dark: "#191613"} }, + testBackground: { value: {base: "#F9F9F9", _dark: "#191613"} }, testSidebar: { value: {base: "#FBFBFB", _dark: "#272421"} }, testButton: { value: {base: "#FFFFFF", _dark: "#C09754"} }, From 2283d156eac8c8f6784309329da021c5e3caf07b Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Fri, 24 Jul 2026 14:11:17 -0700 Subject: [PATCH 07/22] Refactoring some theme colors --- client/src/pages/home/tabs/MyDashboard.jsx | 7 +++---- client/src/theme.js | 10 +++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/client/src/pages/home/tabs/MyDashboard.jsx b/client/src/pages/home/tabs/MyDashboard.jsx index e56f765..e062b6c 100644 --- a/client/src/pages/home/tabs/MyDashboard.jsx +++ b/client/src/pages/home/tabs/MyDashboard.jsx @@ -15,9 +15,9 @@ const ResponsiveGridLayout = WidthProvider(Responsive); export default function MyDashboard() { // πŸŒ— Let's adjust for our light and dark mode - const frameBg = useColorModeValue("gray.100", "#1E1E2B"); - const titleColor = useColorModeValue("gray.800", "gray.100"); - const iconColor = useColorModeValue("gray.600", "gray.400"); + const frameBg = useColorModeValue("FFFFFF0A", "#FFFFFF0A"); + const titleColor = useColorModeValue("gray.800", "#FFFFFF"); + const iconColor = useColorModeValue("gray.600", "#FFFFFF"); const iconHoverBg = useColorModeValue("gray.100", "gray.700"); // ☁️ Database Connection @@ -154,7 +154,6 @@ export default function MyDashboard() { {widgets.map(widget => ( Date: Wed, 29 Jul 2026 13:48:38 -0700 Subject: [PATCH 08/22] Refactor continuing to work on UI --- .../components/dashboard/SettingsButton.jsx | 292 +++++++------- .../deprecated/AlertLevelWidget.jsx | 79 ---- .../components/deprecated/CancelButton.jsx | 7 - client/src/components/deprecated/Canvas.jsx | 39 -- .../deprecated/CustomResizeHandle.jsx | 27 -- .../src/components/deprecated/Dashboard.jsx | 95 ----- .../components/deprecated/DeleteAllButton.jsx | 9 - .../deprecated/FirstWidgetButton.jsx | 57 --- .../src/components/deprecated/SaveButton.jsx | 7 - client/src/components/deprecated/Widget.jsx | 32 -- .../src/components/deprecated/WidgetMenu.jsx | 26 -- client/src/components/deprecated/tooltip.jsx | 35 -- client/src/pages/home/Home.jsx | 101 ++--- client/src/pages/home/tabs/MyDashboard.jsx | 355 +++++++++--------- client/src/theme.js | 196 ++++++++-- 15 files changed, 545 insertions(+), 812 deletions(-) delete mode 100644 client/src/components/deprecated/AlertLevelWidget.jsx delete mode 100644 client/src/components/deprecated/CancelButton.jsx delete mode 100644 client/src/components/deprecated/Canvas.jsx delete mode 100644 client/src/components/deprecated/CustomResizeHandle.jsx delete mode 100644 client/src/components/deprecated/Dashboard.jsx delete mode 100644 client/src/components/deprecated/DeleteAllButton.jsx delete mode 100644 client/src/components/deprecated/FirstWidgetButton.jsx delete mode 100644 client/src/components/deprecated/SaveButton.jsx delete mode 100644 client/src/components/deprecated/Widget.jsx delete mode 100644 client/src/components/deprecated/WidgetMenu.jsx delete mode 100644 client/src/components/deprecated/tooltip.jsx diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index d1fbfb0..cf2a376 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -1,162 +1,170 @@ -import { useState, useMemo, useRef } from "react"; +import {useState, useMemo, useRef} from "react"; import { - Button, - IconButton, - Box, - Text, - Flex, - Grid, - Dialog, - Tabs, - Badge, - Menu, - HStack + Button, + Box, + Text, + Flex, + Grid, + Dialog, + Tabs, + Badge, + Menu, + HStack, + useRecipe, useSlotRecipe } from "@chakra-ui/react"; -import { Settings, Plus, LayoutGrid, Move, Save, X } from 'lucide-react'; -import { useDashboardStore } from "../../store/useDashboardStore.jsx"; -import { WIDGET_REGISTRY } from "../../constants/widgetRegistry.jsx"; +import {Settings, Plus, LayoutGrid, Move, Save, X} from 'lucide-react'; +import {useDashboardStore} from "../../store/useDashboardStore.jsx"; +import {WIDGET_REGISTRY} from "../../constants/widgetRegistry.jsx"; export function SettingsButton() { - // 1. ☁Store connections - const isEditing = useDashboardStore(state => state.isEditing); - const enterEditMode = useDashboardStore(state => state.enterEditMode); + // 1. ☁Store connections + const isEditing = useDashboardStore(state => state.isEditing); + const enterEditMode = useDashboardStore(state => state.enterEditMode); + const saveEdit = useDashboardStore(state => state.saveEdit); + const cancelEdit = useDashboardStore(state => state.cancelEdit); + const addWidget = useDashboardStore(state => state.addWidget); - // This now handles both local UI lock AND database saving! - const saveEdit = useDashboardStore(state => state.saveEdit); + const recipe = useRecipe({key: "glassTab"}); + const tabStyles = recipe(); - const cancelEdit = useDashboardStore(state => state.cancelEdit); - const addWidget = useDashboardStore(state => state.addWidget); + // Pull the glass container styles for the dropdown + const panelRecipe = useSlotRecipe({key: "glassPanel"}); + const panelStyles = panelRecipe({ layout: "dropdown" }); - const [isLibraryOpen, setIsLibraryOpen] = useState(false); + const [isLibraryOpen, setIsLibraryOpen] = useState(false); - // Smart Add Handler - const handleAddWidget = (widgetId, sizeKey) => { - enterEditMode(); - addWidget(widgetId, sizeKey); - }; + // Smart Add Handler + const handleAddWidget = (widgetId, sizeKey) => { + enterEditMode(); + addWidget(widgetId, sizeKey); + }; - const categorizedWidgets = useMemo(() => { - return Object.entries(WIDGET_REGISTRY).reduce((acc, [id, widget]) => { - const cat = widget.category || 'General'; - if (!acc[cat]) acc[cat] = []; - acc[cat].push({ id, ...widget }); - return acc; - }, {}); - }, []); + const categorizedWidgets = useMemo(() => { + return Object.entries(WIDGET_REGISTRY).reduce((acc, [id, widget]) => { + const cat = widget.category || 'General'; + if (!acc[cat]) acc[cat] = []; + acc[cat].push({id, ...widget}); + return acc; + }, {}); + }, []); - const categories = Object.keys(categorizedWidgets); + const categories = Object.keys(categorizedWidgets); - // use ref to capture html node for placing menu accordingly - const menuRef = useRef(null); - const getAnchorRect = () => { - if (!menuRef.current) return null; - return menuRef.current.getBoundingClientRect(); - }; + // use ref to capture html node for placing menu accordingly + const menuRef = useRef(null); + const getAnchorRect = () => { + if (!menuRef.current) return null; + return menuRef.current.getBoundingClientRect(); + }; - return ( - <> - {/* THE STATIC DROPDOWN MENU */} - - - - - - - - - {isEditing ? ( - <> - {/* WIRE DIRECTLY TO saveEdit */} - - Save Layout - + return ( + <> + {/* THE STATIC DROPDOWN MENU */} + + + + {/* size increased to 24 to match tabs. flexShrink: 0 is CRITICAL */} + - - Discard Changes - - - ) : ( - - Edit Layout - - )} + {/* Dynamic text based on editing state! */} + + {isEditing ? "Editing Layout..." : "Dashboard Settings"} + + + - + + + {isEditing ? ( + <> + + Save Layout + - setIsLibraryOpen(true)} cursor="pointer" _hover={{ bg: "gray.100" }}> - Widget Library - - - - + + Discard Changes + + + ) : ( + + Edit Layout + + )} - {/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} - setIsLibraryOpen(e.open)} size="xl" placement="center"> - - - - - Add Dashboard Widgets - + - - - - {categories.map(cat => ( - {cat} - ))} - + setIsLibraryOpen(true)} cursor="pointer" _hover={{fontWeight: "semibold"}}> + Widget Library + + + + - {categories.map(cat => ( - - - {categorizedWidgets[cat].map(widget => ( - - {widget.name} - {widget.description} + {/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} + setIsLibraryOpen(e.open)} size="xl" placement="center"> + + + + + Add Dashboard Widgets + - - Add to Grid: - - {Object.keys(widget.allowedSizes).map(sizeKey => ( - handleAddWidget(widget.id, sizeKey)} - colorScheme="gray" - variant="subtle" - px={3} - py={1} - borderRadius="full" - cursor="pointer" - _hover={{ bg: "blue.50", color: "blue.600", transform: "scale(1.05)" }} - transition="all 0.2s" - > - - {sizeKey.charAt(0).toUpperCase() + sizeKey.slice(1)} - - ))} - - - - ))} - - - ))} - - - - - - - - ); + + + + {categories.map(cat => ( + {cat} + ))} + + + {categories.map(cat => ( + + + {categorizedWidgets[cat].map(widget => ( + + {widget.name} + {widget.description} + + + Add + to Grid: + + {Object.keys(widget.allowedSizes).map(sizeKey => ( + handleAddWidget(widget.id, sizeKey)} + colorScheme="gray" + variant="subtle" + px={3} + py={1} + borderRadius="full" + cursor="pointer" + _hover={{bg: "blue.50", color: "blue.600", transform: "scale(1.05)"}} + transition="all 0.2s" + > + + {sizeKey.charAt(0).toUpperCase() + sizeKey.slice(1)} + + ))} + + + + ))} + + + ))} + + + + + + + + ); } \ No newline at end of file diff --git a/client/src/components/deprecated/AlertLevelWidget.jsx b/client/src/components/deprecated/AlertLevelWidget.jsx deleted file mode 100644 index b3b3afb..0000000 --- a/client/src/components/deprecated/AlertLevelWidget.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import Widget from "./Widget.jsx"; -import { Fieldset, NativeSelect, For, Box, Field, Input, Button, Text, Flex } from "@chakra-ui/react"; -import { useForm } from "react-hook-form"; -import { useState } from "react"; -import { Asterisk } from 'lucide-react'; - -export default function AlertLevel({id, isEditable, isDelete, DeleteWidget}) { - const { register, handleSubmit, reset, formState: {errors} } = useForm(); - const [isSubmit, setIsSubmit] = useState(false); - - const onSubmit = (data) => { - setIsSubmit(true); - reset(); - } - - // Prop Forwarding - const widgetProps = { id, isEditable, isDelete, DeleteWidget, isSubmit, setIsSubmit } - - return ( - - {!isSubmit ? ( - // DISPLAYING INPUT FIElDS AND LABELS - - - - - Volcano - - - - - Country - - - - {(item) => ( - - )} - - - - - - - - - - - ):( - // DISPLAYING ALERT LEVEL DATA - - - Alert Level Data Coming Soon. - - - )} - - ); -} \ No newline at end of file diff --git a/client/src/components/deprecated/CancelButton.jsx b/client/src/components/deprecated/CancelButton.jsx deleted file mode 100644 index 28b68a7..0000000 --- a/client/src/components/deprecated/CancelButton.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { Button } from "@chakra-ui/react"; - -export function CancelButton ({onCancel}) { - return ( - - ) -} \ No newline at end of file diff --git a/client/src/components/deprecated/Canvas.jsx b/client/src/components/deprecated/Canvas.jsx deleted file mode 100644 index 5b449f1..0000000 --- a/client/src/components/deprecated/Canvas.jsx +++ /dev/null @@ -1,39 +0,0 @@ -// DEPRECATED - -// import GlobalMap from "./views/GlobalMap"; -// import RegionalMap from "./views/RegionalMap"; -// import Volcano from "./views/Volcano"; -// import Admin from "./views/Admin"; -// import Account from "./views/Account" -// import Home from "./views/Home"; -// import { DASHBOARD_VIEWS, ADMIN_VIEWS, VOLCANO_VIEWS } from "@/constants/viewKeys"; -// import { chakra } from "@chakra-ui/react"; -// import Personal from "./views/Personal"; -// import Security from "./views/Security"; -// -// export default function Canvas(props) { -// const { view } = props; -// -// return ( -// -// {/* Check if view is a tab inside the Home Header */} -// { DASHBOARD_VIEWS.includes(view) && ( -// -// )} -// {/* Check if view is a tab inside the Volcano Header */} -// { VOLCANO_VIEWS.includes(view) && ( -// -// )} -// {/* Check if view is a tab inside the Admin Header */} -// { ADMIN_VIEWS.includes(view) && ( -// -// )} -// {view === "global" && } -// {view === "regional" && } -// {view === "account" && } -// {view === "settings" && } -// {view === "personal" && } -// {view === "security" && } -// -// ); -// } \ No newline at end of file diff --git a/client/src/components/deprecated/CustomResizeHandle.jsx b/client/src/components/deprecated/CustomResizeHandle.jsx deleted file mode 100644 index 9fa3730..0000000 --- a/client/src/components/deprecated/CustomResizeHandle.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { MoveDiagonal2 } from 'lucide-react'; -import React from "react"; - -const CustomResizeHandle = React.forwardRef((props, ref) => { - const { handleAxis, isVisable, ...restProps } = props; - if (!isVisable) return null; - return ( -
- -
- ); -}); - -export default CustomResizeHandle; \ No newline at end of file diff --git a/client/src/components/deprecated/Dashboard.jsx b/client/src/components/deprecated/Dashboard.jsx deleted file mode 100644 index 275e680..0000000 --- a/client/src/components/deprecated/Dashboard.jsx +++ /dev/null @@ -1,95 +0,0 @@ -// DEPRECATED - -// import Header from './Header/Header.jsx'; -// import Sidebar from './Sidebar/Sidebar.jsx'; -// import Canvas from './Canvas/Canvas.jsx'; -// import { Grid, GridItem, useRecipe, useDialog } from '@chakra-ui/react'; -// import { useState } from 'react'; -// import DialogPopup from './Canvas/CanvasComponents/DialogPopup.jsx'; -// -// export default function Dashboard() { -// const recipe = useRecipe({ key: "dashboard" }); -// const styles = recipe(); -// -// const [darkMode, setDarkMode] = useState(false); -// const [isEditable, setIsEditable] = useState(false); // editable=true means static=false (vice versa) -// const [isDelete, setIsDelete] = useState(false); -// const [originalLayout, setOriginalLayout] = useState([]); -// const [originalWidgets, setOriginalWidgets] = useState([]); -// const [view, setView] = useState("mydashboard"); -// const [layout, setLayout] = useState([]); -// const [widgetArray, setWidgetArray] = useState([]); -// const dialog = useDialog(); -// -// const beginEditIfNeeded = () => { -// if ( !isEditable ) { -// setOriginalLayout(layout); -// setOriginalWidgets(widgetArray); -// } -// }; -// -// const onCancel = () => { -// console.log("canceling changes..."); -// console.log(originalWidgets); -// if (originalLayout.length) { -// setLayout(originalLayout.map(item => ({ -// ...item, -// static: true -// }))); -// }; -// setWidgetArray(originalWidgets); -// setIsEditable(false); -// setIsDelete(false); -// setOriginalLayout([]); -// setOriginalWidgets([]); -// } -// -// // Prevent navigation while in Edit mode without saving or cancelling -// const handleViewChange = (nextView) => { -// console.log("1. handling view change") -// if (view === "mydashboard" && isEditable) { -// console.log("2. currently editing"); -// dialog.setOpen(true); -// return; -// -// // const confirmed = window.confirm( -// // "You have unsaved changes. Discard them and continue?" -// // ); -// // if (!confirmed) return; -// // onCancel(); -// } -// setView(nextView); -// }; -// -// const onLayoutChange = (newLayout) => setLayout(newLayout); -// -// // Prop Forwarding -// const canvasProps = { view, layout, setLayout, setOriginalLayout, onLayoutChange, onChangeView: handleViewChange, -// onCancel, isEditable, setIsEditable, isDelete, setIsDelete, widgetArray, setWidgetArray, originalWidgets, setOriginalWidgets, beginEditIfNeeded -// }; -// const sidebarProps = { view, onChangeView: handleViewChange, darkMode, setDarkMode }; -// const headerProps = { onChangeView: handleViewChange }; -// -// return ( -// <> -// -// -// -//
-// -// -// -// -// -// -// -// -// -// ); -// } \ No newline at end of file diff --git a/client/src/components/deprecated/DeleteAllButton.jsx b/client/src/components/deprecated/DeleteAllButton.jsx deleted file mode 100644 index 016d49a..0000000 --- a/client/src/components/deprecated/DeleteAllButton.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Button } from "@chakra-ui/react"; - -export default function DeleteAllButton ({onDeleteAll}) { - return ( - - ) -} \ No newline at end of file diff --git a/client/src/components/deprecated/FirstWidgetButton.jsx b/client/src/components/deprecated/FirstWidgetButton.jsx deleted file mode 100644 index 5c19f03..0000000 --- a/client/src/components/deprecated/FirstWidgetButton.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Box, Menu, Portal } from '@chakra-ui/react' -import AlertLevel from "@/components/deprecated/AlertLevelWidget.jsx"; - -export default function FirstWidgetButton({ appendWidget }) { - const triggerItem = ( - <> - - - Add Your First Widget - - - - ); - const positioning = { placement: "bottom-middle" }; - // const WidgetMenuProps = { appendWidget, triggerItem, positioning }; - return ( - // - - {triggerItem} - - - - appendWidget(AlertLevel, 4, 7, 4, 7, 8, 14)} // width and height are passed through here for each widget - > - Alert Level - - CO2 - IVANS - Plume Heights - Alert Level Changes - - - - - ); -} \ No newline at end of file diff --git a/client/src/components/deprecated/SaveButton.jsx b/client/src/components/deprecated/SaveButton.jsx deleted file mode 100644 index 19365f0..0000000 --- a/client/src/components/deprecated/SaveButton.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { Button } from "@chakra-ui/react"; - -export function SaveButton ({onSave}) { - return ( - - ) -} \ No newline at end of file diff --git a/client/src/components/deprecated/Widget.jsx b/client/src/components/deprecated/Widget.jsx deleted file mode 100644 index c05678f..0000000 --- a/client/src/components/deprecated/Widget.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useSlotRecipe, chakra, Flex, CloseButton, Text, IconButton } from "@chakra-ui/react" -import { Undo } from 'lucide-react'; - -export default function Widget(props) { - // deconstructing props - const { id, title, isEditable, isDelete, DeleteWidget, isSubmit, setIsSubmit, children } = props; - - const recipe = useSlotRecipe({ key: "widget" }); - const styles = recipe(); - - return ( - - {/* HEADER */} - - {title} - {isDelete ? ( - DeleteWidget(id)} size="xs" _hover={{bg: "tomato", color: "white"}}/> - ):( - isSubmit ? ( - setIsSubmit(false)} size="xs" bg="base300" cursor="pointer"> - ):null - )} - - - {/* CHILDREN */} - - {children} - - - ); -} - diff --git a/client/src/components/deprecated/WidgetMenu.jsx b/client/src/components/deprecated/WidgetMenu.jsx deleted file mode 100644 index 140af76..0000000 --- a/client/src/components/deprecated/WidgetMenu.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Menu, Portal } from "@chakra-ui/react"; -import AlertLevel from "./AlertLevelWidget.jsx"; - -export default function WidgetMenu({appendWidget, triggerItem, positioning}) { - return ( - - {triggerItem} - - - - appendWidget(AlertLevel, 4, 7, 4, 7, 8, 14)} // width and height are passed through here for each widget - > - Alert Level - - CO2 - IVANS - Plume Heights - Alert Level Changes - - - - - ) -} \ No newline at end of file diff --git a/client/src/components/deprecated/tooltip.jsx b/client/src/components/deprecated/tooltip.jsx deleted file mode 100644 index ab225cc..0000000 --- a/client/src/components/deprecated/tooltip.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react' -import * as React from 'react' - -export const Tooltip = React.forwardRef(function Tooltip(props, ref) { - const { - showArrow, - children, - disabled, - portalled = true, - content, - contentProps, - portalRef, - ...rest - } = props - - if (disabled) return children - - return ( - - {children} - - - - {showArrow && ( - - - - )} - {content} - - - - - ) -}) diff --git a/client/src/pages/home/Home.jsx b/client/src/pages/home/Home.jsx index fab657a..4f34457 100644 --- a/client/src/pages/home/Home.jsx +++ b/client/src/pages/home/Home.jsx @@ -1,64 +1,69 @@ -import { HStack, Flex, Box } from "@chakra-ui/react"; -import { NavLink, Outlet, useLocation } from "react-router-dom"; -import { SearchBar } from "../../components/dashboard/SearchBar.jsx"; -import { SettingsButton } from "../../components/dashboard/SettingsButton.jsx"; +import {HStack, Flex, Box, useRecipe} from "@chakra-ui/react"; +import {NavLink, Outlet, useLocation} from "react-router-dom"; +import {SearchBar} from "../../components/dashboard/SearchBar.jsx"; +import {SettingsButton} from "../../components/dashboard/SettingsButton.jsx"; + +// 1. Import your icons (Swap these for whichever you prefer!) +import {LayoutDashboard, CalendarDays, Cloud, Satellite, Activity} from "lucide-react"; + +// 2. Define the tabs array +const HOME_TABS = [ + {name: "My Dashboard", url: "/home", icon: LayoutDashboard, exact: true}, + {name: "Daily Activity", url: "/home/daily", icon: CalendarDays}, + {name: "Gas", url: "/home/gas", icon: Cloud}, + {name: "Remote Sensing", url: "/home/remote", icon: Satellite}, + {name: "Seismic", url: "/home/seismic", icon: Activity}, +]; export default function Home() { + const recipe = useRecipe({key: "glassTab"}); + const tabStyles = recipe(); + const location = useLocation(); const isPersonalDashboard = location.pathname === "/home" || location.pathname === "/home/"; - // 1. CSS making this look like how we want it, TODO turn to theme? - const tabStyles = { - fontSize: "lg", - fontWeight: "bold", - color: "fg.muted", - px: 2, // Horizontal padding - // py: 2, // Vertical padding - borderBottom: "1px solid transparent", - transition: "all 0.2s", - _hover: { color: "fg" }, - - "&.active": { // Target the active class applied by React Router NavLink - color: "fg", - borderColor: "fg", - } - }; - return ( - + - {/* 2. πŸ› οΈ SUB HEADER: Matches your original padding and background */} - + - {/* TABS CONTAINER - Replicates the Tabs.List line container */} - - My Dashboard - Daily Activity - Gas - Remote Sensing - Seismic - + {/* TABS CONTAINER */} + + {HOME_TABS.map((tab) => { + const Icon = tab.icon; + return ( + + {/* flexShrink: 0 ensures the icon never squishes during the gooey animation */} + + {tab.name} + + ); + })} - {/* EDIT MODE BUTTONS & CONTROLS */} - - {isPersonalDashboard ? ( - <> - - {/* This will eventually trigger your Zustand store's edit mode! */} - - - ) : ( - - )} - + {/* The Settings button is now inline with the tabs and only renders on the personal dashboard */} + {isPersonalDashboard && } - {/* 3. πŸ› οΈ CANVAS AREA */} - - - + {/* EDIT MODE BUTTONS & CONTROLS */} + {/* Leaving this HStack here in case you plan to put the SearchBar or other global tools back on the right side */} + + {/* Future global controls go here */} + + + + + + + ); } \ No newline at end of file diff --git a/client/src/pages/home/tabs/MyDashboard.jsx b/client/src/pages/home/tabs/MyDashboard.jsx index e062b6c..fb98af2 100644 --- a/client/src/pages/home/tabs/MyDashboard.jsx +++ b/client/src/pages/home/tabs/MyDashboard.jsx @@ -1,208 +1,199 @@ -import { Box, Text, Flex, IconButton, Spinner, Popover } from "@chakra-ui/react"; -import { X, MoreVertical } from "lucide-react"; -import { Responsive, WidthProvider } from "react-grid-layout"; -import { useDashboardStore } from "@/store/useDashboardStore.jsx"; -import { Suspense, useEffect, useRef } from 'react'; -import { useColorModeValue } from "@/components/ui/color-mode"; +import {Box, Text, Flex, IconButton, Spinner, Popover, useSlotRecipe} from "@chakra-ui/react"; +import {X, MoreVertical} from "lucide-react"; +import {Responsive, WidthProvider} from "react-grid-layout"; +import {useDashboardStore} from "@/store/useDashboardStore.jsx"; +import {Suspense, useEffect, useRef} from 'react'; +import {useColorModeValue} from "@/components/ui/color-mode"; // 1. Import database messenger import useAuthStore from "@/store/authStore"; // 2. Import registry -import { WIDGET_REGISTRY } from '@/constants/widgetRegistry.jsx'; +import {WIDGET_REGISTRY} from '@/constants/widgetRegistry.jsx'; // React-Grid-Layout requires this wrapper to automatically calculate screen width const ResponsiveGridLayout = WidthProvider(Responsive); export default function MyDashboard() { - // πŸŒ— Let's adjust for our light and dark mode - const frameBg = useColorModeValue("FFFFFF0A", "#FFFFFF0A"); - const titleColor = useColorModeValue("gray.800", "#FFFFFF"); - const iconColor = useColorModeValue("gray.600", "#FFFFFF"); - const iconHoverBg = useColorModeValue("gray.100", "gray.700"); + // πŸŒ— Let's adjust for our light and dark mode + const frameBg = useColorModeValue("FFFFFF0A", "#FFFFFF0A"); + const titleColor = useColorModeValue("gray.800", "#FFFFFF"); + const iconColor = useColorModeValue("gray.600", "#FFFFFF"); + const iconHoverBg = useColorModeValue("gray.100", "gray.700"); - // ☁️ Database Connection - const { user } = useAuthStore(); + // ☁️ Database Connection + const {user} = useAuthStore(); - // ☁️ Local UI Connections - const widgets = useDashboardStore(state => state.widgets); - const isEditing = useDashboardStore(state => state.isEditing); - const updateLayoutPositions = useDashboardStore(state => state.updateLayoutPositions); - const removeWidget = useDashboardStore(state => state.removeWidget); - const setWidgets = useDashboardStore(state => state.setWidgets); // The injection action + // ☁️ Local UI Connections + const widgets = useDashboardStore(state => state.widgets); + const isEditing = useDashboardStore(state => state.isEditing); + const updateLayoutPositions = useDashboardStore(state => state.updateLayoutPositions); + const removeWidget = useDashboardStore(state => state.removeWidget); + const setWidgets = useDashboardStore(state => state.setWidgets); // The injection action - // πŸ”’ Safety lock to prevent infinite re-renders - const hasHydrated = useRef(false); + // πŸ”’ Safety lock to prevent infinite re-renders + const hasHydrated = useRef(false); - // ========================================== - // INITIAL LOAD: Grab data from Django - // ========================================== - useEffect(() => { - if (user?.preferences?.dashboard_layout && !hasHydrated.current) { - const savedLayout = user.preferences.dashboard_layout; + // 1. Pull the new global slot recipe + const recipe = useSlotRecipe({key: "glassPanel"}); + const styles = recipe({ layout: "widget" }); - // Safety check: Only inject if it's actually an array of widgets. - // (Prevents a crash if MariaDB returns a default empty object {}) - if (Array.isArray(savedLayout)) { - setWidgets(savedLayout); - } + useEffect(() => { + if (user?.preferences?.dashboard_layout && !hasHydrated.current) { + const savedLayout = user.preferences.dashboard_layout; - hasHydrated.current = true; - } - }, [user, setWidgets]); + // Safety check: Only inject if it's actually an array of widgets. + // (Prevents a crash if MariaDB returns a default empty object {}) + if (Array.isArray(savedLayout)) { + setWidgets(savedLayout); + } + hasHydrated.current = true; + } + }, [user, setWidgets]); - // πŸ› οΈ Format the data for RGL - const rglLayout = widgets.map(w => ({ - i: w.id, - x: w.x, - y: w.y, - w: w.w, - h: w.h, - isResizable: false // 🚫 The magic lock! No resizing allowed globally. - })); + // πŸ› οΈ Format the data for RGL + const rglLayout = widgets.map(w => ({ + i: w.id, + x: w.x, + y: w.y, + w: w.w, + h: w.h, + isResizable: false // 🚫 The magic lock! No resizing allowed globally. + })); - // 3. THE COMPONENT MAPPER - const renderWidget = (widget) => { - const registryEntry = WIDGET_REGISTRY[widget.type]; + // 3. THE COMPONENT MAPPER + const renderWidget = (widget) => { + const registryEntry = WIDGET_REGISTRY[widget.type]; - if (registryEntry && registryEntry.component) { - const Component = registryEntry.component; + if (registryEntry && registryEntry.component) { + const Component = registryEntry.component; - // 1. Check if this widget type has a settings component registered - const SettingsUI = registryEntry.settingsComponent; + // 1. Check if this widget type has a settings component registered + const SettingsUI = registryEntry.settingsComponent; - return ( - // The "Frame" that holds both the Title and the Chart - + return ( + <> + + + {registryEntry.name} + - {/* THE TITLE BAR */} - - - {registryEntry.name} - + {/* THE SETTINGS ELLIPSIS */} + {SettingsUI ? ( + + + + + + - {/* THE SETTINGS ELLIPSIS */} - {SettingsUI ? ( - - - - - - - - {/* NEW: The Positioner lifts the content out of the Flexbox flow */} - - - }> - - - - - - ) : ( - - )} - - - {/* THE CHART CANVAS */} - - {/* Pass the settings object into the actual chart component */} - - - - - ); - } - - // Fallback if someone asks for a widget that doesn't exist - return ( - - Error - Widget '{widget.type}' not found. - - ); - }; - - return ( - - updateLayoutPositions(newLayout)} // Fires when user drops a widget - compactType="vertical" // Auto-packs widgets to the top - > - {/* 3. 🎨 Paint the Widgets */} - {widgets.map(widget => ( - - {/* 4. THE DELETE BUTTON */} - {isEditing && ( - removeWidget(widget.id)} - onMouseDown={(e) => e.stopPropagation()} // 🚫 Stops RGL from grabbing the widget - onTouchStart={(e) => e.stopPropagation()} // 🚫 Same protection for mobile touches - > - - - )} - - {/* 4. SUSPENSE BOUNDARY wrapping the render function */} - - - - }> - {renderWidget(widget)} - - - - ))} - - - {/* Empty State Helper */} - {widgets.length === 0 && ( - - Your dashboard is empty. Click Settings to add widgets. - + {/* NEW: The Positioner lifts the content out of the Flexbox flow */} + + + }> + + + + + + ) : ( + )} - + + + + + + + ); + } + + // Fallback if someone asks for a widget that doesn't exist + return ( + + Error + Widget '{widget.type}' not found. + ); + }; + + return ( + + updateLayoutPositions(newLayout)} // Fires when user drops a widget + compactType="vertical" // Auto-packs widgets to the top + > + {/* 3. 🎨 Paint the Widgets */} + {widgets.map(widget => ( + + {/* 4. THE DELETE BUTTON */} + {isEditing && ( + removeWidget(widget.id)} + onMouseDown={(e) => e.stopPropagation()} // 🚫 Stops RGL from grabbing the widget + onTouchStart={(e) => e.stopPropagation()} // 🚫 Same protection for mobile touches + > + + + )} + + {/* 4. SUSPENSE BOUNDARY wrapping the render function */} + + + + }> + {renderWidget(widget)} + + + ))} + + + {/* Empty State Helper */} + {widgets.length === 0 && ( + + Your dashboard is empty. Click Settings to add widgets. + + )} + + ); } \ No newline at end of file diff --git a/client/src/theme.js b/client/src/theme.js index a3fb5a8..6140c0d 100644 --- a/client/src/theme.js +++ b/client/src/theme.js @@ -1,4 +1,4 @@ -import { defineSlotRecipe, createSystem, defaultConfig, defineConfig, defineRecipe } from "@chakra-ui/react"; +import {defineSlotRecipe, createSystem, defaultConfig, defineConfig, defineRecipe} from "@chakra-ui/react"; // RECIPES const headerRecipe = defineRecipe({ @@ -16,7 +16,7 @@ const headerRecipe = defineRecipe({ const dashboardRecipe = defineRecipe({ base: { - bg: "{testBackground}" + bg: "{MainBackground}" } }); @@ -33,7 +33,10 @@ const sidebarRecipe = defineSlotRecipe({ base: { root: { // 1. Semi-transparent backgrounds - bg: { base: "linear-gradient(to bottom right, #99000040 20%, #FF660040 60%, #EEEE0040 100%)", _dark: "linear-gradient(to bottom right, #9900003C 20%, #FF66003C 60%, #FFC0003C 100%)" }, + bg: { + base: "linear-gradient(to bottom right, #99000040 20%, #FF660040 60%, #EEEE0040 100%)", + _dark: "linear-gradient(to bottom right, #9900003C 20%, #FF66003C 60%, #FFC0003C 100%)" + }, // 2. The glass blur effect backdropFilter: "blur(12px)", @@ -41,7 +44,7 @@ const sidebarRecipe = defineSlotRecipe({ // 3. Subtle semi-transparent borders border: "1px solid", - borderColor: { base: "rgba(255, 255, 255, 0.8)", _dark: "rgba(255, 255, 255, 0.1)" }, + borderColor: {base: "rgba(255, 255, 255, 0.8)", _dark: "rgba(255, 255, 255, 0.1)"}, // CRITICAL: Remove the right border so it doesn't draw a line through your cutout borderRight: "none", @@ -94,11 +97,11 @@ const sidebarRecipe = defineSlotRecipe({ borderLeftRadius: "4xl", "&.active": { - bg: "{testBackground}", + bg: "{MainBackground}", color: "{textInverted}", _hover: { - bg: "{testBackground}", + bg: "{MainBackground}", color: "{textInverted}", fontWeight: "normal", "& svg": { @@ -114,7 +117,7 @@ const sidebarRecipe = defineSlotRecipe({ top: "-25px", width: "25px", height: "25px", - bg: "inherit", // This forces the curve to match {testButton} + bg: "inherit", WebkitMaskImage: "radial-gradient(circle at top left, transparent 25px, black 25.5px)", maskImage: "radial-gradient(circle at top left, transparent 25px, black 25.5px)", pointerEvents: "none", @@ -176,19 +179,155 @@ const widgetRecipe = defineSlotRecipe({ } }); +// const glassPanelRecipe = defineSlotRecipe({ +// slots: ["root", "header", "body"], +// base: { +// root: { +// // Structure +// display: "flex", +// flexDirection: "column", +// width: "100%", +// height: "100%", +// position: "relative", +// overflow: "hidden", +// borderRadius: "3xl", +// +// // Glass Aesthetics (Syncs nicely with your sidebar/tabs) +// bg: { base: "rgba(255, 255, 255, 0.4)", _dark: "rgba(255, 255, 255, 0.04)" }, +// backdropFilter: "blur(12px)", +// WebkitBackdropFilter: "blur(12px)", +// border: "1px solid", +// borderColor: { base: "gray.200", _dark: "whiteAlpha.200" }, +// boxShadow: "sm", +// }, +// header: { +// display: "flex", +// justifyContent: "space-between", +// alignItems: "center", +// px: 3, +// py: 2, +// }, +// body: { +// flex: "1", +// overflow: "hidden", +// position: "relative", +// p: 1, +// } +// } +// }); + +const glassPanelRecipe = defineSlotRecipe({ + slots: ["root", "header", "body"], + + // 🧊 THE GLASS MATH (Applies to everything) + base: { + root: { + display: "flex", + flexDirection: "column", + position: "relative", + overflow: "hidden", + bg: {base: "rgba(255, 255, 255, 0.4)", _dark: "rgba(255, 255, 255, 0.04)"}, + backdropFilter: "blur(12px)", + WebkitBackdropFilter: "blur(12px)", + border: "1px solid", + borderColor: {base: "gray.200", _dark: "whiteAlpha.200"}, + boxShadow: "sm", + } + }, + + // πŸ“ THE LAYOUTS (Applies selectively) + variants: { + layout: { + widget: { + root: {width: "100%", height: "100%", borderRadius: "3xl"}, + header: {display: "flex", justifyContent: "space-between", alignItems: "center", px: 3, pt: 2, pb: 0}, + body: {flex: "1", overflow: "hidden", position: "relative", px: 1, pb: 1, pt: 0} + }, + dropdown: { + root: {borderRadius: "xl", p: 1} + // (Header and body slots are ignored here because dropdowns don't use them) + } + } + }, + + defaultVariants: { + layout: "widget" + } +}); +const glassTabRecipe = defineRecipe({ + base: { + display: "flex", + alignItems: "center", + height: "40px", + + // The Circle State (Inactive) + maxWidth: "40px", + minWidth: "40px", + px: "8px", + gap: 2, + + // CRITICAL: Locks the shape to a perfect circle/pill + borderRadius: "full", + + overflow: "hidden", + whiteSpace: "nowrap", + textDecoration: "none", + fontWeight: "bold", + color: "fg.muted", + + // --- THE GLASS EFFECT (Inactive) --- + // Uses a highly transparent white/black depending on mode + bg: {base: "rgba(255, 255, 255, 0.4)", _dark: "rgba(255, 255, 255, 0.03)"}, + backdropFilter: "blur(12px)", + WebkitBackdropFilter: "blur(12px)", + border: "1px solid", + borderColor: {base: "rgba(0, 0, 0, 0.05)", _dark: "rgba(255, 255, 255, 0.08)"}, + boxShadow: "0 4px 6px rgba(0, 0, 0, 0.02)", // Tiny shadow to lift it off the canvas + + transition: "all 0.85s cubic-bezier(0.34, 1.56, 0.64, 1)", + + "&:hover, &[data-state='open'], &[aria-expanded='true']": { + maxWidth: "250px", + pr: "16px", + // --- THE GLASS EFFECT (Hover) --- + // Becomes slightly more opaque when you mouse over + bg: {base: "rgba(255, 255, 255, 0.8)", _dark: "rgba(255, 255, 255, 0.1)"}, + borderColor: {base: "rgba(0, 0, 0, 0.1)", _dark: "rgba(255, 255, 255, 0.15)"}, + color: "fg", + textDecoration: "none", + }, + + "&.active": { + maxWidth: "250px", + pr: "16px", + + bg: "{gooeyButtonBackground}", + borderColor: "transparent", + color: "{textInverted}", + boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)", // Stronger shadow for the active tab + + _hover: { + bg: "{gooeyButtonBackground}", + cursor: "default", + textDecoration: "none", + } + } + } +}); + const config = defineConfig({ globalCss: { ".react-grid-placeholder": { - background: "#c0d2e3ff !important", - borderColor: "#a9b6c2ff !important", - boxShadow: "0 0 10px #c0d2e3ff !important", - opacity: "1 !important", - borderRadius: "5px", - _dark: { - background: "#393c3fff !important", - borderColor: "#000000ff !important", - boxShadow: "0 0 10px #16181bff !important", + background: "#c0d2e3ff !important", + borderColor: "#a9b6c2ff !important", + boxShadow: "0 0 10px #c0d2e3ff !important", opacity: "1 !important", + borderRadius: "5px", + _dark: { + background: "#393c3fff !important", + borderColor: "#000000ff !important", + boxShadow: "0 0 10px #16181bff !important", + opacity: "1 !important", }, }, ".react-resizable-handle-se": { @@ -251,35 +390,38 @@ const config = defineConfig({ theme: { semanticTokens: { colors: { - base100: { value: {base: "#f6f9faff", _dark: "#1617199e"}}, - base200: { value: {base: "#e6ecf1ff", _dark: "#242424"}}, - base300: { value: {base: "#d3dae4ff", _dark: "#0a0b0bff"}}, - primary: { value: {base: "#2B98F8", _dark: "#2B98F8"}}, - primaryLt: { value: {base: "#98cdffff", _dark: "#98cdffff"}}, - secondaryBG: { value: {base: "#EDF1F1", _dark: "#121212"}}, + base100: {value: {base: "#f6f9faff", _dark: "#1617199e"}}, + base200: {value: {base: "#e6ecf1ff", _dark: "#242424"}}, + base300: {value: {base: "#d3dae4ff", _dark: "#0a0b0bff"}}, + primary: {value: {base: "#2B98F8", _dark: "#2B98F8"}}, + primaryLt: {value: {base: "#98cdffff", _dark: "#98cdffff"}}, + secondaryBG: {value: {base: "#EDF1F1", _dark: "#121212"}}, border: {value: {base: "#000000", _dark: "#cbcbcb84"}}, text: {value: {base: "#000000", _dark: "#FFFFFF"}}, textInverted: {value: {base: "#000000", _dark: "#FFFFFF"}}, icon: {value: {base: "#000000", _dark: "#FFFFFF"}}, - testBackground: { value: {base: "#F9F9F9", _dark: "#141414"} }, - testSidebar: { value: {base: "#FBFBFB", _dark: "#272421"} }, - testButton: { value: {base: "#FFFFFF", _dark: "#C09754"} }, - + gooeyButtonBackground: {value: {base: "rgba(255, 255, 255, 0.1)", _dark: "rgba(255, 255, 255, 0.1)"}}, + MainBackground: {value: {base: "#F9F9F9", _dark: "#141414"}}, + titleColor: {value: {base: "gray.800", _dark: "#FFFFFF"}}, + iconColor: {value: {base: "gray.600", _dark: "#000000"}}, + glassSeperator: {value: {base: "#0000001A", _dark: "#FFFFFF3F"}}, }, }, tokens: { fonts: { - body: { value: "system-ui, sans-serif" }, + body: {value: "system-ui, sans-serif"}, }, }, recipes: { header: headerRecipe, dashboard: dashboardRecipe, canvas: canvasRecipe, + glassTab: glassTabRecipe }, slotRecipes: { sidebar: sidebarRecipe, widget: widgetRecipe, + glassPanel: glassPanelRecipe, }, }, }); From a6ebe5af1e631040d6d3a2981d9dfa1705155886 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Wed, 29 Jul 2026 14:18:10 -0700 Subject: [PATCH 09/22] Refactor recipe adjustment --- .../components/dashboard/SettingsButton.jsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index cf2a376..2782712 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -10,7 +10,8 @@ import { Badge, Menu, HStack, - useRecipe, useSlotRecipe + useRecipe, + useSlotRecipe } from "@chakra-ui/react"; import {Settings, Plus, LayoutGrid, Move, Save, X} from 'lucide-react'; import {useDashboardStore} from "../../store/useDashboardStore.jsx"; @@ -29,7 +30,7 @@ export function SettingsButton() { // Pull the glass container styles for the dropdown const panelRecipe = useSlotRecipe({key: "glassPanel"}); - const panelStyles = panelRecipe({ layout: "dropdown" }); + const panelStyles = panelRecipe({layout: "dropdown"}); const [isLibraryOpen, setIsLibraryOpen] = useState(false); @@ -81,23 +82,23 @@ export function SettingsButton() { {isEditing ? ( <> - + Save Layout - + Discard Changes ) : ( - + Edit Layout )} - setIsLibraryOpen(true)} cursor="pointer" _hover={{fontWeight: "semibold"}}> + setIsLibraryOpen(true)} cursor="pointer" _hover={{WebkitTextStroke: "0.5px currentColor"}}> Widget Library @@ -105,7 +106,14 @@ export function SettingsButton() { {/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} - setIsLibraryOpen(e.open)} size="xl" placement="center"> + setIsLibraryOpen(e.open)} + size="xl" + placement="center" + closeOnInteractOutside={true} + closeOnEscapeKeyDown={true} + > From 2b0300b0756d3be6fd3d6eea17a0e19624a520d1 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Tue, 4 Aug 2026 09:05:30 -0700 Subject: [PATCH 10/22] Refactor continued of UI. changes to theme to make baselines --- .../components/dashboard/SettingsButton.jsx | 100 ++++++++++++------ client/src/pages/home/tabs/MyDashboard.jsx | 5 +- client/src/theme.js | 51 +++------ 3 files changed, 83 insertions(+), 73 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index 2782712..c14ab21 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -60,50 +60,82 @@ export function SettingsButton() { return ( <> - {/* THE STATIC DROPDOWN MENU */} - - + {isEditing ? ( + // πŸ“ EDIT MODE: The 3-Pill Layout + + {/* 1. The Add Widget Button */} setIsLibraryOpen(true)} + color={{ base: "blue.500", _dark: "blue.300" }} + _hover={{ bg: "rgba(66, 153, 225, 0.1)" }} > - {/* size increased to 24 to match tabs. flexShrink: 0 is CRITICAL */} - - - {/* Dynamic text based on editing state! */} - - {isEditing ? "Editing Layout..." : "Dashboard Settings"} - + + Widget Library - - - - {isEditing ? ( - <> - - Save Layout - + {/* 2. The Save Button */} + + + Save Layout + - - Discard Changes - - - ) : ( - - Edit Layout + {/* 3. The Discard Button */} + + + Discard + + + ) : ( + // πŸ‘€ VIEW MODE: The Static Dropdown Menu + + + + {/* size increased to 24 to match tabs. flexShrink: 0 is CRITICAL */} + + Dashboard Settings + + + + + + + Edit Layout - )} - + - setIsLibraryOpen(true)} cursor="pointer" _hover={{WebkitTextStroke: "0.5px currentColor"}}> - Widget Library - - - - + setIsLibraryOpen(true)} + cursor="pointer" + _hover={{ WebkitTextStroke: "0.5px currentColor" }} + > + Widget Library + + + + + )} {/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} { if (user?.preferences?.dashboard_layout && !hasHydrated.current) { @@ -96,7 +97,7 @@ export default function MyDashboard() { {/* NEW: The Positioner lifts the content out of the Flexbox flow */} - + }> diff --git a/client/src/theme.js b/client/src/theme.js index 6140c0d..4001470 100644 --- a/client/src/theme.js +++ b/client/src/theme.js @@ -179,43 +179,6 @@ const widgetRecipe = defineSlotRecipe({ } }); -// const glassPanelRecipe = defineSlotRecipe({ -// slots: ["root", "header", "body"], -// base: { -// root: { -// // Structure -// display: "flex", -// flexDirection: "column", -// width: "100%", -// height: "100%", -// position: "relative", -// overflow: "hidden", -// borderRadius: "3xl", -// -// // Glass Aesthetics (Syncs nicely with your sidebar/tabs) -// bg: { base: "rgba(255, 255, 255, 0.4)", _dark: "rgba(255, 255, 255, 0.04)" }, -// backdropFilter: "blur(12px)", -// WebkitBackdropFilter: "blur(12px)", -// border: "1px solid", -// borderColor: { base: "gray.200", _dark: "whiteAlpha.200" }, -// boxShadow: "sm", -// }, -// header: { -// display: "flex", -// justifyContent: "space-between", -// alignItems: "center", -// px: 3, -// py: 2, -// }, -// body: { -// flex: "1", -// overflow: "hidden", -// position: "relative", -// p: 1, -// } -// } -// }); - const glassPanelRecipe = defineSlotRecipe({ slots: ["root", "header", "body"], @@ -246,6 +209,20 @@ const glassPanelRecipe = defineSlotRecipe({ dropdown: { root: {borderRadius: "xl", p: 1} // (Header and body slots are ignored here because dropdowns don't use them) + }, + popover: { + root: { + borderRadius: "2xl", + p: 4, + boxShadow: "xl", + minW: "240px", + // OVERRIDES: Make the background 85-90% opaque instead of 40% + bg: { base: "rgba(255, 255, 255, 0.75)", _dark: "rgba(20, 22, 25, 0.8)" }, + + // OVERRIDES: Increase the blur just to smooth out any stubborn gridlines + backdropFilter: "blur(20px)", + WebkitBackdropFilter: "blur(20px)", + } } } }, From 9b1ddf6dcce00eeb164dd1b7a08a19809f5f0836 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Thu, 20 Aug 2026 12:51:58 -0700 Subject: [PATCH 11/22] Refactor widget library UI code --- .../components/dashboard/SettingsButton.jsx | 167 ++++++++++-------- client/src/theme.js | 4 +- 2 files changed, 94 insertions(+), 77 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index c14ab21..6139ca0 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -28,9 +28,10 @@ export function SettingsButton() { const recipe = useRecipe({key: "glassTab"}); const tabStyles = recipe(); - // Pull the glass container styles for the dropdown + // Pull the glass container styles for the dropdown and modal const panelRecipe = useSlotRecipe({key: "glassPanel"}); const panelStyles = panelRecipe({layout: "dropdown"}); + const modalStyles = panelRecipe({layout: "popover"}); const [isLibraryOpen, setIsLibraryOpen] = useState(false); @@ -123,88 +124,104 @@ export function SettingsButton() { Edit Layout - + - setIsLibraryOpen(true)} - cursor="pointer" - _hover={{ WebkitTextStroke: "0.5px currentColor" }} + setIsLibraryOpen(true)} + cursor="pointer" + _hover={{ + bg: {base: "rgba(0, 0, 0, 0.05)", _dark: "rgba(255, 255, 255, 0.1)"}, + WebkitTextStroke: "0.5px currentColor" + }} + > + Widget Library + + + + + )} + + {/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} + setIsLibraryOpen(e.open)} + size="xl" + placement="center" + closeOnInteractOutside={true} + closeOnEscapeKeyDown={true} > - Widget Library - - - - - )} + {/* Soft blur can be added to the background behind the modal */} + {/**/} + - {/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} - setIsLibraryOpen(e.open)} - size="xl" - placement="center" - closeOnInteractOutside={true} - closeOnEscapeKeyDown={true} - > - - - - - Add Dashboard Widgets - + - - - - {categories.map(cat => ( - {cat} - ))} - + + Widgets Shop + - {categories.map(cat => ( - - - {categorizedWidgets[cat].map(widget => ( - - {widget.name} - {widget.description} + + + + {categories.map(cat => ( + {cat} + ))} + - - Add - to Grid: - - {Object.keys(widget.allowedSizes).map(sizeKey => ( - handleAddWidget(widget.id, sizeKey)} - colorScheme="gray" - variant="subtle" - px={3} - py={1} - borderRadius="full" - cursor="pointer" - _hover={{bg: "blue.50", color: "blue.600", transform: "scale(1.05)"}} - transition="all 0.2s" + {categories.map(cat => ( + + + {categorizedWidgets[cat].map(widget => ( + - - {sizeKey.charAt(0).toUpperCase() + sizeKey.slice(1)} - + {widget.name} + {widget.description} + + + + Add to Grid: + + + {Object.keys(widget.allowedSizes).map(sizeKey => ( + handleAddWidget(widget.id, sizeKey)} + color={{base: "blue.600", _dark: "blue.300"}} + > + + {sizeKey.charAt(0).toUpperCase() + sizeKey.slice(1)} + + ))} + + + ))} - - - - ))} - - - ))} - - - - - - + + + ))} + + + + + + ); } \ No newline at end of file diff --git a/client/src/theme.js b/client/src/theme.js index 4001470..d59c24f 100644 --- a/client/src/theme.js +++ b/client/src/theme.js @@ -220,8 +220,8 @@ const glassPanelRecipe = defineSlotRecipe({ bg: { base: "rgba(255, 255, 255, 0.75)", _dark: "rgba(20, 22, 25, 0.8)" }, // OVERRIDES: Increase the blur just to smooth out any stubborn gridlines - backdropFilter: "blur(20px)", - WebkitBackdropFilter: "blur(20px)", + backdropFilter: "blur(15px)", + WebkitBackdropFilter: "blur(15px)", } } } From 3017535d02223c952e8f7b9eb3c4e5ffdd457656 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Thu, 20 Aug 2026 14:45:13 -0700 Subject: [PATCH 12/22] Update to buttons on widget choices --- .../components/dashboard/SettingsButton.jsx | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index 6139ca0..13a9577 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -13,10 +13,16 @@ import { useRecipe, useSlotRecipe } from "@chakra-ui/react"; -import {Settings, Plus, LayoutGrid, Move, Save, X} from 'lucide-react'; +import {Settings, LayoutGrid, Move, Save, X} from 'lucide-react'; import {useDashboardStore} from "../../store/useDashboardStore.jsx"; import {WIDGET_REGISTRY} from "../../constants/widgetRegistry.jsx"; +// 🚨 NEW: Tiny helper to convert size keys into abbreviations +const getSizeAbbreviation = (key) => { + const mapping = { small: "S", medium: "M", large: "L", xlarge: "XL" }; + return mapping[key.toLowerCase()] || key.charAt(0).toUpperCase(); +}; + export function SettingsButton() { // 1. ☁Store connections const isEditing = useDashboardStore(state => state.isEditing); @@ -192,7 +198,7 @@ export function SettingsButton() { pt={3}> - Add to Grid: + Add to Dashboard: {Object.keys(widget.allowedSizes).map(sizeKey => ( @@ -203,8 +209,24 @@ export function SettingsButton() { onClick={() => handleAddWidget(widget.id, sizeKey)} color={{base: "blue.600", _dark: "blue.300"}} > - - {sizeKey.charAt(0).toUpperCase() + sizeKey.slice(1)} + {/* 🚨 THE ABBREVIATION ICON: Always visible */} + + {getSizeAbbreviation(sizeKey)} + + + {/* 🚨 THE EXPANDED TEXT: Only visible on hover */} + Add Widget ))} @@ -216,9 +238,7 @@ export function SettingsButton() { ))} - + From 510c63e7b0e4624257df1ca9a87631906933b57c Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Fri, 21 Aug 2026 11:41:08 -0700 Subject: [PATCH 13/22] Update tabs in widget library now scroll and look nice --- .../components/dashboard/SettingsButton.jsx | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index 13a9577..21d08f1 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -168,12 +168,55 @@ export function SettingsButton() { - - + + { + if (e.currentTarget) { + e.currentTarget.scrollLeft += e.deltaY; + } + }} + > {categories.map(cat => ( - {cat} + + {cat} + ))} From f5f09e31fe240d3d91b7c2fa329e3ade28c59a22 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Fri, 21 Aug 2026 12:55:54 -0700 Subject: [PATCH 14/22] Update added chevrons to scrollbar - bug in size of chevrons --- .../components/dashboard/SettingsButton.jsx | 178 ++++++++++++------ 1 file changed, 125 insertions(+), 53 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index 21d08f1..436fc6b 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -1,4 +1,4 @@ -import {useState, useMemo, useRef} from "react"; +import {useState, useMemo, useRef, useEffect, useCallback} from "react"; import { Button, Box, @@ -13,7 +13,7 @@ import { useRecipe, useSlotRecipe } from "@chakra-ui/react"; -import {Settings, LayoutGrid, Move, Save, X} from 'lucide-react'; +import {Settings, LayoutGrid, Move, Save, X, ChevronRight, ChevronLeft} from 'lucide-react'; import {useDashboardStore} from "../../store/useDashboardStore.jsx"; import {WIDGET_REGISTRY} from "../../constants/widgetRegistry.jsx"; @@ -41,6 +41,29 @@ export function SettingsButton() { const [isLibraryOpen, setIsLibraryOpen] = useState(false); + // ⬅️ ➑️ This is the callbacks and scroll control to add the chevron arrows in the tab list + const tabsRef = useRef(null); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const checkScroll = useCallback(() => { + if (tabsRef.current) { + const { scrollLeft, scrollWidth, clientWidth } = tabsRef.current; + setCanScrollLeft(scrollLeft > 0); + // We use -1 here to account for sub-pixel rendering in some browsers + setCanScrollRight(Math.ceil(scrollLeft + clientWidth) < scrollWidth - 1); + } + }, []); + + useEffect(() => { + if (isLibraryOpen) { + // Small timeout ensures the DOM has rendered the tabs before measuring + setTimeout(checkScroll, 50); + window.addEventListener('resize', checkScroll); + return () => window.removeEventListener('resize', checkScroll); + } + }, [isLibraryOpen, checkScroll]); + // Smart Add Handler const handleAddWidget = (widgetId, sizeKey) => { enterEditMode(); @@ -147,7 +170,7 @@ export function SettingsButton() { )} - {/* THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} + {/* πŸ“šπŸ“šπŸ“š THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} setIsLibraryOpen(e.open)} @@ -169,57 +192,106 @@ export function SettingsButton() { - { - if (e.currentTarget) { - e.currentTarget.scrollLeft += e.deltaY; - } - }} - > - {categories.map(cat => ( - - {cat} - - ))} - + + {/* LEFT GLASS CRESCENT */} + {canScrollLeft && ( + tabsRef.current.scrollBy({ left: -200, behavior: 'smooth' })} + // The Glass Look hugging the left pill edge + bg={{ base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)" }} + backdropFilter="blur(8px)" + borderTopLeftRadius="full" + borderBottomLeftRadius="full" + // Fades the inner edge so it blends into the track + boxShadow={{ base: "10px 0 15px -5px rgba(255,255,255,0.8)", _dark: "10px 0 15px -5px rgba(30,30,30,0.9)" }} + color={{ base: "gray.600", _dark: "gray.300" }} + _hover={{ color: { base: "blue.500", _dark: "blue.300" } }} + > + + + )} + { + if (e.currentTarget) { + e.currentTarget.scrollLeft += e.deltaY; + } + }} + > + {categories.map(cat => ( + + {cat} + + ))} + + {canScrollRight && ( + tabsRef.current.scrollBy({ left: 200, behavior: 'smooth' })} + // The Glass Look hugging the right pill edge + bg={{ base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)" }} + backdropFilter="blur(8px)" + borderTopRightRadius="full" + borderBottomRightRadius="full" + boxShadow={{ base: "-10px 0 15px -5px rgba(255,255,255,0.8)", _dark: "-10px 0 15px -5px rgba(30,30,30,0.9)" }} + color={{ base: "gray.600", _dark: "gray.300" }} + _hover={{ color: { base: "blue.500", _dark: "blue.300" } }} + > + + + )} + + {categories.map(cat => ( From 9abcb783273fd45c5b7b5860d34b22d1fb1c89e7 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Fri, 21 Aug 2026 12:58:28 -0700 Subject: [PATCH 15/22] Refactor only formatting of file --- .../components/dashboard/SettingsButton.jsx | 425 +++++++++--------- 1 file changed, 216 insertions(+), 209 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index 436fc6b..8038b94 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -19,7 +19,7 @@ import {WIDGET_REGISTRY} from "../../constants/widgetRegistry.jsx"; // 🚨 NEW: Tiny helper to convert size keys into abbreviations const getSizeAbbreviation = (key) => { - const mapping = { small: "S", medium: "M", large: "L", xlarge: "XL" }; + const mapping = {small: "S", medium: "M", large: "L", xlarge: "XL"}; return mapping[key.toLowerCase()] || key.charAt(0).toUpperCase(); }; @@ -48,7 +48,7 @@ export function SettingsButton() { const checkScroll = useCallback(() => { if (tabsRef.current) { - const { scrollLeft, scrollWidth, clientWidth } = tabsRef.current; + const {scrollLeft, scrollWidth, clientWidth} = tabsRef.current; setCanScrollLeft(scrollLeft > 0); // We use -1 here to account for sub-pixel rendering in some browsers setCanScrollRight(Math.ceil(scrollLeft + clientWidth) < scrollWidth - 1); @@ -99,10 +99,10 @@ export function SettingsButton() { css={tabStyles} data-state="open" onClick={() => setIsLibraryOpen(true)} - color={{ base: "blue.500", _dark: "blue.300" }} - _hover={{ bg: "rgba(66, 153, 225, 0.1)" }} + color={{base: "blue.500", _dark: "blue.300"}} + _hover={{bg: "rgba(66, 153, 225, 0.1)"}} > - + Widget Library @@ -112,10 +112,10 @@ export function SettingsButton() { css={tabStyles} data-state="open" onClick={saveEdit} - color={{ base: "green.600", _dark: "green.400" }} - _hover={{ bg: "rgba(72, 187, 120, 0.1)" }} + color={{base: "green.600", _dark: "green.400"}} + _hover={{bg: "rgba(72, 187, 120, 0.1)"}} > - + Save Layout @@ -125,20 +125,20 @@ export function SettingsButton() { css={tabStyles} data-state="open" onClick={cancelEdit} - color={{ base: "red.500", _dark: "red.400" }} - _hover={{ bg: "rgba(245, 101, 101, 0.1)" }} + color={{base: "red.500", _dark: "red.400"}} + _hover={{bg: "rgba(245, 101, 101, 0.1)"}} > - + Discard ) : ( // πŸ‘€ VIEW MODE: The Static Dropdown Menu - + {/* size increased to 24 to match tabs. flexShrink: 0 is CRITICAL */} - + Dashboard Settings @@ -148,215 +148,222 @@ export function SettingsButton() { - Edit Layout + Edit Layout - + - setIsLibraryOpen(true)} - cursor="pointer" - _hover={{ - bg: {base: "rgba(0, 0, 0, 0.05)", _dark: "rgba(255, 255, 255, 0.1)"}, - WebkitTextStroke: "0.5px currentColor" - }} - > - Widget Library - - - - - )} - - {/* πŸ“šπŸ“šπŸ“š THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} - setIsLibraryOpen(e.open)} - size="xl" - placement="center" - closeOnInteractOutside={true} - closeOnEscapeKeyDown={true} + setIsLibraryOpen(true)} + cursor="pointer" + _hover={{ + bg: {base: "rgba(0, 0, 0, 0.05)", _dark: "rgba(255, 255, 255, 0.1)"}, + WebkitTextStroke: "0.5px currentColor" + }} > - {/* Soft blur can be added to the background behind the modal */} - {/**/} - + Widget Library + + + + + )} - + {/* πŸ“šπŸ“šπŸ“š THE WIDGET LIBRARY MODAL (Controlled by isLibraryOpen) */} + setIsLibraryOpen(e.open)} + size="xl" + placement="center" + closeOnInteractOutside={true} + closeOnEscapeKeyDown={true} + > + {/* Soft blur can be added to the background behind the modal */} + {/**/} + - - Widgets Shop - + - - - + + Widgets Shop + - {/* LEFT GLASS CRESCENT */} - {canScrollLeft && ( - tabsRef.current.scrollBy({ left: -200, behavior: 'smooth' })} - // The Glass Look hugging the left pill edge - bg={{ base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)" }} - backdropFilter="blur(8px)" - borderTopLeftRadius="full" - borderBottomLeftRadius="full" - // Fades the inner edge so it blends into the track - boxShadow={{ base: "10px 0 15px -5px rgba(255,255,255,0.8)", _dark: "10px 0 15px -5px rgba(30,30,30,0.9)" }} - color={{ base: "gray.600", _dark: "gray.300" }} - _hover={{ color: { base: "blue.500", _dark: "blue.300" } }} - > - - - )} - { - if (e.currentTarget) { - e.currentTarget.scrollLeft += e.deltaY; - } - }} - > - {categories.map(cat => ( - - {cat} - - ))} - - {canScrollRight && ( - tabsRef.current.scrollBy({ left: 200, behavior: 'smooth' })} - // The Glass Look hugging the right pill edge - bg={{ base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)" }} - backdropFilter="blur(8px)" - borderTopRightRadius="full" - borderBottomRightRadius="full" - boxShadow={{ base: "-10px 0 15px -5px rgba(255,255,255,0.8)", _dark: "-10px 0 15px -5px rgba(30,30,30,0.9)" }} - color={{ base: "gray.600", _dark: "gray.300" }} - _hover={{ color: { base: "blue.500", _dark: "blue.300" } }} - > - - - )} + + + - - {categories.map(cat => ( - - - {categorizedWidgets[cat].map(widget => ( - tabsRef.current.scrollBy({left: -200, behavior: 'smooth'})} + // The Glass Look hugging the left pill edge + bg={{base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)"}} + backdropFilter="blur(8px)" + borderTopLeftRadius="full" + borderBottomLeftRadius="full" + // Fades the inner edge so it blends into the track + boxShadow={{ + base: "10px 0 15px -5px rgba(255,255,255,0.8)", + _dark: "10px 0 15px -5px rgba(30,30,30,0.9)" + }} + color={{base: "gray.600", _dark: "gray.300"}} + _hover={{color: {base: "blue.500", _dark: "blue.300"}}} + > + + + )} + { + if (e.currentTarget) { + e.currentTarget.scrollLeft += e.deltaY; + } + }} + > + {categories.map(cat => ( + + {cat} + + ))} + + {canScrollRight && ( + tabsRef.current.scrollBy({left: 200, behavior: 'smooth'})} + // The Glass Look hugging the right pill edge + bg={{base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)"}} + backdropFilter="blur(8px)" + borderTopRightRadius="full" + borderBottomRightRadius="full" + boxShadow={{ + base: "-10px 0 15px -5px rgba(255,255,255,0.8)", + _dark: "-10px 0 15px -5px rgba(30,30,30,0.9)" + }} + color={{base: "gray.600", _dark: "gray.300"}} + _hover={{color: {base: "blue.500", _dark: "blue.300"}}} + > + + + )} + + + {categories.map(cat => ( + + + {categorizedWidgets[cat].map(widget => ( + + {widget.name} + {widget.description} + + + + Add to Dashboard: + + + {Object.keys(widget.allowedSizes).map(sizeKey => ( + handleAddWidget(widget.id, sizeKey)} + color={{base: "blue.600", _dark: "blue.300"}} > - {widget.name} - {widget.description} + {/* 🚨 THE ABBREVIATION ICON: Always visible */} + + {getSizeAbbreviation(sizeKey)} + - - - Add to Dashboard: - - - {Object.keys(widget.allowedSizes).map(sizeKey => ( - handleAddWidget(widget.id, sizeKey)} - color={{base: "blue.600", _dark: "blue.300"}} - > - {/* 🚨 THE ABBREVIATION ICON: Always visible */} - - {getSizeAbbreviation(sizeKey)} - - - {/* 🚨 THE EXPANDED TEXT: Only visible on hover */} - Add Widget - - ))} - - - + {/* 🚨 THE EXPANDED TEXT: Only visible on hover */} + Add Widget + ))} - - - ))} - - - - - - + + + + ))} + + + ))} + + + + + + ); } \ No newline at end of file From 8ddf9ff393a729a4dde4e236a4674b955dce95fc Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Fri, 21 Aug 2026 13:30:02 -0700 Subject: [PATCH 16/22] Update chevrons are updated for better UI --- .../components/dashboard/SettingsButton.jsx | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index 8038b94..f574901 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -183,7 +183,7 @@ export function SettingsButton() { {/**/} - + @@ -192,32 +192,36 @@ export function SettingsButton() { - + {/* LEFT GLASS CRESCENT */} {canScrollLeft && ( tabsRef.current.scrollBy({left: -200, behavior: 'smooth'})} - // The Glass Look hugging the left pill edge bg={{base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)"}} - backdropFilter="blur(8px)" + backdropFilter="blur(1px)" borderTopLeftRadius="full" borderBottomLeftRadius="full" - // Fades the inner edge so it blends into the track - boxShadow={{ - base: "10px 0 15px -5px rgba(255,255,255,0.8)", - _dark: "10px 0 15px -5px rgba(30,30,30,0.9)" - }} + + // THE MAGIC: Bites a transparent circle out of the flat right side! + WebkitMaskImage="radial-gradient(circle at 100% 50%, transparent 30px, black 19px)" + maskImage="radial-gradient(circle at 100% 50%, transparent 30px, black 35px)" + color={{base: "gray.600", _dark: "gray.300"}} - _hover={{color: {base: "blue.500", _dark: "blue.300"}}} + transition="background-color 0.2s" + _hover={{ + color: {base: "blue.500", _dark: "blue.300"}, + bg: {base: "rgba(255, 255, 255, 0.9)", _dark: "rgba(40, 40, 40, 0.95)"} + }} > - + {/* Shifted slightly left to optically center it in the crescent mass */} + )} tabsRef.current.scrollBy({left: 200, behavior: 'smooth'})} - // The Glass Look hugging the right pill edge bg={{base: "rgba(255, 255, 255, 0.7)", _dark: "rgba(30, 30, 30, 0.8)"}} - backdropFilter="blur(8px)" + backdropFilter="blur(1px)" borderTopRightRadius="full" borderBottomRightRadius="full" - boxShadow={{ - base: "-10px 0 15px -5px rgba(255,255,255,0.8)", - _dark: "-10px 0 15px -5px rgba(30,30,30,0.9)" - }} + + // THE MAGIC: Bites a transparent circle out of the flat left side! + WebkitMaskImage="radial-gradient(circle at 0% 50%, transparent 30px, black 19px)" + maskImage="radial-gradient(circle at 0% 50%, transparent 30px, black 35px)" + color={{base: "gray.600", _dark: "gray.300"}} - _hover={{color: {base: "blue.500", _dark: "blue.300"}}} + transition="background-color 0.2s" + _hover={{ + color: {base: "blue.500", _dark: "blue.300"}, + bg: {base: "rgba(255, 255, 255, 0.9)", _dark: "rgba(40, 40, 40, 0.95)"} + }} > - + {/* Shifted slightly right to optically center it in the crescent mass */} + )} @@ -330,7 +336,7 @@ export function SettingsButton() { onClick={() => handleAddWidget(widget.id, sizeKey)} color={{base: "blue.600", _dark: "blue.300"}} > - {/* 🚨 THE ABBREVIATION ICON: Always visible */} + {/* THE ABBREVIATION ICON: Always visible */} - {/* 🚨 THE EXPANDED TEXT: Only visible on hover */} + {/* THE EXPANDED TEXT: Only visible on hover */} Add Widget ))} From 1e1130f9658864dda06446bf0f1afb5f2eab71d6 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Fri, 21 Aug 2026 14:39:57 -0700 Subject: [PATCH 17/22] Update widget library will now open to a percentage of screen --- client/src/components/dashboard/SettingsButton.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/dashboard/SettingsButton.jsx b/client/src/components/dashboard/SettingsButton.jsx index f574901..751397f 100644 --- a/client/src/components/dashboard/SettingsButton.jsx +++ b/client/src/components/dashboard/SettingsButton.jsx @@ -183,7 +183,7 @@ export function SettingsButton() { {/**/} - + From 12e540b61bd2029a3fca4b2c8ddae7d07b8c1578 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Mon, 24 Aug 2026 08:58:31 -0700 Subject: [PATCH 18/22] Fix adjustment to VDAP logo and emoji --- client/src/components/layout/Sidebar.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/components/layout/Sidebar.jsx b/client/src/components/layout/Sidebar.jsx index 68d2dbf..81682b2 100644 --- a/client/src/components/layout/Sidebar.jsx +++ b/client/src/components/layout/Sidebar.jsx @@ -43,9 +43,10 @@ export default function Sidebar() { fontWeight="bold" height="auto" // Prevents the large text from squashing vertically overflow="hidden" + width="100%" px={0} - justifyContent={isOpen ? "center" : "center"} // Center the 'V' when collapsed - ml={isOpen ? 3 : 0} // Align with other buttons when open + justifyContent={isOpen ? "flex-start" : "center"} // Center the 'V' when collapsed + ml={isOpen ? 2 : -1} // Align with other buttons when open textStyle={isOpen ? "4xl" : "3xl"} // Scale down slightly when closed > {isOpen ? "πŸŒ‹ VDAP" : "πŸŒ‹"} From 816046d05ac323da5351fbc56663100f19ac9e7b Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Mon, 24 Aug 2026 10:58:09 -0700 Subject: [PATCH 19/22] Fix the color mode button was highlighting odd. removed highlight --- client/src/components/layout/Sidebar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/layout/Sidebar.jsx b/client/src/components/layout/Sidebar.jsx index 81682b2..5188882 100644 --- a/client/src/components/layout/Sidebar.jsx +++ b/client/src/components/layout/Sidebar.jsx @@ -107,7 +107,7 @@ export default function Sidebar() { {/* Theme Toggler */} - + ); From c45bc778ecf536b75677a0e87718d6b18fb823d1 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Wed, 26 Aug 2026 13:04:17 -0700 Subject: [PATCH 20/22] Refactor, cleaned up sidebar and theme --- client/src/components/layout/Sidebar.jsx | 141 ++++++++--------------- client/src/theme.js | 50 +++++++- 2 files changed, 98 insertions(+), 93 deletions(-) diff --git a/client/src/components/layout/Sidebar.jsx b/client/src/components/layout/Sidebar.jsx index 5188882..2ea67ef 100644 --- a/client/src/components/layout/Sidebar.jsx +++ b/client/src/components/layout/Sidebar.jsx @@ -1,22 +1,24 @@ -import { useSlotRecipe, Box, Button, IconButton } from "@chakra-ui/react"; -import { ColorModeButton } from "@/components/ui/color-mode.jsx"; -import { useState } from "react"; -import { NavLink } from "react-router-dom"; +import {useSlotRecipe, Box, Button, IconButton} from "@chakra-ui/react"; +import {ColorModeButton} from "@/components/ui/color-mode.jsx"; +import {useState} from "react"; +import {NavLink} from "react-router-dom"; import Tooltip from "../ui/Tooltip.jsx"; -import { Mountain, House, Earth, MapPin, ShieldUser, Menu, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; +import {Mountain, House, Earth, MapPin, ShieldUser, Menu, PanelLeftClose, PanelLeftOpen} from 'lucide-react'; const SIDEBAR_DEFAULT_BUTTONS = [ - { icon: House, name: "Home", url: "/home" }, - { icon: Earth, name: "Global Map", url: "/global-map" }, - { icon: MapPin, name: "Regional Map", url: "/regional-map" }, - { icon: Mountain, name: "Volcano", url: "/volcano" }, - { icon: ShieldUser, name: "Admin", url: "/admin" } // TODO remove in the future as default should not have admin + {icon: House, name: "Home", url: "/home"}, + {icon: Earth, name: "Global Map", url: "/global-map"}, + {icon: MapPin, name: "Regional Map", url: "/regional-map"}, + {icon: Mountain, name: "Volcano", url: "/volcano"}, + {icon: ShieldUser, name: "Admin", url: "/admin"} // TODO remove in the future as default should not have admin ]; export default function Sidebar() { + const [isOpen, setIsOpen] = useState(true); - const recipe = useSlotRecipe({ key: "sidebar" }); - const styles = recipe(); + + const recipe = useSlotRecipe({key: "sidebar"}); + const styles = recipe({state: isOpen ? "open" : "closed"}); // TODO we will need to call buttons here by authorized user, for now we update the visible buttons as simply the default buttons const visibleButtons = SIDEBAR_DEFAULT_BUTTONS; @@ -24,87 +26,46 @@ export default function Sidebar() { const toggleSidebar = () => setIsOpen((prev) => !prev); return ( - + {/* Home Link Logo */} + - - - - {/* Navigation Buttons */} - {visibleButtons.map((button) => { - const Icon = button.icon; - return ( - // - - // - ); - })} - - {/* Menu Trigger */} - - {isOpen ? : } - + {/* Navigation buttons are [icon text] */} + + + {button.name} + + + ); + })} + + {/* Menu Trigger */} + + {isOpen ? : } + {/* Theme Toggler */} diff --git a/client/src/theme.js b/client/src/theme.js index d59c24f..a68768d 100644 --- a/client/src/theme.js +++ b/client/src/theme.js @@ -29,7 +29,7 @@ const canvasRecipe = defineRecipe({ }); const sidebarRecipe = defineSlotRecipe({ - slots: ["root", "close_button", "buttons", "dark_mode_button"], + slots: ["root", "logo", "footer", "button_text","buttons", "collapse_button"], base: { root: { // 1. Semi-transparent backgrounds @@ -57,14 +57,17 @@ const sidebarRecipe = defineSlotRecipe({ margin: "8px", display: "flex", flexDirection: "column", + position: "relative", justifyContent: "start", alignItems: "stretch", - gap: 2, + gap: 4, + transition: "width 0.4s ease", // CRITICAL: Adjust padding and overflow to allow the button to touch the right edge pl: 2, // Keep left padding py: 2, // Keep top/bottom padding pr: 0, // Remove right padding + pt: 4, // Added top padding to give the logo room to breathe overflow: "visible", // Allows the curved corner patches to render outside the button zIndex: 10, }, @@ -72,10 +75,12 @@ const sidebarRecipe = defineSlotRecipe({ position: "relative", // Required to anchor the corner patches bg: "transparent", + justifyContent: "flex-start", transition: "background-color 0.4s ease, color 0.4s ease", "& svg": { - transition: "stroke-width 0.4s ease" + transition: "stroke-width 0.4s ease", + flexShrink: "0" }, _hover: { @@ -138,6 +143,45 @@ const sidebarRecipe = defineSlotRecipe({ } }, }, + logo: { + width: "100%", + height: "auto", + overflow: "hidden", + fontWeight: "bold", + px: 0, + }, + footer: { + mt: "auto", + display: "flex", + justifyContent: "center", + alignItems: "center", + pb: 4, + pr: 2, + transition: "opacity 0.3s ease, visibility 0.3s ease, margin 0.3s ease" + }, + button_text: { + transition: "opacity 0.3s ease, visibility 0.3s ease, margin 0.3s ease", + whiteSpace: "nowrap" + }, + collapse_button: { + "& svg": { flexShrink: "0" }, + }, + }, + variants: { + state: { + open: { + root: { width: "190px" }, + logo: { justifyContent: "flex-start", ml: 2, textStyle: "4xl" }, + footer: { gap: 16, flexDirection: "row" }, + button_text: {ml: 3, opacity: 1, visibility: "visible"}, + }, + closed: { + root: { width: "71px" }, + logo: { justifyContent: "center", ml: -1, textStyle: "3xl" }, + footer: { gap: 2, flexDirection: "column-reverse" }, + button_text: {ml: 0, opacity: 0, visibility: "hidden"}, + } + } } }); From 8e9b17f6ddfba5327566543c7bda19527deaef53 Mon Sep 17 00:00:00 2001 From: Christopher Hight Date: Wed, 26 Aug 2026 13:10:02 -0700 Subject: [PATCH 21/22] Refactor, cleaned up header file --- client/src/components/layout/Header.jsx | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/client/src/components/layout/Header.jsx b/client/src/components/layout/Header.jsx index 5259c35..82db471 100644 --- a/client/src/components/layout/Header.jsx +++ b/client/src/components/layout/Header.jsx @@ -20,20 +20,11 @@ export default function Header() { const displayEmail = user?.email || "LDAP User"; return ( - - {/*/!* Home Link Logo *!/*/} - {/**/} + - {/* User Profile Section */} - {displayName} - {/**/} - {/* {displayName}*/} - {/* {displayEmail}*/} - {/**/} + {displayName} Date: Wed, 2 Sep 2026 12:45:40 -0700 Subject: [PATCH 22/22] Refactor CSS on mydashboard started needs completed --- client/src/pages/home/tabs/MyDashboard.jsx | 7 +--- client/src/theme.js | 40 ++++++++++++++-------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/client/src/pages/home/tabs/MyDashboard.jsx b/client/src/pages/home/tabs/MyDashboard.jsx index 1e3dbad..aed386f 100644 --- a/client/src/pages/home/tabs/MyDashboard.jsx +++ b/client/src/pages/home/tabs/MyDashboard.jsx @@ -3,7 +3,7 @@ import {X, MoreVertical} from "lucide-react"; import {Responsive, WidthProvider} from "react-grid-layout"; import {useDashboardStore} from "@/store/useDashboardStore.jsx"; import {Suspense, useEffect, useRef} from 'react'; -import {useColorModeValue} from "@/components/ui/color-mode"; + // 1. Import database messenger import useAuthStore from "@/store/authStore"; @@ -14,11 +14,6 @@ import {WIDGET_REGISTRY} from '@/constants/widgetRegistry.jsx'; const ResponsiveGridLayout = WidthProvider(Responsive); export default function MyDashboard() { - // πŸŒ— Let's adjust for our light and dark mode - const frameBg = useColorModeValue("FFFFFF0A", "#FFFFFF0A"); - const titleColor = useColorModeValue("gray.800", "#FFFFFF"); - const iconColor = useColorModeValue("gray.600", "#FFFFFF"); - const iconHoverBg = useColorModeValue("gray.100", "gray.700"); // ☁️ Database Connection const {user} = useAuthStore(); diff --git a/client/src/theme.js b/client/src/theme.js index a68768d..41e836d 100644 --- a/client/src/theme.js +++ b/client/src/theme.js @@ -14,20 +14,6 @@ const headerRecipe = defineRecipe({ } }); -const dashboardRecipe = defineRecipe({ - base: { - bg: "{MainBackground}" - } -}); - -const canvasRecipe = defineRecipe({ - base: { - bg: "{transparent}", - zIndex: "-1", - color: "{text}" - } -}); - const sidebarRecipe = defineSlotRecipe({ slots: ["root", "logo", "footer", "button_text","buttons", "collapse_button"], base: { @@ -185,6 +171,20 @@ const sidebarRecipe = defineSlotRecipe({ } }); +const dashboardRecipe = defineRecipe({ + base: { + bg: "{MainBackground}" + } +}); + +const canvasRecipe = defineRecipe({ + base: { + bg: "{transparent}", + zIndex: "-1", + color: "{text}" + } +}); + const widgetRecipe = defineSlotRecipe({ slots: ["root", "header", "canvas"], base: { @@ -268,6 +268,17 @@ const glassPanelRecipe = defineSlotRecipe({ WebkitBackdropFilter: "blur(15px)", } } + }, + mode: { + editing: { + root: { + boxShadow: "outline", + borderStyle: "dashed", + borderWidth: "2px", + borderColor: "orange.300", + cursor: "grab", + } + } } }, @@ -275,6 +286,7 @@ const glassPanelRecipe = defineSlotRecipe({ layout: "widget" } }); + const glassTabRecipe = defineRecipe({ base: { display: "flex",