diff --git a/client/src/App.jsx b/client/src/App.jsx index 287e35d..e507f47 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 ); 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/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) */} 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 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 */}