From 92676cd12cf13bd46be130efb38d36a1fda201da Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Tue, 18 Mar 2025 09:51:33 +0530 Subject: [PATCH] updated real time viz ui --- .../styles/components/sideBar/_data.scss | 5 +- .../src/assets/styles/pages/_realTimeViz.scss | 53 ++ frontend/src/components/layout/sideBar.tsx | 59 ++- .../ui/sideBar/realTimeViz/chartComponent.tsx | 70 +-- .../ui/sideBar/realTimeViz/data.tsx | 213 ++++---- .../ui/sideBar/realTimeViz/design.tsx | 116 +++-- .../ui/sideBar/realTimeViz/widgets.tsx | 218 +++++---- .../realTimeVisualization/progressCard .tsx | 22 + .../realTimeVisualization.tsx | 459 ++++++++++++------ frontend/src/store/store.ts | 93 ++-- 10 files changed, 783 insertions(+), 525 deletions(-) create mode 100644 frontend/src/pages/realTimeVisualization/progressCard .tsx diff --git a/frontend/src/assets/styles/components/sideBar/_data.scss b/frontend/src/assets/styles/components/sideBar/_data.scss index af419f0..078c6c8 100644 --- a/frontend/src/assets/styles/components/sideBar/_data.scss +++ b/frontend/src/assets/styles/components/sideBar/_data.scss @@ -58,8 +58,11 @@ } } + + + .child { - padding-left: 13%; + // padding-left: 13%; width: 100%; gap: 6px; } diff --git a/frontend/src/assets/styles/pages/_realTimeViz.scss b/frontend/src/assets/styles/pages/_realTimeViz.scss index ae31bb6..6f508df 100644 --- a/frontend/src/assets/styles/pages/_realTimeViz.scss +++ b/frontend/src/assets/styles/pages/_realTimeViz.scss @@ -27,6 +27,50 @@ overflow-y: auto; padding-bottom: 40px; + .progressBar { + height: auto !important; + padding: 12px 10px 41px 10px; + display: flex; + flex-direction: column; + gap: 6px; + + .header { + display: flex; + justify-content: center; + align-items: center; + border-bottom: none; + } + + .stock { + padding: 13px 5px; + background-color: #E0DFFF80; + border-radius: 6.33px; + display: flex; + justify-content: space-between; + + .stock-item { + .stockValues { + display: flex; + flex-direction: row-reverse; + align-items: flex-end; + gap: 3px; + + .value { + color: #4a90e2; + font-size: 16px; + } + } + + .stock-description { + font-size: 12px; + } + } + + } + } + + + &::-webkit-scrollbar { display: none; } @@ -63,6 +107,15 @@ padding: 4px 8px; white-space: nowrap; } + + .active { + background-color: #4a90e2; + color: #FCFDFD !important; + } + } + + .zoon-wrapper.bottom { + bottom: 210px; } diff --git a/frontend/src/components/layout/sideBar.tsx b/frontend/src/components/layout/sideBar.tsx index 1ab0134..56d13d7 100644 --- a/frontend/src/components/layout/sideBar.tsx +++ b/frontend/src/components/layout/sideBar.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo, useCallback } from "react"; import asset1 from "../../assets/images/tempimages/asset1.png"; import asset2 from "../../assets/images/tempimages/asset2.png"; import asset3 from "../../assets/images/tempimages/asset3.png"; @@ -39,33 +39,41 @@ const SideBar: React.FC = ({ header, defaultActive }) => { const [filteredData, setFilteredData] = useState([]); const [inputData, setInputData] = useState(""); - const [assets, setAssets] = useState([]); + const assets: FilteredAssets[] = useMemo( + () => [ + { filename: "Asset 1", thumbnail: asset1, modelfileID: "1" }, + { filename: "Asset 2", thumbnail: asset2, modelfileID: "2" }, + { filename: "Asset 3", thumbnail: asset3, modelfileID: "3" }, + { filename: "Asset 4", thumbnail: asset4, modelfileID: "4" }, + { filename: "Asset 5", thumbnail: asset5, modelfileID: "5" }, + { filename: "Asset 6", thumbnail: asset6, modelfileID: "6" }, + { filename: "Asset 7", thumbnail: asset7, modelfileID: "7" }, + { filename: "Asset 8", thumbnail: asset8, modelfileID: "8" }, + { filename: "Asset 9", thumbnail: asset9, modelfileID: "9" }, + ], + [] + ); - // const assets: FilteredAssets[] = [ - // { name: "Asset 1", img: asset1 }, - // { name: "Asset 2", img: asset2 }, - // { name: "Asset 3", img: asset3 }, - // { name: "Asset 4", img: asset4 }, - // { name: "Asset 5", img: asset5 }, - // { name: "Asset 6", img: asset6 }, - // { name: "Asset 7", img: asset7 }, - // { name: "Asset 8", img: asset8 }, - // { name: "Asset 9", img: asset9 }, - // ]; + // Memoized Input Change Handler + const handleInputChange = useCallback( + (event: React.ChangeEvent) => { + const value = event.target.value; + setInputData(value); - const handleInputChange = (event: React.ChangeEvent) => { - const value = event.target.value; - setInputData(value); - - const filtered = assets.filter((asset) => - asset.filename.toLowerCase().includes(value.toLowerCase()) - ); - setFilteredData(filtered); - }; + const filtered = assets.filter((asset) => + asset.filename.toLowerCase().includes(value.toLowerCase()) + ); + setFilteredData(filtered); + }, + [assets] + ); + // Update Active State Only When Necessary useEffect(() => { - setActive(defaultActive || header[0]); - }, [header, defaultActive]); + if (defaultActive && defaultActive !== active) { + setActive(defaultActive); + } + }, [defaultActive]); return (
= ({ header, defaultActive }) => { ))}
+ {/* Render Components Based on Active Header */} {active === "Outline" && } {active === "Asset library" && } {active === "Overview" && } @@ -96,4 +105,4 @@ const SideBar: React.FC = ({ header, defaultActive }) => { ); }; -export default SideBar; +export default React.memo(SideBar); diff --git a/frontend/src/components/ui/sideBar/realTimeViz/chartComponent.tsx b/frontend/src/components/ui/sideBar/realTimeViz/chartComponent.tsx index 7065e72..52ea262 100644 --- a/frontend/src/components/ui/sideBar/realTimeViz/chartComponent.tsx +++ b/frontend/src/components/ui/sideBar/realTimeViz/chartComponent.tsx @@ -1,13 +1,23 @@ import React, { useEffect, useRef, useMemo } from "react"; import { Chart, ChartType } from "chart.js/auto"; -import { useThemeStore, useWidgetStore } from "../../../../store/store"; +import { useThemeStore } from "../../../../store/store"; +// Define Props Interface interface ChartComponentProps { - type: ChartType; - title: string; - fontFamily?: string; - fontSize?: string; - fontWeight?: "Light" | "Regular" | "Bold"; // Explicitly typing fontWeight + type: any; // Type of chart (e.g., "bar", "line", etc.) + title: string; // Title of the chart + fontFamily?: string; // Optional font family for the chart title + fontSize?: string; // Optional font size for the chart title + fontWeight?: "Light" | "Regular" | "Bold"; // Optional font weight for the chart title + data: { + labels: string[]; // Labels for the x-axis + datasets: { + data: number[]; // Data points for the chart + backgroundColor: string; // Background color for the chart + borderColor: string; // Border color for the chart + borderWidth: number; // Border width for the chart + }[]; + }; // Data for the chart } const ChartComponent = ({ @@ -16,11 +26,12 @@ const ChartComponent = ({ fontFamily, fontSize, fontWeight = "Regular", // Default to "Regular" + data: propsData, }: ChartComponentProps) => { const canvasRef = useRef(null); const { themeColor } = useThemeStore(); - // Memoize theme colors to prevent unnecessary recalculations + // Memoize Theme Colors to Prevent Unnecessary Recalculations const buttonActionColor = useMemo( () => themeColor[0] || "#5c87df", [themeColor] @@ -30,7 +41,7 @@ const ChartComponent = ({ [themeColor] ); - // Memoize font weight mapping + // Memoize Font Weight Mapping const chartFontWeightMap = useMemo( () => ({ Light: "lighter" as const, @@ -40,19 +51,19 @@ const ChartComponent = ({ [] ); - // Parse and memoize fontSize + // Parse and Memoize Font Size const fontSizeValue = useMemo( () => (fontSize ? parseInt(fontSize) : 12), [fontSize] ); - // Determine and memoize font weight + // Determine and Memoize Font Weight const fontWeightValue = useMemo( - () => chartFontWeightMap[fontWeight], // No need for '|| "normal"' since fontWeight is guaranteed to be valid + () => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap] ); - // Memoize chart font style + // Memoize Chart Font Style const chartFontStyle = useMemo( () => ({ family: fontFamily || "Arial", @@ -62,23 +73,10 @@ const ChartComponent = ({ [fontFamily, fontSizeValue, fontWeightValue] ); - // Memoize chart data - const data = useMemo( - () => ({ - labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], - datasets: [ - { - data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: buttonActionColor, - borderColor: buttonAbortColor, - borderWidth: 1, - }, - ], - }), - [buttonActionColor, buttonAbortColor] - ); + // Memoize Chart Data + const data = useMemo(() => propsData, [propsData]); - // Memoize chart options + // Memoize Chart Options const options = useMemo( () => ({ responsive: true, @@ -97,6 +95,7 @@ const ChartComponent = ({ [title, chartFontStyle] ); + // Initialize Chart on Component Mount useEffect(() => { if (!canvasRef.current) return; @@ -105,10 +104,21 @@ const ChartComponent = ({ const chart = new Chart(ctx, { type, data, options }); + // Cleanup: Destroy the chart instance when the component unmounts return () => chart.destroy(); - }, [type, data, options]); // Only recreate chart when these essentials change + }, [type, data, options]); // Only recreate the chart when these dependencies change return ; }; -export default React.memo(ChartComponent); +export default React.memo(ChartComponent, (prevProps, nextProps) => { + // Custom comparison function to prevent unnecessary re-renders + return ( + prevProps.type === nextProps.type && + prevProps.title === nextProps.title && + prevProps.fontFamily === nextProps.fontFamily && + prevProps.fontSize === nextProps.fontSize && + prevProps.fontWeight === nextProps.fontWeight && + JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data) + ); +}); diff --git a/frontend/src/components/ui/sideBar/realTimeViz/data.tsx b/frontend/src/components/ui/sideBar/realTimeViz/data.tsx index 8ea9663..a4fa12a 100644 --- a/frontend/src/components/ui/sideBar/realTimeViz/data.tsx +++ b/frontend/src/components/ui/sideBar/realTimeViz/data.tsx @@ -1,6 +1,7 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import RegularDropDown from "../../inputs/regularDropDown"; import { LinkIcon, RemoveIcon } from "../../../../assets/images/svgExports"; +import { useWidgetStore } from "../../../../store/store"; interface Child { id: number; @@ -14,132 +15,126 @@ interface Group { } const Data = () => { + const { selectedChartId } = useWidgetStore(); const [selectedWidget] = useState("Widget 1"); - const [groups, setGroups] = useState([ - { - id: 1, - easing: "Connecter 1", - children: [ - { id: 1, easing: "Linear" }, - { id: 2, easing: "Ease Out" }, - { id: 3, easing: "Linear" }, - ], - }, - ]); - const handleLinkClick = (childId: number) => { - setGroups((currentGroups) => { - let sourceGroup: Group | undefined; - let sourceChild: Child | undefined; + // State to store groups for all widgets (using Widget.id as keys) + const [chartDataGroups, setChartDataGroups] = useState< + Record + >({}); - // Find the source group and child - for (const group of currentGroups) { - sourceChild = group.children.find((c) => c.id === childId); - if (sourceChild) { - sourceGroup = group; - break; - } - } + useEffect(() => { + console.log("Selected Chart ID:", selectedChartId); - if (!sourceGroup || !sourceChild) return currentGroups; + // Initialize data groups for the newly selected widget if it doesn't exist + if (selectedChartId && !chartDataGroups[selectedChartId.id]) { + setChartDataGroups((prev) => ({ + ...prev, + [selectedChartId.id]: [ + { + id: Date.now(), + easing: "Connecter 1", + children: [ + { id: Date.now(), easing: "Linear" }, + { id: Date.now() + 1, easing: "Ease Out" }, + { id: Date.now() + 2, easing: "Linear" }, + ], + }, + ], + })); + } + }, [selectedChartId]); - // Remove child from source group - const updatedGroups = currentGroups - .map((group) => ({ - ...group, - children: group.children.filter((c) => c.id !== childId), - })) - .filter((group) => group.children.length > 0); + // Handle linking children between groups + const handleLinkClick = (childId: number) => {}; - // Find or create target group - const targetGroup = updatedGroups.find( - (g) => g.id !== sourceGroup!.id && g.easing === sourceGroup!.easing - ); + // Remove a child from a group + const removeChild = (groupId: number, childId: number) => { + setChartDataGroups((currentGroups) => { + if (!selectedChartId) return currentGroups; - if (targetGroup) { - targetGroup.children.push(sourceChild); - } else { - updatedGroups.push({ - id: Date.now(), - easing: sourceGroup.easing, - children: [sourceChild], - }); - } + const currentChartData = currentGroups[selectedChartId.id] || []; - return updatedGroups; + return { + ...currentGroups, + [selectedChartId.id]: currentChartData.map((group) => + group.id === groupId + ? { + ...group, + children: group.children.map((child) => + child.id === childId ? { ...child, easing: "Linear" } : child + ), + } + : group + ), + }; }); }; - const removeChild = (groupId: number, childId: number) => { - setGroups((currentGroups) => - currentGroups.map((group) => { - if (group.id === groupId) { - return { - ...group, - children: group.children.map((child) => - child.id === childId ? { ...child, easing: "Linear" } : child - ), - }; - } - return group; - }) - ); - }; - + console.log("selectedChartId: ", selectedChartId?.title); return (
-
{selectedWidget}
- {groups.map((group) => ( -
-
- -
Data from
- { - setGroups( - groups.map((g) => (g.id === group.id ? { ...g, easing } : g)) - ); - }} - /> -
- {group.children.map((child) => ( -
-
Input {child.id}
- {/* Pass the current easing as the header */} + {selectedChartId?.title && ( +
{selectedChartId?.title}
+ )} + {selectedChartId && + chartDataGroups[selectedChartId.id]?.map((group) => ( +
+ {/*
+ +
Data from
{ - setGroups( - groups.map((g) => ({ - ...g, - children: g.children.map((c) => - c.id === child.id ? { ...c, easing } : c - ), - })) - ); + setChartDataGroups((prev) => ({ + ...prev, + [selectedChartId.id]: prev[selectedChartId.id].map((g) => + g.id === group.id ? { ...g, easing } : g + ), + })); }} /> -
handleLinkClick(child.id)}> - +
*/} + {/* Render children only if there is a selected chart */} + {group.children.map((child) => ( +
+
Input
+ { + setChartDataGroups((prev) => ({ + ...prev, + [selectedChartId.id]: prev[selectedChartId.id].map( + (g) => ({ + ...g, + children: g.children.map((c) => + c.id === child.id ? { ...c, easing } : c + ), + }) + ), + })); + }} + /> +
handleLinkClick(child.id)}> + +
+
removeChild(group.id, child.id)} + > + +
-
removeChild(group.id, child.id)} - > - -
-
- ))} -
- ))} + ))} +
+ ))}
i

diff --git a/frontend/src/components/ui/sideBar/realTimeViz/design.tsx b/frontend/src/components/ui/sideBar/realTimeViz/design.tsx index a1e29f7..5668718 100644 --- a/frontend/src/components/ui/sideBar/realTimeViz/design.tsx +++ b/frontend/src/components/ui/sideBar/realTimeViz/design.tsx @@ -1,22 +1,28 @@ import React, { useEffect, useState } from "react"; import RegularDropDown from "../../inputs/regularDropDown"; import { useWidgetStore } from "../../../../store/store"; -import styles from "./Design.module.scss"; // Import SCSS file import ChartComponent from "./chartComponent"; -type Side = "top" | "bottom" | "left" | "right"; - -// Define the Widget interface +// Define Props Interface +interface DesignProps {} interface Widget { id: string; - type: string; // Change this if the type is more specific - panel: Side; // You should define or import 'Side' type if not already defined + type: string; // Chart type (e.g., "bar", "line") + panel: "top" | "bottom" | "left" | "right"; // Panel location title: string; fontFamily?: string; fontSize?: string; fontWeight?: string; + data: { + labels: string[]; + datasets: { + data: number[]; + backgroundColor: string; + borderColor: string; + borderWidth: number; + }[]; + }; // Data for the chart } - const Design = () => { const [selectedName, setSelectedName] = useState("drop down"); const [selectedElement, setSelectedElement] = useState("drop down"); @@ -24,66 +30,70 @@ const Design = () => { const [selectedSize, setSelectedSize] = useState("drop down"); const [selectedWeight, setSelectedWeight] = useState("drop down"); + // Zustand Store Hooks const { selectedChartId, setSelectedChartId, widgets, setWidgets } = - useWidgetStore(); // Get selected chart ID and list of widgets + useWidgetStore(); - // Find the selected widget based on `selectedChartId` - const selectedWidget = selectedChartId - ? widgets.find((widget) => widget.id === selectedChartId.id) - : null; + // Log Selected Chart ID for Debugging - // Function to update the selected widget + // Handle Widget Updates const handleUpdateWidget = (updatedProperties: Partial) => { - if (!selectedWidget) { - return; - } + if (!selectedChartId) return; + + // Update the selectedChartId + const updatedChartId = { + ...selectedChartId, + ...updatedProperties, + }; + setSelectedChartId(updatedChartId); // Update the widgets array const updatedWidgets = widgets.map((widget) => - widget.id === selectedWidget.id - ? { ...widget, ...updatedProperties } // Merge existing widget with updated properties + widget.id === selectedChartId.id + ? { ...widget, ...updatedProperties } : widget ); - - // Update the global state with the new widgets array setWidgets(updatedWidgets); - - // Update `selectedChartId` to reflect the changes - if (selectedChartId) { - const updatedChartId = { - ...selectedChartId, - ...updatedProperties, // Merge updated properties into `selectedChartId` - }; - setSelectedChartId(updatedChartId); - } }; - useEffect(() => { - // Log the current state of widgets for debugging - }, [widgets]); + // Default Chart Data + const defaultChartData = { + labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], + datasets: [ + { + data: [65, 59, 80, 81, 56, 55, 40], + backgroundColor: "#5c87df", + borderColor: "#ffffff", + borderWidth: 1, + }, + ], + }; return (

- {/* Display the selected widget's title */} + {/* Title of the Selected Widget */}
- {selectedWidget ? selectedWidget.title : "Widget 1"} -
-
- {/* Pass selectedWidget properties to ChartComponent */} - {selectedWidget && ( - - )} + {selectedChartId?.title || "Widget 1"}
- {/* Design Options */} + {/* Chart Component */} +
+ {/* {selectedChartId && ( + + )} */} +
+ + {/* Options Container */}
+ {/* Name Dropdown */}
Name { setSelectedName(value); @@ -92,22 +102,24 @@ const Design = () => { />
+ {/* Element Dropdown */}
Element { setSelectedElement(value); - handleUpdateWidget({ type: value }); // Update element type + handleUpdateWidget({ type: value }); }} />
+ {/* Font Family Dropdown */}
Font Family { setSelectedFont(value); @@ -116,10 +128,11 @@ const Design = () => { />
+ {/* Size Dropdown */}
Size { setSelectedSize(value); @@ -128,10 +141,11 @@ const Design = () => { />
+ {/* Weight Dropdown */}
Weight { setSelectedWeight(value); diff --git a/frontend/src/components/ui/sideBar/realTimeViz/widgets.tsx b/frontend/src/components/ui/sideBar/realTimeViz/widgets.tsx index de1a5c2..79746fc 100644 --- a/frontend/src/components/ui/sideBar/realTimeViz/widgets.tsx +++ b/frontend/src/components/ui/sideBar/realTimeViz/widgets.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState } from "react"; import { useThemeStore, useWidgetStore } from "../../../../store/store"; import { ChartType } from "chart.js/auto"; import ChartComponent from "./chartComponent"; @@ -7,7 +7,6 @@ import { SearchIcon, ThemeIcon, } from "../../../../assets/images/svgExports"; -import RegularDropDown from "../../inputs/regularDropDown"; const Widgets = () => { const chartTypes: ChartType[] = [ @@ -18,63 +17,14 @@ const Widgets = () => { "radar", "polarArea", ]; - const { setDraggedAsset } = useWidgetStore((state) => state); - const [selectedValue, setSelectedValue] = useState(null); + const [viewMode, setViewMode] = useState<"2D" | "3D" | "Floating">("2D"); const switchView = (mode: "2D" | "3D" | "Floating") => { setViewMode(mode); }; - const { themeColor, setThemeColor } = useThemeStore(); - const [theme, setTheme] = useState(false); // Default is false - const [activePresetIndex, setActivePresetIndex] = useState( - null - ); - const [customColor, setCustomColor] = useState("#000000"); - - const themeContainerRef = useRef(null); - - const preset = [ - ["#6F42C1", "#EEEEFE", "#B392F0"], - ["#F8CB47", "#F79002", "#F73F65"], - ["#FDA4B8", "#FF6A90", "#B91348"], - ["#D1BCF6", "#987BEB", "#6443C9"], - ["#FDC64B", "#EF9407", "#B54300"], - ["#69E9AB", "#0BB96E", "#087348"], - ["#85ADFC", "#246FFE", "#0050EB"], - ["#F570C7", "#27CEF7", "#FFAD1A"], - ["#6572F2", "#EE42B7", "#12B56E"], - ["#10BA68", "#FDB022", "#EA48B5"], - ["#10BA68", "#FDB022", "#EA48B5"], - ["#6F42C1", "#CEB2F6", "#EA48B5"], - ]; - - // Close theme container on outside click - useEffect(() => { - const themBtn = document.querySelector(".theme-switch"); - - const handleClickOutside = (event: MouseEvent) => { - // Check if the click is outside the theme container or theme button - if ( - themeContainerRef.current && - !themeContainerRef.current.contains(event.target as Node) && - !themBtn?.contains(event.target as Node) // Ensure that the click is not inside the theme button - ) { - setTheme(false); // Close the theme container when clicking outside - } - }; - - document.addEventListener("mousedown", handleClickOutside); - - return () => { - document.removeEventListener("mousedown", handleClickOutside); // Cleanup event listener on unmount - }; - }, []); - - useEffect(() => {}, [theme]); - return (
{/* Search Container */} @@ -87,7 +37,6 @@ const Widgets = () => {
- {/* Options and Theme Management */}
@@ -110,68 +59,24 @@ const Widgets = () => { Floating
- - {/* Theme Switcher */} -
setTheme(!theme)} // Toggle the theme container - > - -
- - {/* Theme Presets */} - {theme && ( -
-

Presets

-
- {preset.map((colors, index) => ( -
{ - setThemeColor(colors); - setActivePresetIndex(index); - }} - > - {colors.map((color, i) => ( -
- ))} -
- ))} -
-
-

Custom Color

-
- { - const newColor = e.target.value; - setCustomColor(newColor); - setThemeColor([newColor, newColor, newColor]); - }} - /> - {customColor} -
-
-
- )}
- {/* Chart Widgets */}
{chartTypes.map((type, index) => { const widgetTitle = `Widget ${index + 1}`; + const sampleData = { + labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], + datasets: [ + { + data: [65, 59, 80, 81, 56, 55, 40], + backgroundColor: "#5c87df", + borderColor: "#ffffff", + borderWidth: 1, + }, + ], + }; + return (
{ type, id: `widget-${index + 1}`, title: widgetTitle, + panel: "top", // Default panel assignment + data: sampleData, // Include data in the dragged asset }); }} onDragEnd={() => setDraggedAsset(null)} > - +
); })} +
{ + setDraggedAsset({ + type: "progress", // New widget type + id: `widget-7`, + title: "Widget 7", + panel: "top", + data: { + stocks: [ + { + key: "units", + value: 1000, + description: "Initial stock", + }, + ], + }, + }); + }} + onDragEnd={() => setDraggedAsset(null)} + > + {" "} +
Widget 7
+
+ + +
units
+
1000
+
+
Initial stock
+
+
Icon
+
+
+
{ + setDraggedAsset({ + type: "progress", + id: `widget-8`, + title: "Widget 8", + panel: "top", + data: { + stocks: [ + { + key: "units", + value: 1000, + description: "Initial stock", + }, + { + key: "units", + value: 500, + description: "Additional stock", + }, + ], + }, + }); + }} + onDragEnd={() => setDraggedAsset(null)} + > + {" "} +
Widget 8
+
+ + +
units
+
1000
+
+
Initial stock
+
+
Icon
+
+
+ + +
units
+
1000
+
+
Initial stock
+
+
Icon
+
+
); }; export default Widgets; + +// along with my charts i need to additionallly drag and drop my 2 widget 7 and widget 8 styled card to pannel diff --git a/frontend/src/pages/realTimeVisualization/progressCard .tsx b/frontend/src/pages/realTimeVisualization/progressCard .tsx new file mode 100644 index 0000000..8eb42ef --- /dev/null +++ b/frontend/src/pages/realTimeVisualization/progressCard .tsx @@ -0,0 +1,22 @@ +import React from "react"; + +export const ProgressCard = ({ title, data }: { + title: string; + data: { stocks: Array<{ key: string; value: number; description: string }> }; +}) => ( +
+
{title}
+ {data.stocks.map((stock, index) => ( +
+ + +
{stock.key}
+
{stock.value}
+
+
{stock.description}
+
+
Icon
+
+ ))} +
+); \ No newline at end of file diff --git a/frontend/src/pages/realTimeVisualization/realTimeVisualization.tsx b/frontend/src/pages/realTimeVisualization/realTimeVisualization.tsx index 82a0cec..268d3c1 100644 --- a/frontend/src/pages/realTimeVisualization/realTimeVisualization.tsx +++ b/frontend/src/pages/realTimeVisualization/realTimeVisualization.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useMemo, useState, useEffect, useRef } from "react"; import { DndContext, closestCenter, @@ -9,20 +9,20 @@ import { } from "@dnd-kit/core"; import { SortableContext, - useSortable, verticalListSortingStrategy, arrayMove, + useSortable, } from "@dnd-kit/sortable"; - -import { useWidgetStore } from "../../store/store"; // Assuming you have this store -import ChartComponent from "../../components/ui/sideBar/realTimeViz/chartComponent"; // Assuming this exists -import SideBar from "../../components/layout/sideBar"; // Assuming this exists +import { useWidgetStore } from "../../store/store"; +import ChartComponent from "../../components/ui/sideBar/realTimeViz/chartComponent"; +import SideBar from "../../components/layout/sideBar"; import { CleanPannel, DisableSorting, EyeIcon, LockIcon, -} from "../../assets/images/svgExports"; // Assuming these are your icon components +} from "../../assets/images/svgExports"; +import { ProgressCard } from "./progressCard "; type Side = "top" | "bottom" | "left" | "right"; @@ -38,15 +38,17 @@ const DraggableWidget = ({ widget }: { widget: any }) => { transition, isDragging, } = useSortable({ id: widget.id }); + const { setSelectedChartId } = useWidgetStore(); - const { selectedChartId, setSelectedChartId } = useWidgetStore(); - - const style = { - transform: transform - ? `translate3d(${transform.x}px, ${transform.y}px, 0)` - : undefined, - transition: transition || "transform 200ms ease", - }; + const style = useMemo( + () => ({ + transform: transform + ? `translate3d(${transform.x}px, ${transform.y}px, 0)` + : undefined, + transition: transition || "transform 200ms ease", + }), + [transform, transition] + ); const handlePointerDown = () => { if (!isDragging) { @@ -64,137 +66,276 @@ const DraggableWidget = ({ widget }: { widget: any }) => { style={style} onPointerDown={handlePointerDown} > - + {widget.type === "progress" ? ( + + ) : ( + + )}
); }; const RealTimeVisualization = () => { - const [zone, setZone] = useState([ - "Manufacturing unit", - "Assembly unit", - "Packing unit", - "Warehouse", - "Inventory", - ]); - - const [activeSides, setActiveSides] = useState([]); - const [panelOrder, setPanelOrder] = useState([]); - const [selectedSide, setSelectedSide] = useState(null); - const [lockedPanels, setLockedPanels] = useState([]); - const [activeExtraButton, setActiveExtraButton] = useState<{ - [key in Side]?: string; + const [selectedZone, setSelectedZone] = useState("Manufacturing unit"); + const [panelDimensions, setPanelDimensions] = useState<{ + [side in Side]?: { width: number; height: number }; }>({}); - const { draggedAsset, addWidget, widgets, setWidgets } = useWidgetStore(); + const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({}); + + const [zonesData, setZonesData] = useState<{ + [key: string]: { + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: { + id: string; + type: string; + title: string; + panel: Side; + data: any; + }[]; + }; + }>({ + "Manufacturing unit": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Assembly unit": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Packing unit": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + Warehouse: { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + Inventory: { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + }); + + useEffect(() => {}, [zonesData]); const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor) ); - const toggleSide = (side: Side) => { - setActiveSides((prev) => { - const newActive = prev.includes(side) - ? prev.filter((s) => s !== side) - : [...prev, side]; - setPanelOrder(newActive); + useEffect(() => { + const observers: ResizeObserver[] = []; + const currentPanelRefs = panelRefs.current; - if (prev.includes(side)) { - setSelectedSide(null); - } else { - setSelectedSide(side); + zonesData[selectedZone].activeSides.forEach((side) => { + const element = currentPanelRefs[side]; + if (element) { + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + const { width, height } = entry.contentRect; + setPanelDimensions((prev) => ({ + ...prev, + [side]: { width, height }, + })); + } + }); + observer.observe(element); + observers.push(observer); } + }); - return newActive; + return () => { + observers.forEach((observer) => observer.disconnect()); + }; + }, [zonesData[selectedZone].activeSides, selectedZone]); + + const toggleSide = (side: Side) => { + setZonesData((prev) => { + const zoneData = prev[selectedZone]; + const newActiveSides = zoneData.activeSides.includes(side) + ? zoneData.activeSides.filter((s) => s !== side) + : [...zoneData.activeSides, side]; + return { + ...prev, + [selectedZone]: { + ...zoneData, + activeSides: newActiveSides, + panelOrder: newActiveSides, + }, + }; }); }; const toggleLockPanel = (side: Side) => { - setLockedPanels((prev) => - prev.includes(side) - ? prev.filter((panel) => panel !== side) - : [...prev, side] - ); + setZonesData((prev) => { + const zoneData = prev[selectedZone]; + const newLockedPanels = zoneData.lockedPanels.includes(side) + ? zoneData.lockedPanels.filter((panel) => panel !== side) + : [...zoneData.lockedPanels, side]; + return { + ...prev, + [selectedZone]: { + ...zoneData, + lockedPanels: newLockedPanels, + }, + }; + }); }; - const getPanelStyle = (currentSide: Side) => { - const currentIndex = panelOrder.indexOf(currentSide); - const previousPanels = panelOrder.slice(0, currentIndex); + const getPanelStyle = useMemo( + () => (side: Side) => { + const currentIndex = zonesData[selectedZone].panelOrder.indexOf(side); + const previousPanels = zonesData[selectedZone].panelOrder.slice( + 0, + currentIndex + ); + const leftActive = previousPanels.includes("left"); + const rightActive = previousPanels.includes("right"); + const topActive = previousPanels.includes("top"); + const bottomActive = previousPanels.includes("bottom"); - const leftActive = previousPanels.includes("left"); - const rightActive = previousPanels.includes("right"); - const topActive = previousPanels.includes("top"); - const bottomActive = previousPanels.includes("bottom"); - - switch (currentSide) { - case "top": - case "bottom": - return { - width: `calc(100% - ${ - (leftActive ? 204 : 0) + (rightActive ? 204 : 0) - }px)`, - left: leftActive ? "204px" : "0", - right: rightActive ? "204px" : "0", - [currentSide]: "0", - height: "200px", - }; - - case "left": - case "right": - return { - height: `calc(100% - ${ - (topActive ? 204 : 0) + (bottomActive ? 204 : 0) - }px)`, - top: topActive ? "204px" : "0", - bottom: bottomActive ? "204px" : "0", - [currentSide]: "0", - width: "200px", - }; - - default: - return {}; - } - }; + switch (side) { + case "top": + case "bottom": + return { + width: `calc(100% - ${ + (leftActive ? 204 : 0) + (rightActive ? 204 : 0) + }px)`, + left: leftActive ? "204px" : "0", + right: rightActive ? "204px" : "0", + [side]: "0", + height: "200px", + }; + case "left": + case "right": + return { + height: `calc(100% - ${ + (topActive ? 204 : 0) + (bottomActive ? 204 : 0) + }px)`, + top: topActive ? "204px" : "0", + bottom: bottomActive ? "204px" : "0", + [side]: "0", + width: "200px", + }; + default: + return {}; + } + }, + [zonesData, selectedZone] + ); const handleDrop = (e: React.DragEvent, panel: Side) => { e.preventDefault(); + const { draggedAsset } = useWidgetStore.getState(); if (draggedAsset) { - addWidget({ ...draggedAsset, id: generateUniqueId(), panel }); + if (zonesData[selectedZone].lockedPanels.includes(panel)) return; + + const currentWidgetsInPanel = zonesData[selectedZone].widgets.filter( + (w) => w.panel === panel + ).length; + + const dimensions = panelDimensions[panel]; + const CHART_WIDTH = 200; // Width of each chart for top/bottom panels + const CHART_HEIGHT = 200; // Height of each chart for left/right panels + let maxCharts = 0; + + if (dimensions) { + if (panel === "top" || panel === "bottom") { + maxCharts = Math.floor(dimensions.width / CHART_WIDTH); // Use width for top/bottom + } else { + maxCharts = Math.floor(dimensions.height / CHART_HEIGHT); // Use height for left/right + } + } else { + maxCharts = panel === "top" || panel === "bottom" ? 5 : 3; // Default values + } + + if (currentWidgetsInPanel >= maxCharts) { + return; + } + + setZonesData((prev) => ({ + ...prev, + [selectedZone]: { + ...prev[selectedZone], + widgets: [ + ...prev[selectedZone].widgets, + { + ...draggedAsset, + id: generateUniqueId(), + panel, + }, + ], + }, + })); } }; const handleDragEnd = (event: any) => { const { active, over } = event; - if (!over) return; - const oldIndex = widgets.findIndex((widget) => widget.id === active.id); - const newPanel = - widgets.find((widget) => widget.id === over.id)?.panel || active.panel; - - if (active.panel === newPanel) { - const newIndex = widgets.findIndex((widget) => widget.id === over.id); - const reorderedWidgets = arrayMove(widgets, oldIndex, newIndex); - setWidgets(reorderedWidgets); - } else { - const updatedWidgets = widgets.map((widget) => - widget.id === active.id ? { ...widget, panel: newPanel } : widget + setZonesData((prev) => { + const zoneData = prev[selectedZone]; + const oldIndex = zoneData.widgets.findIndex( + (widget) => widget.id === active.id ); + const newPanel = + zoneData.widgets.find((widget) => widget.id === over.id)?.panel || + active.panel; - const widgetsInNewPanel = updatedWidgets.filter( - (w) => w.panel === newPanel - ); - const newIndex = widgetsInNewPanel.findIndex((w) => w.id === over.id); - - const reorderedWidgets = arrayMove(updatedWidgets, oldIndex, newIndex); - setWidgets(reorderedWidgets); - } + if (active.panel === newPanel) { + const newIndex = zoneData.widgets.findIndex( + (widget) => widget.id === over.id + ); + const reorderedWidgets = arrayMove( + zoneData.widgets, + oldIndex, + newIndex + ); + return { + ...prev, + [selectedZone]: { + ...zoneData, + widgets: reorderedWidgets, + }, + }; + } else { + const updatedWidgets = zoneData.widgets.map((widget) => + widget.id === active.id ? { ...widget, panel: newPanel } : widget + ); + const widgetsInNewPanel = updatedWidgets.filter( + (w) => w.panel === newPanel + ); + const newIndex = widgetsInNewPanel.findIndex((w) => w.id === over.id); + const reorderedWidgets = arrayMove(updatedWidgets, oldIndex, newIndex); + return { + ...prev, + [selectedZone]: { + ...zoneData, + widgets: reorderedWidgets, + }, + }; + } + }); }; return ( @@ -208,11 +349,20 @@ const RealTimeVisualization = () => { header={["Overview", "Widgets", "Templates"]} defaultActive={"Widgets"} /> -
-
- {zone.map((zone, index) => ( -
{zone}
+
+ {Object.keys(zonesData).map((zone, index) => ( +
setSelectedZone(zone)} + > + {zone} +
))}
{(["top", "right", "bottom", "left"] as Side[]).map((side) => ( @@ -224,100 +374,100 @@ const RealTimeVisualization = () => { > + -
- setActiveExtraButton((prev) => ({ - ...prev, - [side]: "Disable Sorting", - })) - } + onClick={() => toggleLockPanel(side)} >
- setActiveExtraButton((prev) => ({ - ...prev, - [side]: "Hide Panel", - })) - } + onClick={() => toggleLockPanel(side)} >
- setActiveExtraButton((prev) => ({ - ...prev, - [side]: "Clean Panel", - })) - } + onClick={() => toggleLockPanel(side)} >
{ - toggleLockPanel(side); - setActiveExtraButton((prev) => ({ - ...prev, - [side]: "Lock Panel", - })); - }} + onClick={() => toggleLockPanel(side)} >
))} - - {activeSides.map((side) => ( + {zonesData[selectedZone].activeSides.map((side) => (
handleDrop(e, side)} onDragOver={(e) => e.preventDefault()} + ref={(el) => { + if (el) { + panelRefs.current[side] = el; + } else { + delete panelRefs.current[side]; + } + }} >
w.panel === side) .map((w) => w.id)} strategy={verticalListSortingStrategy} > - {widgets + {zonesData[selectedZone].widgets .filter((w) => w.panel === side) .map((widget) => ( @@ -327,7 +477,6 @@ const RealTimeVisualization = () => {
))}
-
diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts index a0c5313..a33e535 100644 --- a/frontend/src/store/store.ts +++ b/frontend/src/store/store.ts @@ -275,50 +275,6 @@ export const useDrieUIValue = create((set: any) => ({ type Side = "top" | "bottom" | "left" | "right"; -// Define Widget interface -interface Widget { - id: string; - type: any; - panel: Side; - title: string; // Ensure title is a string - fontFamily?: string; - fontSize?: string; - fontWeight?: string; -} - -// Store interface -interface WidgetStore { - draggedAsset: { type: any; id: string; title: string } | null; - widgets: Widget[]; - setDraggedAsset: ( - asset: { type: any; id: string; title: string } | null - ) => void; - addWidget: (widget: Widget) => void; - setWidgets: (widgets: Widget[]) => void; - selectedChartId: Widget | null; // Change this to store an object of type Widget - setSelectedChartId: (widget: Widget | { - "type": "line", - "id": "1741781992712-ecvq5t61y", - "title": "Widget 2", - "panel": "left", - "fontWeight": "Light", - "fontSize": "12px", - "fontFamily": "Sans-serif" -}) => void; // Update to accept a Widget object -} - -// Create the store with Zustand -export const useWidgetStore = create((set) => ({ - draggedAsset: null, - widgets: [], - setDraggedAsset: (asset) => set({ draggedAsset: asset }), - addWidget: (widget) => - set((state) => ({ widgets: [...state.widgets, widget] })), - setWidgets: (widgets) => set({ widgets }), - selectedChartId: null, - setSelectedChartId: (widget) => set({ selectedChartId: widget }), -})); - interface ThemeState { themeColor: string[]; // This should be an array of strings setThemeColor: (colors: string[]) => void; // This function will accept an array of strings @@ -328,3 +284,52 @@ export const useThemeStore = create((set) => ({ themeColor: ["#5c87df", "#EEEEFE", "#969BA7"], setThemeColor: (colors) => set({ themeColor: colors }), })); +// Define the WidgetStore interface +// Define the WidgetStore interface +interface Widget { + id: string; + type: string; // Can be chart type or "progress" + panel: "top" | "bottom" | "left" | "right"; + title: string; + fontFamily?: string; + fontSize?: string; + fontWeight?: string; + data: { + // Chart data + labels?: string[]; + datasets?: Array<{ + data: number[]; + backgroundColor: string; + borderColor: string; + borderWidth: number; + }>; + // Progress card data + stocks?: Array<{ + key: string; + value: number; + description: string; + }>; + }; +} + +interface WidgetStore { + draggedAsset: Widget | null; // The currently dragged widget asset + widgets: Widget[]; // List of all widgets + selectedChartId: Widget | null; // The currently selected chart/widget + setDraggedAsset: (asset: Widget | null) => void; // Setter for draggedAsset + addWidget: (widget: Widget) => void; // Add a new widget + setWidgets: (widgets: Widget[]) => void; // Replace the entire widgets array + setSelectedChartId: (widget: Widget | null) => void; // Set the selected chart/widget +} + +// Create the store with Zustand +export const useWidgetStore = create((set) => ({ + draggedAsset: null, + widgets: [], + selectedChartId: null, // Initialize as null, not as an array + setDraggedAsset: (asset) => set({ draggedAsset: asset }), + addWidget: (widget) => + set((state) => ({ widgets: [...state.widgets, widget] })), + setWidgets: (widgets) => set({ widgets }), + setSelectedChartId: (widget) => set({ selectedChartId: widget }), +}));