From 95ed64fc6f13488b01597ba66018330c28c06175 Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Wed, 19 Mar 2025 18:06:33 +0530 Subject: [PATCH 1/7] Your commit message --- app/package-lock.json | 19 + app/package.json | 1 + .../components/icons/ExportModuleIcons.tsx | 31 ++ .../icons/RealTimeVisulationIcons.tsx | 162 ++++++ .../layout/sidebarLeft/SideBarLeft.tsx | 22 +- .../sidebarLeft/visualization/Templates.tsx | 114 +++++ .../visualization/widgets/ChartComponent.tsx | 130 +++++ .../visualization/widgets/Widgets.tsx | 28 ++ .../visualization/widgets/Widgets2D.tsx | 138 ++++++ .../visualization/widgets/Widgets3D.tsx | 11 + .../visualization/widgets/WidgetsTemplate.tsx | 12 + .../layout/sidebarRight/SideBarRight.tsx | 7 +- .../visualization/Visualization.tsx | 28 ++ .../sidebarRight/visualization/data/Data.tsx | 138 ++++++ .../visualization/design/Design.tsx | 199 ++++++++ app/src/components/ui/ModuleToggle.tsx | 12 + .../components/ui/componets/AddButtons.tsx | 187 +++++++ .../ui/componets/DraggableWidget.tsx | 217 ++++++++ app/src/components/ui/componets/Panel.tsx | 197 ++++++++ .../ui/componets/RealTimeVisulization.tsx | 207 ++++++++ .../ui/inputs/MultiLevelDropDown.tsx | 94 ++++ .../components/ui/inputs/RegularDropDown.tsx | 82 ++++ app/src/pages/Project.tsx | 13 +- app/src/store/useModuleStore.ts | 4 +- app/src/store/usePlayButtonStore.ts | 11 + app/src/store/useTemplateStore.ts | 39 ++ app/src/store/useThemeStore.ts | 11 + app/src/store/useWidgetStore.ts | 49 ++ app/src/store/useZoneStore.ts | 41 ++ .../styles/components/_regularDropDown.scss | 52 ++ app/src/styles/layout/sidebar.scss | 403 +++++++++++++++ app/src/styles/main.scss | 2 + app/src/styles/pages/realTimeViz.scss | 464 ++++++++++++++++++ 33 files changed, 3115 insertions(+), 10 deletions(-) create mode 100644 app/src/components/icons/RealTimeVisulationIcons.tsx create mode 100644 app/src/components/layout/sidebarLeft/visualization/Templates.tsx create mode 100644 app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx create mode 100644 app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx create mode 100644 app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx create mode 100644 app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx create mode 100644 app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsTemplate.tsx create mode 100644 app/src/components/layout/sidebarRight/visualization/Visualization.tsx create mode 100644 app/src/components/layout/sidebarRight/visualization/data/Data.tsx create mode 100644 app/src/components/layout/sidebarRight/visualization/design/Design.tsx create mode 100644 app/src/components/ui/componets/AddButtons.tsx create mode 100644 app/src/components/ui/componets/DraggableWidget.tsx create mode 100644 app/src/components/ui/componets/Panel.tsx create mode 100644 app/src/components/ui/componets/RealTimeVisulization.tsx create mode 100644 app/src/components/ui/inputs/MultiLevelDropDown.tsx create mode 100644 app/src/components/ui/inputs/RegularDropDown.tsx create mode 100644 app/src/store/usePlayButtonStore.ts create mode 100644 app/src/store/useTemplateStore.ts create mode 100644 app/src/store/useThemeStore.ts create mode 100644 app/src/store/useWidgetStore.ts create mode 100644 app/src/store/useZoneStore.ts create mode 100644 app/src/styles/components/_regularDropDown.scss create mode 100644 app/src/styles/pages/realTimeViz.scss diff --git a/app/package-lock.json b/app/package-lock.json index 8dfd6e7..45a89a1 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,6 +8,7 @@ "name": "react", "version": "0.0.0", "dependencies": { + "chart.js": "^4.4.8", "path": "^0.12.7", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -1029,6 +1030,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2159,6 +2166,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chart.js": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", + "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", diff --git a/app/package.json b/app/package.json index 07751c7..abba5c4 100644 --- a/app/package.json +++ b/app/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "chart.js": "^4.4.8", "path": "^0.12.7", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/app/src/components/icons/ExportModuleIcons.tsx b/app/src/components/icons/ExportModuleIcons.tsx index e2547d4..ffa64c4 100644 --- a/app/src/components/icons/ExportModuleIcons.tsx +++ b/app/src/components/icons/ExportModuleIcons.tsx @@ -100,3 +100,34 @@ export function VisualizationIcon({ isActive }: { isActive: boolean }) { ); } + +export function CartIcon({ isActive }: { isActive: boolean }) { + return ( + + + + + + + ); +} diff --git a/app/src/components/icons/RealTimeVisulationIcons.tsx b/app/src/components/icons/RealTimeVisulationIcons.tsx new file mode 100644 index 0000000..0174eda --- /dev/null +++ b/app/src/components/icons/RealTimeVisulationIcons.tsx @@ -0,0 +1,162 @@ +export function CleanPannel() { + return ( + + + + + + + + + + + + + + + + + ); +} + +export function EyeIcon() { + return ( + + + + + ); +} + +export function LockIcon() { + return ( + + + + ); +} + +export function PlayIcon() { + return ( + + + + ); +} + +export function CommentIcon() { + return ( + + + + + + ); +} + +export function SaveTeemplateIcon() { + return ( + + + + + ); +} diff --git a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx index f6b0842..fb7496a 100644 --- a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx +++ b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx @@ -5,21 +5,31 @@ import Header from "./Header"; import useToggleStore from "../../../store/useUIToggleStore"; import Assets from "./Assets"; import useModuleStore from "../../../store/useModuleStore"; +import Widgets from "./visualization/widgets/Widgets"; +import Templates from "./visualization/Templates"; +import Search from "../../ui/inputs/Search"; const SideBarLeft: React.FC = () => { - const [activeOption, setActiveOption] = useState("Outline"); + const [activeOption, setActiveOption] = useState("Widgets"); const { toggleUI } = useToggleStore(); const { activeModule } = useModuleStore(); - // Reset activeList whenever activeModule changes + // Reset activeOption whenever activeModule changes useEffect(() => { setActiveOption("Outline"); + if (activeModule === "visualization") setActiveOption("Widgets"); }, [activeModule]); const handleToggleClick = (option: string) => { setActiveOption(option); // Update the active option }; + + const handleSearchChange = (value: string) => { + // Log the search value for now + console.log(value); + }; + return (
@@ -28,11 +38,17 @@ const SideBarLeft: React.FC = () => { {activeModule === "visualization" ? ( <> + +
+ {activeOption === "Widgets" ? : } +
+ ) : activeModule === "market" ? ( + <> ) : ( <> { + const { templates, removeTemplate } = useTemplateStore(); + const { setSelectedZone } = useSelectedZoneStore(); + + console.log('templates: ', templates); + const handleDeleteTemplate = (id: string) => { + removeTemplate(id); + }; + + const handleLoadTemplate = (template: any) => { + setSelectedZone((prev) => ({ + ...prev, + panelOrder: template.panelOrder, + activeSides: Array.from(new Set([...prev.activeSides, ...template.panelOrder])), + widgets: template.widgets, + })); + }; + + return ( +
+ {templates.map((template) => ( +
+ {template.snapshot && ( +
{/* 16:9 aspect ratio */} + {`${template.name} handleLoadTemplate(template)} + /> +
+ )} +
+
handleLoadTemplate(template)} + style={{ + cursor: 'pointer', + fontWeight: '500', + // ':hover': { + // textDecoration: 'underline' + // } + }} + > + {template.name} +
+ +
+
+ ))} + {templates.length === 0 && ( +
+ No saved templates yet. Create one in the visualization view! +
+ )} +
+ ); +}; + +export default Templates; + diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx new file mode 100644 index 0000000..39da08d --- /dev/null +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useRef, useMemo } from "react"; +import { Chart } from "chart.js/auto"; +import { useThemeStore } from "../../../../../store/useThemeStore"; + +// Define Props Interface +interface ChartComponentProps { + 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 = ({ + type, + title, + fontFamily, + fontSize, + fontWeight = "Regular", // Default to "Regular" + data: propsData, +}: ChartComponentProps) => { + const canvasRef = useRef(null); + const { themeColor } = useThemeStore(); + + // Memoize Theme Colors to Prevent Unnecessary Recalculations + const buttonActionColor = useMemo( + () => themeColor[0] || "#5c87df", + [themeColor] + ); + const buttonAbortColor = useMemo( + () => themeColor[1] || "#ffffff", + [themeColor] + ); + + // Memoize Font Weight Mapping + const chartFontWeightMap = useMemo( + () => ({ + Light: "lighter" as const, + Regular: "normal" as const, + Bold: "bold" as const, + }), + [] + ); + + // Parse and Memoize Font Size + const fontSizeValue = useMemo( + () => (fontSize ? parseInt(fontSize) : 12), + [fontSize] + ); + + // Determine and Memoize Font Weight + const fontWeightValue = useMemo( + () => chartFontWeightMap[fontWeight], + [fontWeight, chartFontWeightMap] + ); + + // Memoize Chart Font Style + const chartFontStyle = useMemo( + () => ({ + family: fontFamily || "Arial", + size: fontSizeValue, + weight: fontWeightValue, + color: "#2B3344", + }), + [fontFamily, fontSizeValue, fontWeightValue] + ); + + // Memoize Chart Data + const data = useMemo(() => propsData, [propsData]); + + // Memoize Chart Options + const options = useMemo( + () => ({ + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: title, + font: chartFontStyle, + align: "start", // Left align the title + padding: { + top: 10, // Add padding above the title + bottom: 20, // Add padding between the title and the chart + }, + }, + legend: { + display: false, + }, + }, + }), + [title, chartFontStyle] + ); + + // Initialize Chart on Component Mount + useEffect(() => { + if (!canvasRef.current) return; + + const ctx = canvasRef.current.getContext("2d"); + if (!ctx) return; + + 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 the chart when these dependencies change + + return ; +}; + +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/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx new file mode 100644 index 0000000..3fde4c8 --- /dev/null +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx @@ -0,0 +1,28 @@ +import { useState } from "react"; +import ToggleHeader from "../../../../ui/inputs/ToggleHeader"; +import Widgets2D from "./Widgets2D"; +import Widgets3D from "./Widgets3D"; +import WidgetsTemplate from "./WidgetsTemplate"; + +const Widgets = () => { + const [activeOption, setActiveOption] = useState("2D"); + + const handleToggleClick = (option: string) => { + setActiveOption(option); + }; + + return ( +
+ + {activeOption === "2D" && } + {activeOption === "3D" && } + {activeOption === "Templates" && } +
+ ); +}; + +export default Widgets; diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx new file mode 100644 index 0000000..eaa453e --- /dev/null +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx @@ -0,0 +1,138 @@ +import React from "react"; +import { useWidgetStore } from "../../../../../store/useWidgetStore"; +import { ChartType } from "chart.js/auto"; +import ChartComponent from "./ChartComponent"; + +const chartTypes: ChartType[] = [ + "bar", + "line", + "pie", + "doughnut", + "radar", + "polarArea", +]; + +const sampleData = { + labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], + datasets: [ + { + data: [65, 59, 80, 81, 56, 55, 40], + backgroundColor: "#6f42c1", + borderColor: "#ffffff", + borderWidth: 1, + }, + ], +}; + +interface WidgetProps { + type: ChartType; + index: number; + title: string; +} + +const ChartWidget: React.FC = ({ type, index, title }) => { + const { setDraggedAsset } = useWidgetStore((state) => state); + + return ( +
{ + setDraggedAsset({ + type, + id: `widget-${index + 1}`, + title, + panel: "top", + data: sampleData, + }); + }} + onDragEnd={() => setDraggedAsset(null)} + > + +
+ ); +}; + +const ProgressBarWidget = ({ + id, + title, + data, +}: { + id: string; + title: string; + data: any; +}) => { + const { setDraggedAsset } = useWidgetStore((state) => state); + + return ( +
{ + setDraggedAsset({ + type: "progress", + id, + title, + panel: "top", + data, + }); + }} + onDragEnd={() => setDraggedAsset(null)} + > +
{title}
+ {data.stocks.map((stock: any, index: number) => ( +
+ + +
{stock.key}
+
{stock.value}
+
+
{stock.description}
+
+
Icon
+
+ ))} +
+ ); +}; + +const Widgets2D = () => { + return ( +
+
+ {chartTypes.map((type, index) => { + const widgetTitle = `Widget ${index + 1}`; + return ( + + ); + })} + + +
+
+ ); +}; + +export default Widgets2D; diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx new file mode 100644 index 0000000..9a14410 --- /dev/null +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const Widgets3D = () => { + return ( +
+ Widgets3D +
+ ) +} + +export default Widgets3D diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsTemplate.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsTemplate.tsx new file mode 100644 index 0000000..14ab07e --- /dev/null +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsTemplate.tsx @@ -0,0 +1,12 @@ +import React from 'react' + +const WidgetsTemplate = () => { + return ( +
+ WidgetsTemplate + +
+ ) +} + +export default WidgetsTemplate diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index 9cac07b..db44f69 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -8,6 +8,7 @@ import { } from "../../icons/SimulationIcons"; import useToggleStore from "../../../store/useUIToggleStore"; import MachineMechanics from "./mechanics/MachineMechanics"; +import Visualization from "./visualization/Visualization"; const SideBarRight: React.FC = () => { const { activeModule } = useModuleStore(); @@ -54,13 +55,17 @@ const SideBarRight: React.FC = () => { )}
)} - {toggleUI && ( + {/* process builder */} + {toggleUI && activeModule === "builder" && (
)} + + {/* realtime visualization */} + {activeModule === "visualization" && } ); }; diff --git a/app/src/components/layout/sidebarRight/visualization/Visualization.tsx b/app/src/components/layout/sidebarRight/visualization/Visualization.tsx new file mode 100644 index 0000000..c29aef2 --- /dev/null +++ b/app/src/components/layout/sidebarRight/visualization/Visualization.tsx @@ -0,0 +1,28 @@ +import { useState } from "react"; +import Search from "../../../ui/inputs/Search"; +import ToggleHeader from "../../../ui/inputs/ToggleHeader"; +import Data from "./data/Data"; +import Design from "./design/Design"; + +const Visualization = () => { + const [activeOption, setActiveOption] = useState("Data"); + + const handleToggleClick = (option: string) => { + setActiveOption(option); // Update the active option + }; + + return ( +
+ +
+ {activeOption === "Data" ? : } +
+
+ ); +}; + +export default Visualization; diff --git a/app/src/components/layout/sidebarRight/visualization/data/Data.tsx b/app/src/components/layout/sidebarRight/visualization/data/Data.tsx new file mode 100644 index 0000000..d1122bb --- /dev/null +++ b/app/src/components/layout/sidebarRight/visualization/data/Data.tsx @@ -0,0 +1,138 @@ +import React, { useEffect, useState } from "react"; +import { useWidgetStore } from "../../../../../store/useWidgetStore"; +import { RemoveIcon } from "../../../../icons/ExportCommonIcons"; +import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; +import MultiLevelDropDown from "../../../../ui/inputs/MultiLevelDropDown"; + +interface Child { + id: number; + easing: string; +} +const DATA_STRUCTURE = { + furnace: { + coolingRate: "coolingRate", + furnaceTemperature: "furnaceTemperature", + heatingRate: "heatingRate", + machineId: "machineId", + powerConsumption: "powerConsumption", + status: "status", + timestamp: "timestamp", + vacuumLevel: "vacuumLevel", + }, + testDevice: { + abrasiveLevel: { + data1: "Data 1", + data2: "Data 2", + data3: "Data 3", + }, + airPressure: "airPressure", + machineId: "machineId", + powerConsumption: "powerConsumption", + status: "status", + temperature: { + data1: "Data 1", + data2: "Data 2", + data3: "Data 3", + }, + timestamp: { + data1: "Data 1", + data2: "Data 2", + data3: "Data 3", + }, + }, +}; + +interface Group { + id: number; + easing: string; + children: Child[]; +} + +const Data = () => { + const { selectedChartId } = useWidgetStore(); + + // State to store groups for all widgets (using Widget.id as keys) + const [chartDataGroups, setChartDataGroups] = useState< + Record + >({}); + + useEffect(() => { + // 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]); + + // Handle adding a new child to the group + const handleAddClick = (groupId: number) => { + setChartDataGroups((prevGroups) => { + const currentGroups = prevGroups[selectedChartId.id] || []; + const group = currentGroups.find((g) => g.id === groupId); + + if (group && group.children.length < 7) { + const newChild = { id: Date.now(), easing: "Linear" }; + return { + ...prevGroups, + [selectedChartId.id]: currentGroups.map((g) => + g.id === groupId ? { ...g, children: [...g.children, newChild] } : g + ), + }; + } + return prevGroups; + }); + }; + + // Remove a child from a group + const removeChild = (groupId: number, childId: number) => { + setChartDataGroups((currentGroups) => { + const currentChartData = currentGroups[selectedChartId.id] || []; + + return { + ...currentGroups, + [selectedChartId.id]: currentChartData.map((group) => + group.id === groupId + ? { + ...group, + children: group.children.filter( + (child) => child.id !== childId + ), + } + : group + ), + }; + }); + }; + + return ( +
+ {selectedChartId?.title && ( +
{selectedChartId?.title}
+ )} + {/* */} +
+ i +

+ + By adding templates and widgets, you create a customizable and + dynamic environment. + +

+
+
+ ); +}; + +export default Data; diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx new file mode 100644 index 0000000..93332dd --- /dev/null +++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx @@ -0,0 +1,199 @@ +import React, { useState } from "react"; +import { useWidgetStore } from "../../../../../store/useWidgetStore"; +import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent"; +import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; + +// Define Props Interface +interface Widget { + id: string; + 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"); + const [selectedFont, setSelectedFont] = useState("drop down"); + const [selectedSize, setSelectedSize] = useState("drop down"); + const [selectedWeight, setSelectedWeight] = useState("drop down"); + const [elementColor, setElementColor] = useState("#6f42c1"); // Default color for elements + const [showColorPicker, setShowColorPicker] = useState(false); // Manage visibility of the color picker + + // Zustand Store Hooks + const { selectedChartId, setSelectedChartId, widgets, setWidgets } = + useWidgetStore(); + + // Handle Widget Updates + const handleUpdateWidget = (updatedProperties: Partial) => { + if (!selectedChartId) return; + + // Update the selectedChartId + const updatedChartId = { + ...selectedChartId, + ...updatedProperties, + }; + setSelectedChartId(updatedChartId); + + // Update the widgets array + const updatedWidgets = widgets.map((widget) => + widget.id === selectedChartId.id + ? { ...widget, ...updatedProperties } + : widget + ); + setWidgets(updatedWidgets); + }; + + // Default Chart Data + const defaultChartData = { + labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"], + datasets: [ + { + data: [65, 59, 80, 81, 56, 55, 40], + backgroundColor: elementColor, // Default background color + borderColor: "#ffffff", // Default border color + borderWidth: 1, + }, + ], + }; + + return ( +
+ {/* Title of the Selected Widget */} +
+ {selectedChartId?.title || "Widget 1"} +
+ + {/* Chart Component */} +
+ {selectedChartId && ( + + )} +
+ + {/* Options Container */} +
+ {/* Name Dropdown */} +
+ Name + { + setSelectedName(value); + handleUpdateWidget({ title: value }); + }} + /> +
+ + {/* Element Dropdown */} +
+ Element + { + setSelectedElement(value); + handleUpdateWidget({ type: value }); + }} + /> +
+ + {/* Font Family Dropdown */} +
+ Font Family + { + setSelectedFont(value); + handleUpdateWidget({ fontFamily: value }); + }} + /> +
+ + {/* Size Dropdown */} +
+ Size + { + setSelectedSize(value); + handleUpdateWidget({ fontSize: value }); + }} + /> +
+ + {/* Weight Dropdown */} +
+ Weight + { + setSelectedWeight(value); + handleUpdateWidget({ fontWeight: value }); + }} + /> +
+ + {/* Element Color Picker */} +
+
setShowColorPicker((prev) => !prev)} + > + Element Color +
{" "} + {/* Change icon based on the visibility */} +
+ + {/* Show color picker only when 'showColorPicker' is true */} + {showColorPicker && ( +
+ { + setElementColor(e.target.value); + handleUpdateWidget({ + data: { + labels: selectedChartId?.data?.labels || [], + datasets: [ + { + ...selectedChartId?.data?.datasets[0], + backgroundColor: e.target.value, // Update the element color + }, + ], + }, + }); + }} + /> + {/* Display the selected color value */} + {elementColor} +
+ )} +
+
+
+ ); +}; + +export default Design; diff --git a/app/src/components/ui/ModuleToggle.tsx b/app/src/components/ui/ModuleToggle.tsx index cbe64e3..bea9c43 100644 --- a/app/src/components/ui/ModuleToggle.tsx +++ b/app/src/components/ui/ModuleToggle.tsx @@ -2,6 +2,7 @@ import React from "react"; import useModuleStore from "../../store/useModuleStore"; import { BuilderIcon, + CartIcon, SimulationIcon, VisualizationIcon, } from "../icons/ExportModuleIcons"; @@ -40,6 +41,17 @@ const ModuleToggle: React.FC = () => {
Visualization
+
setActiveModule("market")} + > +
+ +
+
Market Place
+
); }; diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/components/ui/componets/AddButtons.tsx new file mode 100644 index 0000000..31110a6 --- /dev/null +++ b/app/src/components/ui/componets/AddButtons.tsx @@ -0,0 +1,187 @@ +import React from "react"; +import { CleanPannel, EyeIcon, LockIcon } from "../../icons/RealTimeVisulationIcons"; + +// Define the type for `Side` +type Side = "top" | "bottom" | "left" | "right"; + +// Define the type for the props passed to the Buttons component +interface ButtonsProps { + selectedZone: { + zoneName: string; // Add zoneName property + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: { + id: string; + type: string; + title: string; + panel: Side; + data: any; + }[]; + }; + setSelectedZone: React.Dispatch< + React.SetStateAction<{ + zoneName: string; // Ensure zoneName is included in the state type + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: { + id: string; + type: string; + title: string; + panel: Side; + data: any; + }[]; + }> + >; +} + +const AddButtons: React.FC = ({ + selectedZone, + setSelectedZone, +}) => { + // Function to toggle lock/unlock a panel + const toggleLockPanel = (side: Side) => { + const newLockedPanels = selectedZone.lockedPanels.includes(side) + ? selectedZone.lockedPanels.filter((panel) => panel !== side) + : [...selectedZone.lockedPanels, side]; + + const updatedZone = { + ...selectedZone, + lockedPanels: newLockedPanels, + }; + + // Update the selectedZone state + setSelectedZone(updatedZone); + }; + + // Function to toggle visibility of a panel + const toggleVisibility = (side: Side) => { + const newActiveSides = selectedZone.activeSides.includes(side) + ? selectedZone.activeSides.filter((s) => s !== side) + : [...selectedZone.activeSides, side]; + + const updatedZone = { + ...selectedZone, + activeSides: newActiveSides, + panelOrder: newActiveSides, + }; + + // Update the selectedZone state + setSelectedZone(updatedZone); + }; + + // Function to clean all widgets from a panel + const cleanPanel = (side: Side) => { + const cleanedWidgets = selectedZone.widgets.filter( + (widget) => widget.panel !== side + ); + + const updatedZone = { + ...selectedZone, + widgets: cleanedWidgets, + }; + + // Update the selectedZone state + setSelectedZone(updatedZone); + }; + + // Function to handle "+" button click + const handlePlusButtonClick = (side: Side) => { + if (selectedZone.activeSides.includes(side)) { + // If the panel is already active, remove all widgets and close the panel + const cleanedWidgets = selectedZone.widgets.filter( + (widget) => widget.panel !== side + ); + const newActiveSides = selectedZone.activeSides.filter((s) => s !== side); + + const updatedZone = { + ...selectedZone, + widgets: cleanedWidgets, + activeSides: newActiveSides, + panelOrder: newActiveSides, + }; + + // Update the selectedZone state + setSelectedZone(updatedZone); + } else { + // If the panel is not active, activate it + const newActiveSides = [...selectedZone.activeSides, side]; + + const updatedZone = { + ...selectedZone, + activeSides: newActiveSides, + panelOrder: newActiveSides, + }; + + // Update the selectedZone state + setSelectedZone(updatedZone); + } + }; + + return ( +
+ {(["top", "right", "bottom", "left"] as Side[]).map((side) => ( +
+ {/* "+" Button */} + + + {/* Extra Buttons */} +
+ {/* Hide Panel */} +
toggleVisibility(side)} + > + +
+ + {/* Clean Panel */} +
cleanPanel(side)} + > + +
+ + {/* Lock/Unlock Panel */} +
toggleLockPanel(side)} + > + +
+
+
+ ))} +
+ ); +}; + +export default AddButtons; diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx new file mode 100644 index 0000000..342f31f --- /dev/null +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -0,0 +1,217 @@ +import { useMemo, useState } from "react"; +import ChartComponent from "../../layout/sidebarLeft/visualization/widgets/ChartComponent"; +import { useWidgetStore } from "../../../store/useWidgetStore"; + +export const DraggableWidget = ({ widget }: { widget: any }) => { + const { selectedChartId, setSelectedChartId } = useWidgetStore(); + + // State for managing the popup visibility and customization options + const [isPopupOpen, setIsPopupOpen] = useState(false); + const [customizationOptions, setCustomizationOptions] = useState({ + templateBackground: "#ffffff", + cardBackground: "#ffffff", + cardOpacity: 1, + cardBlur: 0, + font: "Arial", + margin: 0, + radius: 5, + shadow: "Low", + }); + + // Handle pointer down to select the chart + const handlePointerDown = () => { + if (selectedChartId?.id !== widget.id) { + setSelectedChartId(widget); + } + }; + + // Handle double-click to open the popup + const handleDoubleClick = () => { + setIsPopupOpen(true); + }; + + // Close the popup + const handleClosePopup = () => { + setIsPopupOpen(false); + }; + + // Save the changes made in the popup + const handleSaveChanges = () => { + // Here you can save the customizationOptions to your store or API + console.log("Saved Customization Options:", customizationOptions); + setIsPopupOpen(false); + }; + + // Compute dynamic card styles based on customizationOptions + const cardStyle = useMemo(() => { + const shadowLevels = { + Low: "0px 2px 4px rgba(0, 0, 0, 0.1)", + Medium: "0px 4px 8px rgba(0, 0, 0, 0.2)", + High: "0px 8px 16px rgba(0, 0, 0, 0.3)", + }; + + return { + backgroundColor: customizationOptions.cardBackground, + opacity: customizationOptions.cardOpacity, + filter: `blur(${customizationOptions.cardBlur}px)`, + fontFamily: customizationOptions.font, + margin: `${customizationOptions.margin}px`, + borderRadius: `${customizationOptions.radius}px`, + // boxShadow: shadowLevels[customizationOptions.shadow], + }; + }, [customizationOptions]); + + return ( + <> +
+ {widget.type === "progress" ? ( + // + <> + ) : ( + + )} +
+ + {/* Popup for Customizing Template Theme */} + {isPopupOpen && ( +
+
+

Customize Template Theme

+
+ + + setCustomizationOptions((prev) => ({ + ...prev, + templateBackground: e.target.value, + })) + } + /> +
+
+ + + setCustomizationOptions((prev) => ({ + ...prev, + cardBackground: e.target.value, + })) + } + /> +
+
+ + + setCustomizationOptions((prev) => ({ + ...prev, + cardOpacity: parseFloat(e.target.value), + })) + } + /> +
+
+ + + setCustomizationOptions((prev) => ({ + ...prev, + cardBlur: parseInt(e.target.value), + })) + } + /> +
+
+ + +
+
+ + + setCustomizationOptions((prev) => ({ + ...prev, + margin: parseInt(e.target.value), + })) + } + /> +
+
+ + + setCustomizationOptions((prev) => ({ + ...prev, + radius: parseInt(e.target.value), + })) + } + /> +
+
+ + +
+
+ + +
+
+
+ )} + + ); +}; diff --git a/app/src/components/ui/componets/Panel.tsx b/app/src/components/ui/componets/Panel.tsx new file mode 100644 index 0000000..502114b --- /dev/null +++ b/app/src/components/ui/componets/Panel.tsx @@ -0,0 +1,197 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { useWidgetStore } from "../../../store/useWidgetStore"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { DraggableWidget } from "./DraggableWidget"; + +type Side = "top" | "bottom" | "left" | "right"; + +interface Widget { + id: string; + type: string; + title: string; + panel: Side; + data: any; +} + +interface PanelProps { + selectedZone: { + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; + }; + setSelectedZone: React.Dispatch< + React.SetStateAction<{ + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; + }> + >; +} + +const generateUniqueId = () => + `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + +const Panel: React.FC = ({ selectedZone, setSelectedZone }) => { + const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({}); + const [panelDimensions, setPanelDimensions] = useState<{ + [side in Side]?: { width: number; height: number }; + }>({}); + + const getPanelStyle = useMemo( + () => (side: Side) => { + const currentIndex = selectedZone.panelOrder.indexOf(side); + const previousPanels = 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"); + + 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 {}; + } + }, + [selectedZone.panelOrder] + ); + + const handleDrop = (e: React.DragEvent, panel: Side) => { + e.preventDefault(); + const { draggedAsset } = useWidgetStore.getState(); + + if (draggedAsset) { + if (selectedZone.lockedPanels.includes(panel)) return; + + const currentWidgetsInPanel = selectedZone.widgets.filter( + (w) => w.panel === panel + ).length; + + const dimensions = panelDimensions[panel]; + const CHART_WIDTH = 200; + const CHART_HEIGHT = 200; + let maxCharts = 0; + + if (dimensions) { + if (panel === "top" || panel === "bottom") { + maxCharts = Math.floor(dimensions.width / CHART_WIDTH); + } else { + maxCharts = Math.floor(dimensions.height / CHART_HEIGHT); + } + } else { + maxCharts = panel === "top" || panel === "bottom" ? 5 : 3; + } + + if (currentWidgetsInPanel >= maxCharts) { + return; + } + + const updatedZone = { + ...selectedZone, + widgets: [ + ...selectedZone.widgets, + { + ...draggedAsset, + id: generateUniqueId(), + panel, + }, + ], + }; + + setSelectedZone(updatedZone); + } + }; + + useEffect(() => { + const observers: ResizeObserver[] = []; + const currentPanelRefs = panelRefs.current; + + 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 () => { + observers.forEach((observer) => observer.disconnect()); + }; + }, [selectedZone.activeSides]); + + const { isPlaying } = usePlayButtonStore(); + + return ( + <> + {selectedZone.activeSides.map((side) => ( +
handleDrop(e, side)} + onDragOver={(e) => e.preventDefault()} + ref={(el) => { + if (el) { + panelRefs.current[side] = el; + } else { + delete panelRefs.current[side]; + } + }} + > +
+ {selectedZone.widgets + .filter((w) => w.panel === side) + .map((widget) => ( + + ))} +
+
+ ))} + + ); +}; + +export default Panel; +// only load selected template + diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx new file mode 100644 index 0000000..5bb2687 --- /dev/null +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -0,0 +1,207 @@ +import React, { useEffect, useState, useRef } from "react"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import Panel from "./Panel"; +import AddButtons from "./AddButtons"; +import { + CommentIcon, + PlayIcon, + SaveTeemplateIcon, +} from "../../icons/RealTimeVisulationIcons"; +import useTemplateStore from "../../../store/useTemplateStore"; +import { useSelectedZoneStore } from "../../../store/useZoneStore"; + +type Side = "top" | "bottom" | "left" | "right"; + +interface Widget { + id: string; + type: string; + title: string; + panel: Side; + data: any; +} + +const RealTimeVisulization: React.FC = () => { + const containerRef = useRef(null); + const [zonesData, setZonesData] = useState<{ + [key: string]: { + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; + }; + }>({ + "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: [], + }, + }); + + const { isPlaying, setIsPlaying } = usePlayButtonStore(); + const { addTemplate } = useTemplateStore(); + const { selectedZone, setSelectedZone } = useSelectedZoneStore(); + + const generateUniqueId = (): string => + `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + + const captureVisualization = async (): Promise => { + const container = containerRef.current; + if (!container) return null; + + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (!ctx) return null; + + const rect = container.getBoundingClientRect(); + canvas.width = rect.width; + canvas.height = rect.height; + + // Draw background + ctx.fillStyle = getComputedStyle(container).backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Capture all canvas elements + const canvases = container.querySelectorAll('canvas'); + canvases.forEach(childCanvas => { + const childRect = childCanvas.getBoundingClientRect(); + const x = childRect.left - rect.left; + const y = childRect.top - rect.top; + ctx.drawImage(childCanvas, x, y, childRect.width, childRect.height); + }); + + // Capture SVG elements + const svgs = container.querySelectorAll('svg'); + for (const svg of Array.from(svgs)) { + const svgString = new XMLSerializer().serializeToString(svg); + const svgBlob = new Blob([svgString], { type: 'image/svg+xml' }); + const url = URL.createObjectURL(svgBlob); + + try { + const img = await new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = reject; + image.src = url; + }); + + const svgRect = svg.getBoundingClientRect(); + ctx.drawImage( + img, + svgRect.left - rect.left, + svgRect.top - rect.top, + svgRect.width, + svgRect.height + ); + } finally { + URL.revokeObjectURL(url); + } + } + + return canvas.toDataURL('image/png'); + }; + + const handleSaveTemplate = async () => { + const snapshot = await captureVisualization(); + const template = { + id: generateUniqueId(), + name: `Template ${Date.now()}`, + panelOrder: selectedZone.panelOrder, + widgets: selectedZone.widgets, + snapshot, + }; + addTemplate(template); + + }; + + useEffect(() => { + setZonesData((prev) => ({ + ...prev, + [selectedZone.zoneName]: selectedZone, + })); + }, [selectedZone]); + + return ( +
+
+
+ +
+
+ +
+
setIsPlaying(!isPlaying)} + > + +
+
+ +
+ {Object.keys(zonesData).map((zoneName, index) => ( +
{ + setSelectedZone({ + zoneName, + ...zonesData[zoneName], + }); + }} + > + {zoneName} +
+ ))} +
+ + {!isPlaying && ( + + )} + + +
+ ); +}; + +export default RealTimeVisulization; \ No newline at end of file diff --git a/app/src/components/ui/inputs/MultiLevelDropDown.tsx b/app/src/components/ui/inputs/MultiLevelDropDown.tsx new file mode 100644 index 0000000..215bbd8 --- /dev/null +++ b/app/src/components/ui/inputs/MultiLevelDropDown.tsx @@ -0,0 +1,94 @@ +import React, { useState } from "react"; + + +// Dropdown Item Component +const DropdownItem = ({ + label, + href, + onClick, +}: { + label: string; + href?: string; + onClick?: () => void; +}) => ( + { + e.preventDefault(); + onClick?.(); + }} + > + {label} + +); + +// Nested Dropdown Component +const NestedDropdown = ({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) => { + const [open, setOpen] = useState(false); + + return ( +
+ {/* Dropdown Trigger */} +
setOpen(!open)} // Toggle submenu on click + > + {label} {open ? "▼" : "▶"} +
+ + {/* Submenu */} + {open &&
{children}
} +
+ ); +}; + +// Recursive Function to Render Nested Data +const renderNestedData = (data: Record) => { + return Object.entries(data).map(([key, value]) => { + if (typeof value === "object" && !Array.isArray(value)) { + // If the value is an object, render it as a nested dropdown + return ( + + {renderNestedData(value)} + + ); + } else if (Array.isArray(value)) { + // If the value is an array, render each item as a dropdown item + return value.map((item, index) => ( + + )); + } else { + // If the value is a simple string, render it as a dropdown item + return ; + } + }); +}; + +// Main Multi-Level Dropdown Component +const MultiLevelDropdown = ({ data }: { data: Record }) => { + const [open, setOpen] = useState(false); + + return ( +
+ {/* Dropdown Trigger Button */} + + + {/* Dropdown Menu */} + {open &&
{renderNestedData(data)}
} +
+ ); +}; + +export default MultiLevelDropdown; \ No newline at end of file diff --git a/app/src/components/ui/inputs/RegularDropDown.tsx b/app/src/components/ui/inputs/RegularDropDown.tsx new file mode 100644 index 0000000..318913f --- /dev/null +++ b/app/src/components/ui/inputs/RegularDropDown.tsx @@ -0,0 +1,82 @@ +import React, { useState, useEffect, useRef } from "react"; + +interface DropdownProps { + header: string; + options: string[]; + onSelect: (option: string) => void; +} + +const RegularDropDown: React.FC = ({ + header, + options, + onSelect, +}) => { + const [isOpen, setIsOpen] = useState(false); + const [selectedOption, setSelectedOption] = useState(null); + const dropdownRef = useRef(null); // Create a ref for the dropdown container + + // Reset selectedOption when the dropdown closes + useEffect(() => { + if (!isOpen) { + setSelectedOption(null); // Clear local state when the dropdown closes + } + }, [isOpen]); + + // Reset selectedOption when the header prop changes + useEffect(() => { + setSelectedOption(null); // Ensure the dropdown reflects the updated header + }, [header]); + + // Close dropdown if clicked outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); // Close the dropdown if clicked outside + } + }; + + document.addEventListener("click", handleClickOutside); + + // Cleanup the event listener on component unmount + return () => { + document.removeEventListener("click", handleClickOutside); + }; + }, []); + + const toggleDropdown = () => { + setIsOpen((prev) => !prev); + }; + + const handleOptionClick = (option: string) => { + setSelectedOption(option); + onSelect(option); // Call the onSelect function passed from the parent + setIsOpen(false); // Close the dropdown after selection + }; + + return ( +
+ {/* Dropdown Header */} +
+
{selectedOption || header}
+
+
+ + {/* Dropdown Options */} + {isOpen && ( +
+ {options.map((option, index) => ( +
handleOptionClick(option)} + > + {option} +
+ ))} +
+ )} +
+ ); +}; + +export default RegularDropDown; diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 6e0b37b..29f8c6c 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -1,14 +1,19 @@ -import React from 'react'; -import ModuleToggle from '../components/ui/ModuleToggle'; -import SideBarLeft from '../components/layout/sidebarLeft/SideBarLeft'; -import SideBarRight from '../components/layout/sidebarRight/SideBarRight'; +import React from "react"; +import ModuleToggle from "../components/ui/ModuleToggle"; +import SideBarLeft from "../components/layout/sidebarLeft/SideBarLeft"; +import SideBarRight from "../components/layout/sidebarRight/SideBarRight"; +import useModuleStore from "../store/useModuleStore"; +import RealTimeVisulization from "../components/ui/componets/RealTimeVisulization"; const Project: React.FC = () => { + const { activeModule } = useModuleStore(); + return (
+ {activeModule === "visualization" && }
); }; diff --git a/app/src/store/useModuleStore.ts b/app/src/store/useModuleStore.ts index 5281c0b..6d26a8e 100644 --- a/app/src/store/useModuleStore.ts +++ b/app/src/store/useModuleStore.ts @@ -1,5 +1,5 @@ // store/useModuleStore.ts -import { create } from 'zustand'; +import { create } from "zustand"; interface ModuleStore { activeModule: string; @@ -7,7 +7,7 @@ interface ModuleStore { } const useModuleStore = create((set) => ({ - activeModule: 'builder', // Initial state + activeModule: "visualization", // Initial state setActiveModule: (module) => set({ activeModule: module }), // Update state })); diff --git a/app/src/store/usePlayButtonStore.ts b/app/src/store/usePlayButtonStore.ts new file mode 100644 index 0000000..5b2913c --- /dev/null +++ b/app/src/store/usePlayButtonStore.ts @@ -0,0 +1,11 @@ +import { create } from "zustand"; + +type PlayButtonStore = { + isPlaying: boolean; // Updated state name to reflect the play/pause status more clearly + setIsPlaying: (value: boolean) => void; // Updated setter function name for clarity +}; + +export const usePlayButtonStore = create((set) => ({ + isPlaying: false, // Default state for play/pause + setIsPlaying: (value) => set({ isPlaying: value }), // Update isPlaying state +})); diff --git a/app/src/store/useTemplateStore.ts b/app/src/store/useTemplateStore.ts new file mode 100644 index 0000000..a91c14a --- /dev/null +++ b/app/src/store/useTemplateStore.ts @@ -0,0 +1,39 @@ +import { create } from "zustand"; + +type Side = "top" | "bottom" | "left" | "right"; + +interface Widget { + id: string; + type: string; + title: string; + panel: Side; + data: any; +} + +interface Template { + id: string; + name: string; + panelOrder: Side[]; + widgets: Widget[]; + snapshot?: string; // Add an optional image property (base64) +} + +interface TemplateStore { + templates: Template[]; + addTemplate: (template: Template) => void; + removeTemplate: (id: string) => void; +} + +export const useTemplateStore = create((set) => ({ + templates: [], + addTemplate: (template) => + set((state) => ({ + templates: [...state.templates, template], + })), + removeTemplate: (id) => + set((state) => ({ + templates: state.templates.filter((t) => t.id !== id), + })), +})); + +export default useTemplateStore; diff --git a/app/src/store/useThemeStore.ts b/app/src/store/useThemeStore.ts new file mode 100644 index 0000000..944a77f --- /dev/null +++ b/app/src/store/useThemeStore.ts @@ -0,0 +1,11 @@ +import { create } from "zustand"; + +interface ThemeState { + themeColor: string[]; // This should be an array of strings + setThemeColor: (colors: string[]) => void; // This function will accept an array of strings +} + +export const useThemeStore = create((set) => ({ + themeColor: ["#5c87df", "#EEEEFE", "#969BA7"], + setThemeColor: (colors) => set({ themeColor: colors }), +})); diff --git a/app/src/store/useWidgetStore.ts b/app/src/store/useWidgetStore.ts new file mode 100644 index 0000000..013581b --- /dev/null +++ b/app/src/store/useWidgetStore.ts @@ -0,0 +1,49 @@ +import { create } from "zustand"; + +export 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: any; + 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 }), +})); diff --git a/app/src/store/useZoneStore.ts b/app/src/store/useZoneStore.ts new file mode 100644 index 0000000..5d7c4a4 --- /dev/null +++ b/app/src/store/useZoneStore.ts @@ -0,0 +1,41 @@ +import { create } from "zustand"; + +type Side = "top" | "bottom" | "left" | "right"; + +interface Widget { + id: string; + type: string; + title: string; + panel: Side; + data: any; +} + +interface SelectedZoneState { + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; +} + +interface SelectedZoneStore { + selectedZone: SelectedZoneState; + setSelectedZone: (zone: Partial | ((prev: SelectedZoneState) => SelectedZoneState)) => void; +} + +export const useSelectedZoneStore = create((set) => ({ + selectedZone: { + zoneName: "Manufacturing unit", + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + setSelectedZone: (zone) => + set((state) => ({ + selectedZone: + typeof zone === "function" + ? zone(state.selectedZone) // Handle functional updates + : { ...state.selectedZone, ...zone }, // Handle partial updates + })), +})); \ No newline at end of file diff --git a/app/src/styles/components/_regularDropDown.scss b/app/src/styles/components/_regularDropDown.scss new file mode 100644 index 0000000..085a5cd --- /dev/null +++ b/app/src/styles/components/_regularDropDown.scss @@ -0,0 +1,52 @@ +@use '../abstracts/variables.scss' as *; + + +.regularDropdown-container { + width: 104px; + height: 22px; + border: 1px solid var(--text-color); // Ensure $border-color is defined + border-radius: 6px; + position: relative; + cursor: pointer; + padding: 0 6px; + + .dropdown-header { + height: 100%; + display: flex; + justify-content: space-between; + cursor: pointer; + // padding: 5px; + border: 1px solid var(--primary-color); + border-radius: 6px; + background-color: var(--background-color); + + // .icon { + // padding-right: 6px; + // } + } + + .dropdown-options { + position: absolute; // Ensure dropdown options position correctly + width: 100%; // Ensure options width matches the header + background-color: var(--primary-color); // Optional: Background color + border: 1px solid #ccc; // Optional: Border styling + border-radius: 4px; // Optional: Border radius + z-index: 10; // Ensure dropdown appears above other elements + max-height: 200px; // Optional: Limit height + overflow-y: auto; // Optional: Enable scrolling if content exceeds height + left: 0; + top: 104%; + .option { + padding: 5px; + cursor: pointer; + + &:hover { + background-color: var(--primary-color); // Optional: Hover effect + } + } + } + + .icon { + height: auto; + } +} \ No newline at end of file diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index 49ce93b..622cade 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -9,25 +9,31 @@ background-color: var(--background-color); border-radius: #{$border-radius-extra-large}; box-shadow: #{$box-shadow-medium}; + .header-container { @include flex-space-between; padding: 10px; width: 100%; + .header-content { @include flex-center; width: calc(100% - 34px); + .logo-container { @include flex-center; } + .header-title { padding: 0 8px; width: 100%; max-width: calc(100% - 32px); + .input-value { color: var(--accent-color); } } } + .toggle-sidebar-ui-button { @include flex-center; cursor: pointer; @@ -36,31 +42,108 @@ min-height: 32px; min-width: 32px; border-radius: #{$border-radius-small}; + &:hover { background-color: var(--background-color-secondary); } } + .active { background-color: var(--background-color-secondary); outline: 1px solid var(--accent-color); outline-offset: -1px; } } + .sidebar-left-container { min-height: 50vh; padding-bottom: 12px; position: relative; display: flex; flex-direction: column; + .sidebar-left-content-container { border-bottom: 1px solid var(--border-color); // flex: 1; height: calc(100% - 36px); position: relative; overflow: auto; + + .widget-left-sideBar { + min-height: 50vh; + max-height: 60vh; + + .widget2D { + .chart-container { + display: flex; + flex-direction: column; + gap: 8px; + padding-right: 6px; + flex-wrap: nowrap; + overflow: auto; + + .chart { + min-height: 170px; + background: var(--background-primary, #FCFDFD); + border: 1.23px solid var(--Grays-Gray-5, #E5E5EA); + box-shadow: 0px 4.91px 4.91px 0px #0000001C; + border-radius: $border-radius-medium; + padding: 12px 6px; + } + + .progressBar { + height: auto !important; + padding: 12px 10px 41px 10px; + display: flex; + flex-direction: column; + gap: 16px; + + .header { + display: flex; + justify-content: start; + 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: var(--accent-color); + font-size: 16px; + } + } + + .stock-description { + font-size: 12px; + } + } + + } + } + + } + } + } + + + } + .outline-container { height: 100%; + .outline-content-container { position: relative; height: 100%; @@ -79,15 +162,18 @@ background-color: var(--background-color); border-radius: #{$border-radius-extra-large}; box-shadow: #{$box-shadow-medium}; + .header-container { @include flex-space-between; padding: 10px; width: 100%; gap: 12px; height: 52px; + .options-container { @include flex-center; gap: 8px; + .share-button { padding: 4px 12px; color: var(--primary-color); @@ -96,18 +182,22 @@ border-radius: #{$border-radius-medium}; cursor: pointer; } + .app-docker-button { @include flex-center; } } + .split { height: 20px; width: 2px; background: var(--background-color-secondary); } + .users-container { width: 100%; @include flex-space-between; + .user-profile { @include flex-center; height: 26px; @@ -118,8 +208,10 @@ font-weight: var(--font-weight-bold); color: white; } + .guest-users-container { display: flex; + .other-guest { @include flex-center; height: 26px; @@ -134,11 +226,14 @@ outline-offset: -1px; } } + .user-profile-container { display: flex; + .user-organnization { height: 100%; max-width: 52px; + img { height: 100%; width: 100%; @@ -148,9 +243,11 @@ } } } + .sidebar-actions-container { position: absolute; left: -40px; + .sidebar-action-list { margin-bottom: 12px; @include flex-center; @@ -160,16 +257,19 @@ background: var(--primary-color); box-shadow: #{$box-shadow-medium}; } + .active { background: var(--accent-color); } } + .sidebar-right-container { min-height: 50vh; padding-bottom: 12px; position: relative; display: flex; flex-direction: column; + .sidebar-right-content-container { border-bottom: 1px solid var(--border-color); // flex: 1; @@ -178,12 +278,179 @@ overflow: auto; } } + + .visualization-right-sideBar { + min-height: 50vh; + max-height: 60vh; + + .sidebar-left-content-container { + .dataSideBar { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 12px; + padding: 10px 12px; + + .sideBarHeader { + color: #5c87df; + border-bottom: 1px solid var(--border-color); + padding-bottom: 6px; + } + + .selectedMain-container { + display: flex; + flex-direction: column; + gap: 6px; + border-bottom: 1px solid var(--border-color); + padding-bottom: 10px; + + .selectedMain { + display: flex; + align-items: center; + gap: 6px; + + main { + width: 35%; + white-space: nowrap; + /* Prevent wrapping */ + } + + .icon { + padding: 0; + cursor: pointer; + } + + button { + background-color: transparent; + box-shadow: none; + color: #5273eb; + padding: 6px; + font-size: 18px; + } + + .bulletPoint { + color: #5273eb; + font-size: 16px; + } + + .regularDropdown-container { + width: 100%; + } + + &:first-child { + gap: 4px; + } + } + } + + .child { + width: 100%; + gap: 6px; + } + + .infoBox { + display: flex; + align-items: flex-start; + gap: 6px; + color: #444; + border-radius: 6px; + font-size: 14px; + + .infoIcon { + padding: 0px 7px; + border-radius: 50%; + border: 1px solid gray; + } + + p { + margin: 0; + } + } + } + + .design { + width: 100%; + display: flex; + flex-direction: column; + gap: 15px; + padding: 0; + font-size: 14px; + color: #4a4a4a; + + .selectedWidget { + padding: 6px 12px; + border-top: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); + } + } + + .reviewChart { + width: 100%; + height: 150px; + background: #f0f0f0; + border-radius: 8px; + } + + .optionsContainer { + display: flex; + flex-direction: column; + gap: 10px; + padding: 0 12px; + + .option { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + + .regularDropdown-container { + width: 160px; + } + + &:last-child { + flex-direction: column; + + .header { + width: 100%; + display: flex; + justify-content: space-between; + } + + .colorDisplayer { + width: 100%; + display: flex; + justify-content: start; + align-items: center; + + input[type="color"] { + border: none; + outline: none; + background: none; + width: 24px; + height: 26px; + border-radius: 3.2px; + + } + + } + } + + span { + min-width: 100px; + } + } + } + + } + } } .machine-mechanics-container { .header { @include flex-space-between; padding: 6px 12px; + .add-button { @include flex-center; padding: 2px 4px; @@ -191,19 +458,23 @@ color: var(--primary-color); border-radius: #{$border-radius-small}; cursor: pointer; + path { stroke: var(--primary-color); } } } + .lists-main-container { margin: 2px 8px; width: calc(100% - 16px); background: var(--background-color-secondary); border-radius: #{$border-radius-small}; + .list-container { min-height: 120px; padding: 4px; + .list-item { @include flex-space-between; padding: 4px 12px; @@ -211,15 +482,19 @@ margin: 2px 0; border-radius: #{$border-radius-small}; } + .active { background: var(--accent-color); + .value { color: var(--primary-color); } + path { stroke: var(--primary-color); } } + .remove-button { @include flex-center; height: 12px; @@ -227,18 +502,22 @@ cursor: pointer; } } + .resize-icon { @include flex-center; padding: 4px; cursor: grab; + &:active { cursor: grabbing; } } } + .selected-properties-container { padding: 12px; } + .footer { @include flex-center; justify-content: flex-start; @@ -247,3 +526,127 @@ font-size: var(--font-size-tiny); } } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/* Base styles */ +.multi-level-dropdown { + position: relative; + display: inline-block; + text-align: left; + + .dropdown-button { + background-color: #3b82f6; /* Blue background */ + color: white; + padding: 0.5rem 1rem; + font-size: 0.875rem; + border: none; + border-radius: 0.375rem; + cursor: pointer; + display: flex; + align-items: center; + transition: background-color 0.2s ease; + + &:hover { + background-color: #2563eb; /* Darker blue on hover */ + } + + .icon { + margin-left: 0.5rem; + } + } + + .dropdown-menu { + position: absolute; + top: calc(100% + 0.5rem); /* Add spacing below the button */ + left: 0; + width: 12rem; + background-color: white; + border: 1px solid #e5e7eb; /* Light gray border */ + border-radius: 0.375rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */ + z-index: 10; + } +} + +/* Dropdown Item */ +.dropdown-item { + display: block; + padding: 0.5rem 1rem; + font-size: 0.875rem; + color: #4b5563; /* Gray text */ + text-decoration: none; + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: #f3f4f6; /* Light gray background on hover */ + } +} + +/* Nested Dropdown */ +.nested-dropdown { + position: relative; + + .dropdown-trigger { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 1rem; + font-size: 0.875rem; + color: #4b5563; /* Gray text */ + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: #f3f4f6; /* Light gray background on hover */ + } + + .icon { + margin-left: 0.5rem; + } + } + + .submenu { + position: absolute; + top: 0; + left: 100%; /* Position submenu to the right */ + width: 12rem; + background-color: white; + border: 1px solid #e5e7eb; /* Light gray border */ + border-radius: 0.375rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */ + z-index: 20; + } +} \ No newline at end of file diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index 2c9b7df..68688f6 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -18,9 +18,11 @@ @use 'components/moduleToggle'; @use 'components/templates'; @use 'components/tools'; +@use 'components/regularDropDown'; // layout @use 'layout/sidebar'; // pages @use 'pages/home'; +@use 'pages/realTimeViz'; \ No newline at end of file diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss new file mode 100644 index 0000000..6bf0ae3 --- /dev/null +++ b/app/src/styles/pages/realTimeViz.scss @@ -0,0 +1,464 @@ +@use '../abstracts/variables.scss' as *; + +// Main Container +.realTime-viz { + background-color: var(--background-color); + border-radius: 20px; + box-shadow: 0px 4px 8px rgba(60, 60, 67, 0.1019607843); + width: 55%; + height: 600px; + position: absolute; + top: 50%; + left: 48%; + transform: translate(-50%, -50%); + + .icon { + display: flex; + align-items: center; + position: relative; + + &:first-child { + &::after { + content: ""; + display: block; + width: 1px; + height: 18px; + background-color: #000; + margin-top: 10px; + position: absolute; + right: -10px; + top: -4px; + } + } + } + + .icons-container { + .icon { + &:first-child { + &::after { + display: none; + } + } + } + + + + } + + .realTimeViz-tools { + position: fixed; + bottom: -150px; + left: 50%; + transform: translateX(-50%); + box-shadow: 0px 4px 8px 0px rgba(60, 60, 67, 0.1); + background: $background-color; + display: flex; + gap: 12px; + border-radius: 12px; + z-index: 1000; + transition: bottom 0.3s ease; + padding: 8px; + + .icons-container { + padding-left: 8px; + display: flex; + align-items: center; + gap: 10px; + } + } + + + .zoon-wrapper { + display: flex; + background-color: #E0DFFF80; + position: absolute; + bottom: 10px; + left: 50%; + transform: translate(-50%, 0); + gap: 6px; + padding: 4px; + border-radius: 8px; + max-width: 80%; + overflow: auto; + + &::-webkit-scrollbar { + display: none; + } + + .zone { + width: auto; + background-color: #FCFDFD; + border-radius: 6px; + padding: 4px 8px; + white-space: nowrap; + font-size: $small; + + } + + .active { + background-color: var(--accent-color); + color: var(--background-color); + // color: #FCFDFD !important; + } + } + + .zoon-wrapper.bottom { + bottom: 210px; + } + + @media (max-width: 1024px) { + width: 80%; // Increase width to take more space on smaller screens + height: 500px; // Reduce height to fit smaller screens + left: 50%; // Center horizontally + + .main-container { + margin: 0 15px; // Reduce margin for better spacing + } + + .zoon-wrapper { + bottom: 5px; // Adjust position for smaller screens + + &.bottom { + bottom: 150px; // Adjust for bottom placement + } + } + } + + @media (max-width: 768px) { + width: 90%; // Take even more width on very small screens + height: 400px; // Further reduce height + top: 45%; // Adjust vertical position slightly upward + + .panel { + + &.top-panel, + &.bottom-panel { + .panel-content { + flex-direction: column; // Stack panels vertically on small screens + + .chart-container { + width: 100%; // Make charts full width + height: 150px; // Reduce chart height + } + } + } + } + } + + @media (max-width: 480px) { + width: 95%; // Take almost full width on very small devices + height: 350px; // Further reduce height + top: 40%; // Move slightly higher for better visibility + + .side-button-container { + flex-direction: row !important; // Force buttons into a row + gap: 4px; // Reduce spacing between buttons + + &.top, + &.bottom { + left: 50%; // Center horizontally + transform: translateX(-50%); + } + } + } + + .content-container { + display: flex; + height: 100vh; + transition: all 0.3s ease; + } + + .main-container { + position: relative; + flex: 1; + height: 600px; + background-color: rgb(235, 235, 235); + margin: 0 30px; + transition: height 0.3s ease, margin 0.3s ease; + + + + .zoon-wrapper { + display: flex; + background-color: rgba(224, 223, 255, 0.5); + position: absolute; + bottom: 10px; + left: 50%; + transform: translate(-50%, 0); + gap: 6px; + padding: 4px; + border-radius: 8px; + max-width: 80%; + overflow: auto; + transition: transform 0.3s ease; + + &::-webkit-scrollbar { + display: none; + } + + .zone { + width: auto; + background-color: $background-color; + border-radius: 6px; + padding: 4px 8px; + white-space: nowrap; + cursor: pointer; + transition: background-color 0.3s ease; + + &.active { + background-color: var(--primary-color); + color: var(--accent-color); + } + } + + &.bottom { + bottom: 210px; + } + } + } + + .panel { + position: absolute; + background: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + border-radius: 6px; + overflow: visible !important; + + .panel-content { + position: relative; + height: 100%; + padding: 10px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 10px; + + &::-webkit-scrollbar { + display: none; + } + + .chart-container { + width: 100%; + height: 200px; + max-height: 100%; + border: 1px dotted #a9a9a9; + border-radius: 8px; + box-shadow: 0px 2px 6px 0px rgba(60, 60, 67, 0.1); + padding: 6px 0; + background-color: white; + } + + .close-btn { + position: absolute; + top: 5px; + right: 5px; + background: none; + border: none; + cursor: pointer; + color: var(--primary-color); + } + } + + &.top-panel, + &.bottom-panel { + left: 0; + right: 0; + + .panel-content { + display: flex; + flex-direction: row; + + .chart-container { + height: 100%; + width: 230px; + } + } + } + + &.top-panel { + top: 0; + } + + &.bottom-panel { + bottom: 0; + } + + &.left-panel { + left: 0; + top: 0; + bottom: 0; + + .chart-container { + width: 100%; + height: 200px; + } + } + + &.right-panel { + right: 0; + top: 0; + bottom: 0; + } + } +} + +// Side Buttons +.side-button-container { + position: absolute; + display: flex; + background-color: $background-color; + padding: 5px; + border-radius: 8px; + transition: transform 0.3s ease; + + .extra-Bs { + display: flex; + align-items: center; + gap: 12px; + + .icon { + display: flex; + } + + &:hover { + cursor: pointer; + } + } + + .side-button { + cursor: pointer; + transition: background-color 0.3s ease; + width: 18px; + height: 18px; + display: flex; + justify-content: center; + // align-items: center; + background-color: var(--accent-color); + border: none; + color: var(--background-color); + border-radius: 4px; + + &:hover { + // background-color: var(--primary-color); + // color: var(--accent-color); + } + } + + &.top { + top: -30px; + left: 50%; + transform: translateX(-50%); + flex-direction: row; + gap: 6px; + } + + &.right { + right: -30px; + top: 50%; + transform: translateY(-50%); + flex-direction: column; + gap: 6px; + } + + &.bottom { + bottom: -30px; + left: 50%; + transform: translateX(-50%); + flex-direction: row; + gap: 6px; + } + + &.left { + left: -30px; + top: 50%; + transform: translateY(-50%); + flex-direction: column; + gap: 6px; + } +} + +.right.side-button-container { + + .extra-Bs { + flex-direction: column; + } + +} + +.left.side-button-container { + + .extra-Bs { + flex-direction: column; + } +} + +// Theme Container +.theme-container { + width: 250px; + padding: 12px; + box-shadow: 1px -3px 4px 0px rgba(0, 0, 0, 0.11); + border-radius: 8px; + background-color: white; + position: absolute; + top: 20px; + right: -100%; + transform: translate(-0%, 0); + + h2 { + font-size: 12px; + margin-bottom: 8px; + color: #2B3344; + } + + .theme-preset-wrapper { + display: flex; + gap: 5px; + flex-wrap: wrap; + + .theme-preset { + display: flex; + gap: 2px; + margin-bottom: 10px; + border: 1px solid $border-color; + padding: 5px 10px; + border-radius: 4px; + transition: border 0.3s ease; + + &.active { + border: 1px solid var(--primary-color); + + &::after { + content: ''; + position: absolute; + top: 1px; + left: 1px; + width: 10px; + height: 10px; + background-color: var(--primary-color); + border-radius: 50%; + } + } + } + } + + .custom-color { + display: flex; + justify-content: space-between; + + .color-displayer { + display: flex; + gap: 5px; + align-items: center; + border: 1px solid var(--accent-color); + border-radius: 4px; + padding: 0 5px; + + input { + border: none; + outline: none; + border-radius: 50%; + } + } + } +} \ No newline at end of file -- 2.49.1 From 299b3ed3810122aa63b57108a285cafbac0fd83b Mon Sep 17 00:00:00 2001 From: Vishnu Date: Wed, 19 Mar 2025 18:15:52 +0530 Subject: [PATCH 2/7] tools updated --- app/src/components/icons/ExportToolsIcons.tsx | 105 ++++++-- app/src/components/ui/Tools.tsx | 228 +++++++++++++++++- app/src/styles/components/tools.scss | 145 +++++++++++ 3 files changed, 460 insertions(+), 18 deletions(-) diff --git a/app/src/components/icons/ExportToolsIcons.tsx b/app/src/components/icons/ExportToolsIcons.tsx index afffc88..2b7134a 100644 --- a/app/src/components/icons/ExportToolsIcons.tsx +++ b/app/src/components/icons/ExportToolsIcons.tsx @@ -10,11 +10,15 @@ export function ZoneIcon({ isActive }: { isActive: boolean }) { @@ -37,27 +41,27 @@ export function AsileIcon({ isActive }: { isActive: boolean }) { > ); @@ -82,35 +86,37 @@ export function FloorIcon({ isActive }: { isActive: boolean }) { /> ); @@ -366,7 +372,10 @@ export function PillerIcon({ isActive }: { isActive: boolean }) { fill="none" xmlns="http://www.w3.org/2000/svg" > - + @@ -563,3 +572,67 @@ export function CursorIcon({ isActive }: { isActive: boolean }) { ); } + +export function PlayIcon({ isActive }: { isActive: boolean }) { + return ( + + + + ); +} + +export function PenIcon({ isActive }: { isActive: boolean }) { + return ( + + + + ); +} + +export function SaveTemplateIcon({ isActive }: { isActive: boolean }) { + return ( + + + + + ); +} diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index d1a7a14..291a8f2 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -1,8 +1,232 @@ -import React from 'react'; +import React, { useEffect, useRef, useState } from "react"; +import { + AsileIcon, + CommentIcon, + CursorIcon, + FloorIcon, + FreeMoveIcon, + PenIcon, + PlayIcon, + SaveTemplateIcon, + WallIcon, + ZoneIcon, +} from "../icons/ExportToolsIcons"; +import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons"; +import useModuleStore from "../../store/useModuleStore"; const Tools: React.FC = () => { + const [activeTool, setActiveTool] = useState("cursor"); + const [activeSubTool, setActiveSubTool] = useState("cursor"); + const [toggleThreeD, setToggleThreeD] = useState(true); + + const dropdownRef = useRef(null); + const [openDrop, setOpenDrop] = useState(false); + + const { activeModule } = useModuleStore(); + + // Reset activeTool whenever activeModule changes + useEffect(() => { + setActiveTool(activeSubTool); + setActiveSubTool(activeSubTool); + }, [activeModule]); + + useEffect(() => { + const handleOutsideClick = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setOpenDrop(false); // Close the dropdown + } + }; + + document.addEventListener("mousedown", handleOutsideClick); + + return () => { + document.removeEventListener("mousedown", handleOutsideClick); + }; + }, []); + return ( -
Tools
+
+
+
+ {activeSubTool == "cursor" && ( +
{ + setActiveTool("cursor"); + }} + > + +
+ )} + {activeSubTool == "free-hand" && ( +
{ + setActiveTool("free-hand"); + }} + > + +
+ )} + {activeModule !== "visualization" && ( +
{ + setOpenDrop(!openDrop); + console.log(openDrop); + }} + > + + {openDrop && ( +
+
{ + setOpenDrop(false); + setActiveTool("cursor"); + setActiveSubTool("cursor"); + }} + > +
+ {activeSubTool === "cursor" && } +
+ +
Cursor
+
+
{ + setOpenDrop(false); + setActiveTool("free-hand"); + setActiveSubTool("free-hand"); + }} + > +
+ {activeSubTool === "free-hand" && } +
+ +
Free Hand
+
+
+ )} +
+ )} +
+
+ {!toggleThreeD && activeModule === "builder" && ( + <> +
+
+
{ + setActiveTool("draw-wall"); + }} + > + +
+
{ + setActiveTool("draw-zone"); + }} + > + +
+
{ + setActiveTool("draw-aisle"); + }} + > + +
+
{ + setActiveTool("draw-floor"); + }} + > + +
+
+ + )} + {activeModule === "simulation" && ( + <> +
+
+
{ + setActiveTool("pen"); + }} + > + +
+
+ + )} + {activeModule === "visualization" && ( + <> +
+
+
+ +
+
+ + )} +
+
+
{ + setActiveTool("comment"); + }} + > + +
+
{ + setActiveTool("play"); + }} + > + +
+
+
+
{ + setToggleThreeD(!toggleThreeD); + }} + > +
+ 2d +
+
+ 3d +
+
+
); }; diff --git a/app/src/styles/components/tools.scss b/app/src/styles/components/tools.scss index e69de29..e4149c0 100644 --- a/app/src/styles/components/tools.scss +++ b/app/src/styles/components/tools.scss @@ -0,0 +1,145 @@ +@use "../abstracts/variables" as *; +@use "../abstracts/mixins" as *; + +.tools-container { + @include flex-center; + position: fixed; + bottom: 32px; + left: 50%; + transform: translate(-50%, 0); + padding: 8px; + gap: 10px; + box-shadow: #{$box-shadow-medium}; + border-radius: #{$border-radius-large}; + width: fit-content; + transition: width 0.2s; + .split { + height: 20px; + width: 2px; + border-radius: 2px; + background: var(--highlight-accent-color); + } + .draw-tools, + .general-options, + .activeDropicon { + @include flex-center; + gap: 8px; + interpolate-size: allow-keywords; + width: 0; + opacity: 0; + animation: expandWidth 0.2s ease-in-out forwards; + .tool-button { + @include flex-center; + height: 28px; + width: 28px; + cursor: pointer; + border-radius: #{$border-radius-small}; + &:hover { + background: color-mix( + in srgb, + var(--highlight-accent-color) 60%, + transparent + ); + } + } + .active { + background-color: var(--accent-color); + &:hover { + background-color: var(--accent-color); + } + } + } + .activeDropicon { + gap: 2px; + .drop-down-option-button { + @include flex-center; + height: 28px; + cursor: pointer; + border-radius: #{$border-radius-small}; + position: relative; + &:hover { + background: color-mix( + in srgb, + var(--highlight-accent-color) 60%, + transparent + ); + } + .drop-down-container { + position: absolute; + bottom: 40px; + left: 0; + box-shadow: #{$box-shadow-medium}; + padding: 8px; + border-radius: #{$border-radius-large}; + .option-list { + margin: 4px 0; + display: flex; + align-items: center; + white-space: nowrap; + border-radius: #{$border-radius-medium}; + gap: 6px; + padding: 4px; + &:hover { + background-color: var(--highlight-accent-color); + color: var(--accent-color); + path { + stroke: var(--accent-color); + } + } + .active-option { + height: 12px; + width: 12px; + @include flex-center; + } + .option { + color: inherit; + } + } + } + } + } + .toggle-threed-button { + @include flex-center; + padding: 3px; + border-radius: #{$border-radius-small}; + background-color: var(--highlight-accent-color); + gap: 2px; + position: relative; + .toggle-option { + font-size: var(--font-size-small); + padding: 2px; + z-index: 1; + transition: all 0.2s; + } + &::after { + content: ""; + position: absolute; + background-color: var(--accent-color); + left: 3px; + top: 3px; + height: 18px; + width: 18px; + border-radius: #{$border-radius-small}; + transition: all 0.2s; + } + .active { + color: var(--highlight-accent-color); + } + } + .toggled { + &::after { + left: 24px; + } + } +} + +@keyframes expandWidth { + from { + width: 0; + opacity: 0; + } + to { + width: fit-content; + opacity: 1; + } +} -- 2.49.1 From b9fa37cef87e7ff632d3b9d280716d33890ba7d6 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 20 Mar 2025 09:36:10 +0530 Subject: [PATCH 3/7] visualization functions seperated --- app/src/assets/orgTemp.png | Bin 0 -> 25838 bytes .../components/icons/ExportCommonIcons.tsx | 14 +- .../components/icons/ExportModuleIcons.tsx | 4 +- app/src/components/icons/ExportToolsIcons.tsx | 4 +- .../icons/RealTimeVisulationIcons.tsx | 92 +-- app/src/components/icons/SimulationIcons.tsx | 8 +- .../components/layout/sidebarRight/Header.tsx | 3 +- app/src/components/ui/Tools.tsx | 13 +- .../components/ui/componets/AddButtons.tsx | 77 +- .../ui/componets/RealTimeVisulization.tsx | 122 +-- app/src/functions/generateUniqueId.ts | 2 + app/src/hooks/temp.md | 0 app/src/modules/builder/temp.md | 0 app/src/modules/simulation/temp.md | 0 .../visualization/captureVisualization.ts | 55 ++ .../visualization/handleSaveTemplate.ts | 36 + app/src/pages/Project.tsx | 2 + app/src/services/temp.md | 0 app/src/store/useTemplateStore.ts | 12 +- app/src/styles/layout/sidebar.scss | 57 +- app/src/styles/pages/realTimeViz.scss | 764 ++++++++---------- 21 files changed, 564 insertions(+), 701 deletions(-) create mode 100644 app/src/assets/orgTemp.png create mode 100644 app/src/functions/generateUniqueId.ts create mode 100644 app/src/hooks/temp.md create mode 100644 app/src/modules/builder/temp.md create mode 100644 app/src/modules/simulation/temp.md create mode 100644 app/src/modules/visualization/captureVisualization.ts create mode 100644 app/src/modules/visualization/handleSaveTemplate.ts create mode 100644 app/src/services/temp.md diff --git a/app/src/assets/orgTemp.png b/app/src/assets/orgTemp.png new file mode 100644 index 0000000000000000000000000000000000000000..e3865d6ad2d2fb5d4439f26ce4d20199e5e9ba69 GIT binary patch literal 25838 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 literal 0 HcmV?d00001 diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index 53c5b9a..e64af49 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -378,8 +378,8 @@ export function UndoIcon() { xmlns="http://www.w3.org/2000/svg" > @@ -397,8 +397,8 @@ export function RedoIcon() { xmlns="http://www.w3.org/2000/svg" > @@ -436,7 +436,7 @@ export function RemoveIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - + ); } @@ -458,8 +458,8 @@ export function InfoIcon() { d="M6.00006 10.3175C8.45219 10.3175 10.4401 8.32963 10.4401 5.8775C10.4401 3.42536 8.45219 1.4375 6.00006 1.4375C3.54792 1.4375 1.56006 3.42536 1.56006 5.8775C1.56006 8.32963 3.54792 10.3175 6.00006 10.3175Z" stroke="var(--text-color)" stroke-width="0.72" - stroke-linecap="round" - stroke-linejoin="round" + strokeLinecap="round" + strokeLinejoin="round" /> ); diff --git a/app/src/components/icons/ExportModuleIcons.tsx b/app/src/components/icons/ExportModuleIcons.tsx index ffa64c4..fb2f566 100644 --- a/app/src/components/icons/ExportModuleIcons.tsx +++ b/app/src/components/icons/ExportModuleIcons.tsx @@ -113,7 +113,7 @@ export function CartIcon({ isActive }: { isActive: boolean }) { ); diff --git a/app/src/components/icons/ExportToolsIcons.tsx b/app/src/components/icons/ExportToolsIcons.tsx index 2b7134a..d463c0a 100644 --- a/app/src/components/icons/ExportToolsIcons.tsx +++ b/app/src/components/icons/ExportToolsIcons.tsx @@ -605,7 +605,7 @@ export function PenIcon({ isActive }: { isActive: boolean }) { stroke={ isActive ? "var(--highlight-accent-color)" : "var(--text-color)" } - stroke-linecap="round" + strokeLinecap="round" /> ); @@ -631,7 +631,7 @@ export function SaveTemplateIcon({ isActive }: { isActive: boolean }) { stroke={ isActive ? "var(--highlight-accent-color)" : "var(--text-color)" } - stroke-linecap="round" + strokeLinecap="round" /> ); diff --git a/app/src/components/icons/RealTimeVisulationIcons.tsx b/app/src/components/icons/RealTimeVisulationIcons.tsx index 0174eda..7c1a8d0 100644 --- a/app/src/components/icons/RealTimeVisulationIcons.tsx +++ b/app/src/components/icons/RealTimeVisulationIcons.tsx @@ -7,40 +7,40 @@ export function CleanPannel() { fill="none" xmlns="http://www.w3.org/2000/svg" > - - + + - + @@ -98,65 +98,3 @@ export function LockIcon() { ); } - -export function PlayIcon() { - return ( - - - - ); -} - -export function CommentIcon() { - return ( - - - - - - ); -} - -export function SaveTeemplateIcon() { - return ( - - - - - ); -} diff --git a/app/src/components/icons/SimulationIcons.tsx b/app/src/components/icons/SimulationIcons.tsx index 1dc82a7..3ce3ebe 100644 --- a/app/src/components/icons/SimulationIcons.tsx +++ b/app/src/components/icons/SimulationIcons.tsx @@ -10,14 +10,14 @@ export function AnalysisIcon({ isActive }: { isActive: boolean }) { ); diff --git a/app/src/components/layout/sidebarRight/Header.tsx b/app/src/components/layout/sidebarRight/Header.tsx index e9ebaa8..b80fd53 100644 --- a/app/src/components/layout/sidebarRight/Header.tsx +++ b/app/src/components/layout/sidebarRight/Header.tsx @@ -1,5 +1,6 @@ import React from "react"; import { AppDockIcon } from "../../icons/HeaderIcons"; +import orgImg from "../../../assets/orgTemp.png" const Header: React.FC = () => { const guestUsers = [ @@ -38,7 +39,7 @@ const Header: React.FC = () => { V
- +
diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 291a8f2..a182e1d 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -13,6 +13,10 @@ import { } from "../icons/ExportToolsIcons"; import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons"; import useModuleStore from "../../store/useModuleStore"; +import { handleSaveTemplate } from "../../modules/visualization/handleSaveTemplate"; +import { usePlayButtonStore } from "../../store/usePlayButtonStore"; +import useTemplateStore from "../../store/useTemplateStore"; +import { useSelectedZoneStore } from "../../store/useZoneStore"; const Tools: React.FC = () => { const [activeTool, setActiveTool] = useState("cursor"); @@ -23,6 +27,9 @@ const Tools: React.FC = () => { const [openDrop, setOpenDrop] = useState(false); const { activeModule } = useModuleStore(); + const { isPlaying, setIsPlaying } = usePlayButtonStore(); + const { addTemplate } = useTemplateStore(); + const { selectedZone } = useSelectedZoneStore(); // Reset activeTool whenever activeModule changes useEffect(() => { @@ -187,7 +194,10 @@ const Tools: React.FC = () => { <>
-
+
handleSaveTemplate({ addTemplate, selectedZone })} + >
@@ -207,6 +217,7 @@ const Tools: React.FC = () => { className={`tool-button ${activeTool === "play" ? "active" : ""}`} onClick={() => { setActiveTool("play"); + setIsPlaying(!isPlaying); }} > diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/components/ui/componets/AddButtons.tsx index 31110a6..0a810f7 100644 --- a/app/src/components/ui/componets/AddButtons.tsx +++ b/app/src/components/ui/componets/AddButtons.tsx @@ -1,5 +1,9 @@ import React from "react"; -import { CleanPannel, EyeIcon, LockIcon } from "../../icons/RealTimeVisulationIcons"; +import { + CleanPannel, + EyeIcon, + LockIcon, +} from "../../icons/RealTimeVisulationIcons"; // Define the type for `Side` type Side = "top" | "bottom" | "left" | "right"; @@ -137,47 +141,42 @@ const AddButtons: React.FC = ({ {/* Extra Buttons */} -
- {/* Hide Panel */} -
toggleVisibility(side)} - > - -
+ {selectedZone.activeSides.includes(side) && ( +
+ {/* Hide Panel */} +
toggleVisibility(side)} + > + +
- {/* Clean Panel */} -
cleanPanel(side)} - > - -
+ {/* Clean Panel */} +
cleanPanel(side)} + > + +
- {/* Lock/Unlock Panel */} -
toggleLockPanel(side)} - > - + {/* Lock/Unlock Panel */} +
toggleLockPanel(side)} + > + +
-
+ )}
))}
diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 5bb2687..fb2d653 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -2,12 +2,6 @@ import React, { useEffect, useState, useRef } from "react"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import Panel from "./Panel"; import AddButtons from "./AddButtons"; -import { - CommentIcon, - PlayIcon, - SaveTeemplateIcon, -} from "../../icons/RealTimeVisulationIcons"; -import useTemplateStore from "../../../store/useTemplateStore"; import { useSelectedZoneStore } from "../../../store/useZoneStore"; type Side = "top" | "bottom" | "left" | "right"; @@ -62,82 +56,9 @@ const RealTimeVisulization: React.FC = () => { }, }); - const { isPlaying, setIsPlaying } = usePlayButtonStore(); - const { addTemplate } = useTemplateStore(); + const { isPlaying } = usePlayButtonStore(); + const { selectedZone, setSelectedZone } = useSelectedZoneStore(); - - const generateUniqueId = (): string => - `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - - const captureVisualization = async (): Promise => { - const container = containerRef.current; - if (!container) return null; - - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - if (!ctx) return null; - - const rect = container.getBoundingClientRect(); - canvas.width = rect.width; - canvas.height = rect.height; - - // Draw background - ctx.fillStyle = getComputedStyle(container).backgroundColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - // Capture all canvas elements - const canvases = container.querySelectorAll('canvas'); - canvases.forEach(childCanvas => { - const childRect = childCanvas.getBoundingClientRect(); - const x = childRect.left - rect.left; - const y = childRect.top - rect.top; - ctx.drawImage(childCanvas, x, y, childRect.width, childRect.height); - }); - - // Capture SVG elements - const svgs = container.querySelectorAll('svg'); - for (const svg of Array.from(svgs)) { - const svgString = new XMLSerializer().serializeToString(svg); - const svgBlob = new Blob([svgString], { type: 'image/svg+xml' }); - const url = URL.createObjectURL(svgBlob); - - try { - const img = await new Promise((resolve, reject) => { - const image = new Image(); - image.onload = () => resolve(image); - image.onerror = reject; - image.src = url; - }); - - const svgRect = svg.getBoundingClientRect(); - ctx.drawImage( - img, - svgRect.left - rect.left, - svgRect.top - rect.top, - svgRect.width, - svgRect.height - ); - } finally { - URL.revokeObjectURL(url); - } - } - - return canvas.toDataURL('image/png'); - }; - - const handleSaveTemplate = async () => { - const snapshot = await captureVisualization(); - const template = { - id: generateUniqueId(), - name: `Template ${Date.now()}`, - panelOrder: selectedZone.panelOrder, - widgets: selectedZone.widgets, - snapshot, - }; - addTemplate(template); - - }; - useEffect(() => { setZonesData((prev) => ({ ...prev, @@ -148,38 +69,25 @@ const RealTimeVisulization: React.FC = () => { return (
-
-
- -
-
- -
-
setIsPlaying(!isPlaying)} - > - -
-
- -
+
{Object.keys(zonesData).map((zoneName, index) => (
{ setSelectedZone({ zoneName, @@ -204,4 +112,4 @@ const RealTimeVisulization: React.FC = () => { ); }; -export default RealTimeVisulization; \ No newline at end of file +export default RealTimeVisulization; diff --git a/app/src/functions/generateUniqueId.ts b/app/src/functions/generateUniqueId.ts new file mode 100644 index 0000000..a315524 --- /dev/null +++ b/app/src/functions/generateUniqueId.ts @@ -0,0 +1,2 @@ +export const generateUniqueId = (): string => + `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; \ No newline at end of file diff --git a/app/src/hooks/temp.md b/app/src/hooks/temp.md new file mode 100644 index 0000000..e69de29 diff --git a/app/src/modules/builder/temp.md b/app/src/modules/builder/temp.md new file mode 100644 index 0000000..e69de29 diff --git a/app/src/modules/simulation/temp.md b/app/src/modules/simulation/temp.md new file mode 100644 index 0000000..e69de29 diff --git a/app/src/modules/visualization/captureVisualization.ts b/app/src/modules/visualization/captureVisualization.ts new file mode 100644 index 0000000..01d67a0 --- /dev/null +++ b/app/src/modules/visualization/captureVisualization.ts @@ -0,0 +1,55 @@ +export const captureVisualization = async (): Promise => { + const container = document.getElementById("real-time-vis-canvas"); + if (!container) return null; + + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + if (!ctx) return null; + + const rect = container.getBoundingClientRect(); + canvas.width = rect.width; + canvas.height = rect.height; + + // Draw background + ctx.fillStyle = getComputedStyle(container).backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Capture all canvas elements + const canvases = container.querySelectorAll("canvas"); + canvases.forEach((childCanvas) => { + const childRect = childCanvas.getBoundingClientRect(); + const x = childRect.left - rect.left; + const y = childRect.top - rect.top; + ctx.drawImage(childCanvas, x, y, childRect.width, childRect.height); + }); + + // Capture SVG elements + const svgs = container.querySelectorAll("svg"); + for (const svg of Array.from(svgs)) { + const svgString = new XMLSerializer().serializeToString(svg); + const svgBlob = new Blob([svgString], { type: "image/svg+xml" }); + const url = URL.createObjectURL(svgBlob); + + try { + const img = await new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = reject; + image.src = url; + }); + + const svgRect = svg.getBoundingClientRect(); + ctx.drawImage( + img, + svgRect.left - rect.left, + svgRect.top - rect.top, + svgRect.width, + svgRect.height + ); + } finally { + URL.revokeObjectURL(url); + } + } + + return canvas.toDataURL("image/png"); +}; diff --git a/app/src/modules/visualization/handleSaveTemplate.ts b/app/src/modules/visualization/handleSaveTemplate.ts new file mode 100644 index 0000000..290395a --- /dev/null +++ b/app/src/modules/visualization/handleSaveTemplate.ts @@ -0,0 +1,36 @@ +import { Template } from "../../store/useTemplateStore"; +import { captureVisualization } from "./captureVisualization"; + +type HandleSaveTemplateProps = { + addTemplate: (template: Template) => void; + selectedZone: { + panelOrder: string[]; // Adjust the type based on actual data structure + widgets: any[]; // Replace `any` with the actual widget type + }; +}; + +// Generate a unique ID (placeholder function) +const generateUniqueId = (): string => { + return Math.random().toString(36).substring(2, 15); +}; + +// Refactored function +export const handleSaveTemplate = async ({ + addTemplate, + selectedZone, +}: HandleSaveTemplateProps): Promise => { + try { + const snapshot = await captureVisualization(); + const template: Template = { + id: generateUniqueId(), + name: `Template ${Date.now()}`, + panelOrder: selectedZone.panelOrder, + widgets: selectedZone.widgets, + snapshot, + }; + console.log('template: ', template); + addTemplate(template); + } catch (error) { + console.error('Failed to save template:', error); + } +}; diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 29f8c6c..16c0461 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -4,6 +4,7 @@ import SideBarLeft from "../components/layout/sidebarLeft/SideBarLeft"; import SideBarRight from "../components/layout/sidebarRight/SideBarRight"; import useModuleStore from "../store/useModuleStore"; import RealTimeVisulization from "../components/ui/componets/RealTimeVisulization"; +import Tools from "../components/ui/Tools"; const Project: React.FC = () => { const { activeModule } = useModuleStore(); @@ -14,6 +15,7 @@ const Project: React.FC = () => { {activeModule === "visualization" && } +
); }; diff --git a/app/src/services/temp.md b/app/src/services/temp.md new file mode 100644 index 0000000..e69de29 diff --git a/app/src/store/useTemplateStore.ts b/app/src/store/useTemplateStore.ts index a91c14a..f8f3711 100644 --- a/app/src/store/useTemplateStore.ts +++ b/app/src/store/useTemplateStore.ts @@ -1,21 +1,21 @@ import { create } from "zustand"; -type Side = "top" | "bottom" | "left" | "right"; +// type Side = "top" | "bottom" | "left" | "right"; -interface Widget { +export interface Widget { id: string; type: string; title: string; - panel: Side; + panel: string; data: any; } -interface Template { +export interface Template { id: string; name: string; - panelOrder: Side[]; + panelOrder: string[]; widgets: Widget[]; - snapshot?: string; // Add an optional image property (base64) + snapshot?: string | null; // Add an optional image property (base64) } interface TemplateStore { diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index 622cade..0daeca3 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -84,9 +84,9 @@ .chart { min-height: 170px; - background: var(--background-primary, #FCFDFD); - border: 1.23px solid var(--Grays-Gray-5, #E5E5EA); - box-shadow: 0px 4.91px 4.91px 0px #0000001C; + background: var(--background-primary, #fcfdfd); + border: 1.23px solid var(--Grays-Gray-5, #e5e5ea); + box-shadow: 0px 4.91px 4.91px 0px #0000001c; border-radius: $border-radius-medium; padding: 12px 6px; } @@ -107,7 +107,7 @@ .stock { padding: 13px 5px; - background-color: #E0DFFF80; + background-color: #e0dfff80; border-radius: 6.33px; display: flex; justify-content: space-between; @@ -129,16 +129,11 @@ font-size: 12px; } } - } } - } } } - - - } .outline-container { @@ -230,14 +225,18 @@ .user-profile-container { display: flex; - .user-organnization { + .user-organization { height: 100%; max-width: 52px; + border-radius: 20px; + overflow: hidden; + margin-left: 2px; img { height: 100%; width: 100%; object-fit: cover; + vertical-align: top; } } } @@ -430,9 +429,7 @@ width: 24px; height: 26px; border-radius: 3.2px; - } - } } @@ -441,7 +438,6 @@ } } } - } } } @@ -527,39 +523,6 @@ } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /* Base styles */ .multi-level-dropdown { position: relative; @@ -649,4 +612,4 @@ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */ z-index: 20; } -} \ No newline at end of file +} diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 6bf0ae3..828cc7b 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -1,464 +1,412 @@ -@use '../abstracts/variables.scss' as *; +@use "../abstracts/variables.scss" as *; // Main Container .realTime-viz { - background-color: var(--background-color); - border-radius: 20px; - box-shadow: 0px 4px 8px rgba(60, 60, 67, 0.1019607843); - width: 55%; - height: 600px; - position: absolute; - top: 50%; - left: 48%; - transform: translate(-50%, -50%); + background-color: var(--background-color); + border-radius: 20px; + box-shadow: 0px 4px 8px rgba(60, 60, 67, 0.1019607843); + width: calc(100% - (320px + 270px + 80px)); + height: 600px; + position: absolute; + top: 50%; + left: calc(270px + 40px); + transform: translate(0, -50%); + .icon { + display: flex; + align-items: center; + position: relative; + } + + .icons-container { .icon { - display: flex; - align-items: center; - position: relative; - - &:first-child { - &::after { - content: ""; - display: block; - width: 1px; - height: 18px; - background-color: #000; - margin-top: 10px; - position: absolute; - right: -10px; - top: -4px; - } + &:first-child { + &::after { + display: none; } + } + } + } + + .zoon-wrapper { + display: flex; + background-color: #e0dfff80; + position: absolute; + bottom: 10px; + left: 50%; + transform: translate(-50%, 0); + gap: 6px; + padding: 4px; + border-radius: 8px; + max-width: 80%; + overflow: auto; + + &::-webkit-scrollbar { + display: none; } - .icons-container { - .icon { - &:first-child { - &::after { - display: none; - } - } - } - - - + .zone { + width: auto; + background-color: #fcfdfd; + border-radius: 6px; + padding: 4px 8px; + white-space: nowrap; + font-size: $small; } - .realTimeViz-tools { - position: fixed; - bottom: -150px; - left: 50%; - transform: translateX(-50%); - box-shadow: 0px 4px 8px 0px rgba(60, 60, 67, 0.1); - background: $background-color; - display: flex; - gap: 12px; - border-radius: 12px; - z-index: 1000; - transition: bottom 0.3s ease; - padding: 8px; - - .icons-container { - padding-left: 8px; - display: flex; - align-items: center; - gap: 10px; - } + .active { + background-color: var(--accent-color); + color: var(--background-color); + // color: #FCFDFD !important; } + } + .zoon-wrapper.bottom { + bottom: 210px; + } - .zoon-wrapper { - display: flex; - background-color: #E0DFFF80; - position: absolute; - bottom: 10px; - left: 50%; - transform: translate(-50%, 0); - gap: 6px; - padding: 4px; - border-radius: 8px; - max-width: 80%; - overflow: auto; - - &::-webkit-scrollbar { - display: none; - } - - .zone { - width: auto; - background-color: #FCFDFD; - border-radius: 6px; - padding: 4px 8px; - white-space: nowrap; - font-size: $small; - - } - - .active { - background-color: var(--accent-color); - color: var(--background-color); - // color: #FCFDFD !important; - } - } - - .zoon-wrapper.bottom { - bottom: 210px; - } - - @media (max-width: 1024px) { - width: 80%; // Increase width to take more space on smaller screens - height: 500px; // Reduce height to fit smaller screens - left: 50%; // Center horizontally - - .main-container { - margin: 0 15px; // Reduce margin for better spacing - } - - .zoon-wrapper { - bottom: 5px; // Adjust position for smaller screens - - &.bottom { - bottom: 150px; // Adjust for bottom placement - } - } - } - - @media (max-width: 768px) { - width: 90%; // Take even more width on very small screens - height: 400px; // Further reduce height - top: 45%; // Adjust vertical position slightly upward - - .panel { - - &.top-panel, - &.bottom-panel { - .panel-content { - flex-direction: column; // Stack panels vertically on small screens - - .chart-container { - width: 100%; // Make charts full width - height: 150px; // Reduce chart height - } - } - } - } - } - - @media (max-width: 480px) { - width: 95%; // Take almost full width on very small devices - height: 350px; // Further reduce height - top: 40%; // Move slightly higher for better visibility - - .side-button-container { - flex-direction: row !important; // Force buttons into a row - gap: 4px; // Reduce spacing between buttons - - &.top, - &.bottom { - left: 50%; // Center horizontally - transform: translateX(-50%); - } - } - } - - .content-container { - display: flex; - height: 100vh; - transition: all 0.3s ease; - } + @media (max-width: 1024px) { + width: 80%; // Increase width to take more space on smaller screens + height: 500px; // Reduce height to fit smaller screens + left: 50%; // Center horizontally .main-container { - position: relative; - flex: 1; - height: 600px; - background-color: rgb(235, 235, 235); - margin: 0 30px; - transition: height 0.3s ease, margin 0.3s ease; - - - - .zoon-wrapper { - display: flex; - background-color: rgba(224, 223, 255, 0.5); - position: absolute; - bottom: 10px; - left: 50%; - transform: translate(-50%, 0); - gap: 6px; - padding: 4px; - border-radius: 8px; - max-width: 80%; - overflow: auto; - transition: transform 0.3s ease; - - &::-webkit-scrollbar { - display: none; - } - - .zone { - width: auto; - background-color: $background-color; - border-radius: 6px; - padding: 4px 8px; - white-space: nowrap; - cursor: pointer; - transition: background-color 0.3s ease; - - &.active { - background-color: var(--primary-color); - color: var(--accent-color); - } - } - - &.bottom { - bottom: 210px; - } - } + margin: 0 15px; // Reduce margin for better spacing } + .zoon-wrapper { + bottom: 5px; // Adjust position for smaller screens + + &.bottom { + bottom: 150px; // Adjust for bottom placement + } + } + } + + @media (max-width: 768px) { + width: 90%; // Take even more width on very small screens + height: 400px; // Further reduce height + top: 45%; // Adjust vertical position slightly upward + .panel { - position: absolute; - background: white; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - transition: all 0.3s ease; - border-radius: 6px; - overflow: visible !important; - + &.top-panel, + &.bottom-panel { .panel-content { - position: relative; - height: 100%; - padding: 10px; - overflow: auto; - display: flex; - flex-direction: column; - gap: 10px; + flex-direction: column; // Stack panels vertically on small screens - &::-webkit-scrollbar { - display: none; - } - - .chart-container { - width: 100%; - height: 200px; - max-height: 100%; - border: 1px dotted #a9a9a9; - border-radius: 8px; - box-shadow: 0px 2px 6px 0px rgba(60, 60, 67, 0.1); - padding: 6px 0; - background-color: white; - } - - .close-btn { - position: absolute; - top: 5px; - right: 5px; - background: none; - border: none; - cursor: pointer; - color: var(--primary-color); - } - } - - &.top-panel, - &.bottom-panel { - left: 0; - right: 0; - - .panel-content { - display: flex; - flex-direction: row; - - .chart-container { - height: 100%; - width: 230px; - } - } - } - - &.top-panel { - top: 0; - } - - &.bottom-panel { - bottom: 0; - } - - &.left-panel { - left: 0; - top: 0; - bottom: 0; - - .chart-container { - width: 100%; - height: 200px; - } - } - - &.right-panel { - right: 0; - top: 0; - bottom: 0; + .chart-container { + width: 100%; // Make charts full width + height: 150px; // Reduce chart height + } } + } } + } + + @media (max-width: 480px) { + width: 95%; // Take almost full width on very small devices + height: 350px; // Further reduce height + top: 40%; // Move slightly higher for better visibility + + .side-button-container { + flex-direction: row !important; // Force buttons into a row + gap: 4px; // Reduce spacing between buttons + + &.top, + &.bottom { + left: 50%; // Center horizontally + transform: translateX(-50%); + } + } + } + + .content-container { + display: flex; + height: 100vh; + transition: all 0.3s ease; + } + + .main-container { + position: relative; + flex: 1; + height: 600px; + background-color: rgb(235, 235, 235); + margin: 0 30px; + transition: height 0.3s ease, margin 0.3s ease; + + .zoon-wrapper { + display: flex; + background-color: rgba(224, 223, 255, 0.5); + position: absolute; + bottom: 10px; + left: 50%; + transform: translate(-50%, 0); + gap: 6px; + padding: 4px; + border-radius: 8px; + max-width: 80%; + overflow: auto; + transition: transform 0.3s ease; + + &::-webkit-scrollbar { + display: none; + } + + .zone { + width: auto; + background-color: $background-color; + border-radius: 6px; + padding: 4px 8px; + white-space: nowrap; + cursor: pointer; + transition: background-color 0.3s ease; + + &.active { + background-color: var(--primary-color); + color: var(--accent-color); + } + } + + &.bottom { + bottom: 210px; + } + } + } + + .panel { + position: absolute; + background: white; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + border-radius: 6px; + overflow: visible !important; + + .panel-content { + position: relative; + height: 100%; + padding: 10px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 10px; + + &::-webkit-scrollbar { + display: none; + } + + .chart-container { + width: 100%; + height: 200px; + max-height: 100%; + border: 1px dotted #a9a9a9; + border-radius: 8px; + box-shadow: 0px 2px 6px 0px rgba(60, 60, 67, 0.1); + padding: 6px 0; + background-color: white; + } + + .close-btn { + position: absolute; + top: 5px; + right: 5px; + background: none; + border: none; + cursor: pointer; + color: var(--primary-color); + } + } + + &.top-panel, + &.bottom-panel { + left: 0; + right: 0; + + .panel-content { + display: flex; + flex-direction: row; + + .chart-container { + height: 100%; + width: 230px; + } + } + } + + &.top-panel { + top: 0; + } + + &.bottom-panel { + bottom: 0; + } + + &.left-panel { + left: 0; + top: 0; + bottom: 0; + + .chart-container { + width: 100%; + height: 200px; + } + } + + &.right-panel { + right: 0; + top: 0; + bottom: 0; + } + } } // Side Buttons .side-button-container { - position: absolute; + position: absolute; + display: flex; + background-color: $background-color; + padding: 5px; + border-radius: 8px; + transition: transform 0.3s ease; + + .extra-Bs { display: flex; - background-color: $background-color; - padding: 5px; - border-radius: 8px; - transition: transform 0.3s ease; + align-items: center; + gap: 12px; - .extra-Bs { - display: flex; - align-items: center; - gap: 12px; - - .icon { - display: flex; - } - - &:hover { - cursor: pointer; - } + .icon { + display: flex; } - .side-button { - cursor: pointer; - transition: background-color 0.3s ease; - width: 18px; - height: 18px; - display: flex; - justify-content: center; - // align-items: center; - background-color: var(--accent-color); - border: none; - color: var(--background-color); - border-radius: 4px; - - &:hover { - // background-color: var(--primary-color); - // color: var(--accent-color); - } + &:hover { + cursor: pointer; } + } - &.top { - top: -30px; - left: 50%; - transform: translateX(-50%); - flex-direction: row; - gap: 6px; - } + .side-button { + cursor: pointer; + transition: background-color 0.3s ease; + width: 18px; + height: 18px; + display: flex; + justify-content: center; + // align-items: center; + background-color: var(--accent-color); + border: none; + color: var(--background-color); + border-radius: 4px; + } - &.right { - right: -30px; - top: 50%; - transform: translateY(-50%); - flex-direction: column; - gap: 6px; - } + &.top { + top: -30px; + left: 50%; + transform: translateX(-50%); + flex-direction: row; + gap: 6px; + } - &.bottom { - bottom: -30px; - left: 50%; - transform: translateX(-50%); - flex-direction: row; - gap: 6px; - } + &.right { + right: -30px; + top: 50%; + transform: translateY(-50%); + flex-direction: column; + gap: 6px; + } - &.left { - left: -30px; - top: 50%; - transform: translateY(-50%); - flex-direction: column; - gap: 6px; - } + &.bottom { + bottom: -30px; + left: 50%; + transform: translateX(-50%); + flex-direction: row; + gap: 6px; + } + + &.left { + left: -30px; + top: 50%; + transform: translateY(-50%); + flex-direction: column; + gap: 6px; + } } .right.side-button-container { - - .extra-Bs { - flex-direction: column; - } - + .extra-Bs { + flex-direction: column; + } } .left.side-button-container { - - .extra-Bs { - flex-direction: column; - } + .extra-Bs { + flex-direction: column; + } } // Theme Container .theme-container { - width: 250px; - padding: 12px; - box-shadow: 1px -3px 4px 0px rgba(0, 0, 0, 0.11); - border-radius: 8px; - background-color: white; - position: absolute; - top: 20px; - right: -100%; - transform: translate(-0%, 0); + width: 250px; + padding: 12px; + box-shadow: 1px -3px 4px 0px rgba(0, 0, 0, 0.11); + border-radius: 8px; + background-color: white; + position: absolute; + top: 20px; + right: -100%; + transform: translate(-0%, 0); - h2 { - font-size: 12px; - margin-bottom: 8px; - color: #2B3344; - } + h2 { + font-size: 12px; + margin-bottom: 8px; + color: #2b3344; + } - .theme-preset-wrapper { - display: flex; - gap: 5px; - flex-wrap: wrap; + .theme-preset-wrapper { + display: flex; + gap: 5px; + flex-wrap: wrap; - .theme-preset { - display: flex; - gap: 2px; - margin-bottom: 10px; - border: 1px solid $border-color; - padding: 5px 10px; - border-radius: 4px; - transition: border 0.3s ease; + .theme-preset { + display: flex; + gap: 2px; + margin-bottom: 10px; + border: 1px solid $border-color; + padding: 5px 10px; + border-radius: 4px; + transition: border 0.3s ease; - &.active { - border: 1px solid var(--primary-color); + &.active { + border: 1px solid var(--primary-color); - &::after { - content: ''; - position: absolute; - top: 1px; - left: 1px; - width: 10px; - height: 10px; - background-color: var(--primary-color); - border-radius: 50%; - } - } + &::after { + content: ""; + position: absolute; + top: 1px; + left: 1px; + width: 10px; + height: 10px; + background-color: var(--primary-color); + border-radius: 50%; } + } } + } - .custom-color { - display: flex; - justify-content: space-between; + .custom-color { + display: flex; + justify-content: space-between; - .color-displayer { - display: flex; - gap: 5px; - align-items: center; - border: 1px solid var(--accent-color); - border-radius: 4px; - padding: 0 5px; + .color-displayer { + display: flex; + gap: 5px; + align-items: center; + border: 1px solid var(--accent-color); + border-radius: 4px; + padding: 0 5px; - input { - border: none; - outline: none; - border-radius: 50%; - } - } + input { + border: none; + outline: none; + border-radius: 50%; + } } -} \ No newline at end of file + } +} -- 2.49.1 From 4549a5cae40ef6ba397b0dfd84bea567682806d8 Mon Sep 17 00:00:00 2001 From: Marikannan-HexrFactory Date: Thu, 20 Mar 2025 10:16:11 +0530 Subject: [PATCH 4/7] UI Test --- .vscode/settings.json | 13 +++++++++++++ app/src/main.tsx | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..905c103 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "**/.retool_types/**": true, + "**/*tsconfig.json": true, + ".cache": true, + "retool.config.json": true + } +} \ No newline at end of file diff --git a/app/src/main.tsx b/app/src/main.tsx index 424d38b..4f48401 100644 --- a/app/src/main.tsx +++ b/app/src/main.tsx @@ -2,7 +2,7 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './styles/main.scss' import App from './App.tsx' - +//Test createRoot(document.getElementById('root')!).render( -- 2.49.1 From 7950b58ba8fd24ec5f9e4f5325a2f6b4a232b015 Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Thu, 20 Mar 2025 16:30:43 +0530 Subject: [PATCH 5/7] updated real time vis --- app/package-lock.json | 11 + app/package.json | 1 + .../sidebarLeft/visualization/Templates.tsx | 110 ++++---- .../visualization/widgets/Widgets.tsx | 8 +- .../visualization/widgets/WidgetsFloating.tsx | 191 ++++++++++++++ .../visualization/widgets/WidgetsTemplate.tsx | 12 - .../layout/sidebarRight/SideBarRight.tsx | 2 +- .../sidebarRight/visualization/data/Data.tsx | 63 ++++- app/src/components/ui/Tools.tsx | 9 +- .../ui/charts/BarGraphComponent.tsx | 94 +++++++ .../ui/charts/LineGraphComponent.tsx | 93 +++++++ .../ui/charts/PieGraphComponent.tsx | 90 +++++++ .../components/ui/componets/AddButtons.tsx | 38 +-- .../components/ui/componets/DisplayZone.tsx | 178 +++++++++++++ .../ui/componets/DraggableWidget.tsx | 249 ++++-------------- app/src/components/ui/componets/Panel.tsx | 92 ++++--- .../ui/componets/RealTimeVisulization.tsx | 33 +-- .../ui/inputs/MultiLevelDropDown.tsx | 71 ++++- .../visualization/handleSaveTemplate.ts | 80 ++++-- app/src/styles/abstracts/variables.scss | 2 +- .../styles/components/_regularDropDown.scss | 2 + .../floating/energyConsumed.scss | 110 ++++++++ app/src/styles/layout/sidebar.scss | 210 ++++++++++----- app/src/styles/main.scss | 1 + app/src/styles/pages/realTimeViz.scss | 66 ++--- 25 files changed, 1310 insertions(+), 506 deletions(-) create mode 100644 app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx delete mode 100644 app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsTemplate.tsx create mode 100644 app/src/components/ui/charts/BarGraphComponent.tsx create mode 100644 app/src/components/ui/charts/LineGraphComponent.tsx create mode 100644 app/src/components/ui/charts/PieGraphComponent.tsx create mode 100644 app/src/components/ui/componets/DisplayZone.tsx create mode 100644 app/src/styles/components/visualization/floating/energyConsumed.scss diff --git a/app/package-lock.json b/app/package-lock.json index 45a89a1..937d3e6 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -11,6 +11,7 @@ "chart.js": "^4.4.8", "path": "^0.12.7", "react": "^19.0.0", + "react-chartjs-2": "^5.3.0", "react-dom": "^19.0.0", "react-router-dom": "^7.3.0", "zustand": "^5.0.3" @@ -3247,6 +3248,16 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", diff --git a/app/package.json b/app/package.json index abba5c4..3f3d446 100644 --- a/app/package.json +++ b/app/package.json @@ -13,6 +13,7 @@ "chart.js": "^4.4.8", "path": "^0.12.7", "react": "^19.0.0", + "react-chartjs-2": "^5.3.0", "react-dom": "^19.0.0", "react-router-dom": "^7.3.0", "zustand": "^5.0.3" diff --git a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx index d54d666..4528c06 100644 --- a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx @@ -5,8 +5,8 @@ import { useSelectedZoneStore } from "../../../../store/useZoneStore"; const Templates = () => { const { templates, removeTemplate } = useTemplateStore(); const { setSelectedZone } = useSelectedZoneStore(); - - console.log('templates: ', templates); + + console.log("templates: ", templates); const handleDeleteTemplate = (id: string) => { removeTemplate(id); }; @@ -15,40 +15,49 @@ const Templates = () => { setSelectedZone((prev) => ({ ...prev, panelOrder: template.panelOrder, - activeSides: Array.from(new Set([...prev.activeSides, ...template.panelOrder])), + activeSides: Array.from( + new Set([...prev.activeSides, ...template.panelOrder]) + ), widgets: template.widgets, })); }; return ( -
+
{templates.map((template) => ( -
+
{template.snapshot && ( -
{/* 16:9 aspect ratio */} +
+ {" "} + {/* 16:9 aspect ratio */} {`${template.name} { />
)} -
+
handleLoadTemplate(template)} - style={{ - cursor: 'pointer', - fontWeight: '500', + style={{ + cursor: "pointer", + fontWeight: "500", // ':hover': { // textDecoration: 'underline' // } @@ -75,16 +86,16 @@ const Templates = () => { > {template.name}
-
))} {templates.length === 0 && ( -
+
No saved templates yet. Create one in the visualization view!
)} @@ -111,4 +124,3 @@ const Templates = () => { }; export default Templates; - diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx index 3fde4c8..a96ff83 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx @@ -2,10 +2,10 @@ import { useState } from "react"; import ToggleHeader from "../../../../ui/inputs/ToggleHeader"; import Widgets2D from "./Widgets2D"; import Widgets3D from "./Widgets3D"; -import WidgetsTemplate from "./WidgetsTemplate"; +import WidgetsFloating from "./WidgetsFloating"; const Widgets = () => { - const [activeOption, setActiveOption] = useState("2D"); + const [activeOption, setActiveOption] = useState("Floating"); const handleToggleClick = (option: string) => { setActiveOption(option); @@ -14,13 +14,13 @@ const Widgets = () => { return (
{activeOption === "2D" && } {activeOption === "3D" && } - {activeOption === "Templates" && } + {activeOption === "Floating" && }
); }; diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx new file mode 100644 index 0000000..9247508 --- /dev/null +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx @@ -0,0 +1,191 @@ +import React, { + useState, + DragEvent, + MouseEvent, + useRef, + useEffect, +} from "react"; + +const WidgetsFloating: React.FC = () => { + const stateWorking = [ + { "Oil Tank": "24/341" }, + { "Oil Refin": "36.023" }, + { Transmission: "36.023" }, + { Fuel: "36732" }, + { Power: "1300" }, + { Time: "13-9-2023" }, + ]; + + // State for storing the dragged widget and its position + const [draggedFloating, setDraggedFloating] = useState(null); + const [position, setPosition] = useState<{ x: number; y: number }>({ + x: 0, + y: 0, + }); + + // State to store all placed widgets with their positions + const [placedWidgets, setPlacedWidgets] = useState< + { name: string; x: number; y: number }[] + >([]); + + const canvasRef = useRef(null); + + // Handle the drag start event + const handleDragStart = ( + event: DragEvent, + widget: string + ) => { + setDraggedFloating(widget); + // Initialize position to the current position when drag starts + setPosition({ x: event.clientX, y: event.clientY }); + }; + + // Handle the drag move event + const handleDragMove = (event: MouseEvent) => { + if (!draggedFloating) return; + + // Calculate new position and update it + const canvas = canvasRef.current; + if (canvas) { + const canvasRect = canvas.getBoundingClientRect(); + const newX = event.clientX - canvasRect.left; + const newY = event.clientY - canvasRect.top; + setPosition({ x: newX, y: newY }); + } + }; + + // Handle the drag end event + const handleDragEnd = () => { + if (draggedFloating) { + // Store the final position of the dragged widget + setPlacedWidgets((prevWidgets) => [ + ...prevWidgets, + { name: draggedFloating, x: position.x, y: position.y }, + ]); + // Reset the dragged floating widget after dragging is completed + setDraggedFloating(null); + } + }; + + useEffect(() => { + console.log("position: ", position); + console.log("draggedFloating: ", draggedFloating); + }, [draggedFloating, position]); + + return ( +
+ {/* The floating widget that's being dragged */} + {draggedFloating && ( +
+ {draggedFloating} +
+ )} + + {/* Render all placed widgets */} + {placedWidgets.map((widget, index) => ( +
+ {widget.name} +
+ ))} + + {/* The rest of your floating widgets */} +
handleDragStart(e, "working-state")} + style={{ position: "absolute", top: "50px", left: "50px" }} + > +
+
+
State
+
+ Working + +
+
+
+ Factory +
+
+
+ {stateWorking.map((state, index) => { + const key = Object.keys(state)[0]; + const value = state[key]; + return ( +
+ {key}: + {value} +
+ ); + })} +
+
+ + {/* Other floating widgets */} +
handleDragStart(e, "floating-2")} + style={{ position: "absolute", top: "120px", left: "150px" }} + > + floating-2 +
+
handleDragStart(e, "floating-3")} + style={{ position: "absolute", top: "200px", left: "250px" }} + > + floating-3 +
+
handleDragStart(e, "floating-4")} + style={{ position: "absolute", top: "300px", left: "350px" }} + > + floating-4 +
+
+ ); +}; + +export default WidgetsFloating; \ No newline at end of file diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsTemplate.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsTemplate.tsx deleted file mode 100644 index 14ab07e..0000000 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsTemplate.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react' - -const WidgetsTemplate = () => { - return ( -
- WidgetsTemplate - -
- ) -} - -export default WidgetsTemplate diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index db44f69..5c59c19 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -65,7 +65,7 @@ const SideBarRight: React.FC = () => { )} {/* realtime visualization */} - {activeModule === "visualization" && } + {toggleUI && activeModule === "visualization" && }
); }; diff --git a/app/src/components/layout/sidebarRight/visualization/data/Data.tsx b/app/src/components/layout/sidebarRight/visualization/data/Data.tsx index d1122bb..e08c4d9 100644 --- a/app/src/components/layout/sidebarRight/visualization/data/Data.tsx +++ b/app/src/components/layout/sidebarRight/visualization/data/Data.tsx @@ -1,13 +1,9 @@ import React, { useEffect, useState } from "react"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; -import { RemoveIcon } from "../../../../icons/ExportCommonIcons"; -import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; +import { AddIcon, RemoveIcon } from "../../../../icons/ExportCommonIcons"; import MultiLevelDropDown from "../../../../ui/inputs/MultiLevelDropDown"; -interface Child { - id: number; - easing: string; -} +// Define the data structure for demonstration purposes const DATA_STRUCTURE = { furnace: { coolingRate: "coolingRate", @@ -35,13 +31,22 @@ const DATA_STRUCTURE = { data3: "Data 3", }, timestamp: { - data1: "Data 1", + data1: { + Data01: "Data 01", + Data02: "Data 02", + Data03: "Data 03", + }, data2: "Data 2", data3: "Data 3", }, }, }; +interface Child { + id: number; + easing: string; +} + interface Group { id: number; easing: string; @@ -65,11 +70,7 @@ const Data = () => { { 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" }, - ], + children: [{ id: Date.now(), easing: "Linear" }], }, ], })); @@ -121,7 +122,43 @@ const Data = () => { {selectedChartId?.title && (
{selectedChartId?.title}
)} - {/* */} + {/* Render groups dynamically */} + {chartDataGroups[selectedChartId?.id]?.map((group) => ( +
+ {group.children.map((child, index) => ( +
+
Input {index + 1}
+
+ + {/* Add Icon */} + {group.children.length < 7 && ( +
handleAddClick(group.id)} // Pass groupId to handleAddClick + > + +
+ )} + {/* Remove Icon */} + + 1 ? "" : "disable" + }`} + onClick={(e) => { + e.stopPropagation(); // Prevent event bubbling + removeChild(group.id, child.id); // Pass groupId and childId to removeChild + }} + > + + +
+
+ ))} +
+ ))} + + {/* Info Box */}
i

diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index a182e1d..f6389e9 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -19,6 +19,7 @@ import useTemplateStore from "../../store/useTemplateStore"; import { useSelectedZoneStore } from "../../store/useZoneStore"; const Tools: React.FC = () => { + const { templates } = useTemplateStore(); const [activeTool, setActiveTool] = useState("cursor"); const [activeSubTool, setActiveSubTool] = useState("cursor"); const [toggleThreeD, setToggleThreeD] = useState(true); @@ -196,7 +197,13 @@ const Tools: React.FC = () => {

handleSaveTemplate({ addTemplate, selectedZone })} + onClick={() => + handleSaveTemplate({ + addTemplate, + selectedZone, + templates, + }) + } >
diff --git a/app/src/components/ui/charts/BarGraphComponent.tsx b/app/src/components/ui/charts/BarGraphComponent.tsx new file mode 100644 index 0000000..466d4df --- /dev/null +++ b/app/src/components/ui/charts/BarGraphComponent.tsx @@ -0,0 +1,94 @@ +import { useRef, useMemo } from "react"; + +import { Bar, Line } from "react-chartjs-2"; + +interface ChartComponentProps { + type: any; + title: string; + fontFamily?: string; + fontSize?: string; + fontWeight?: "Light" | "Regular" | "Bold"; + data: any; +} + +const LineGraphComponent = ({ + title, + fontFamily, + fontSize, + fontWeight = "Regular", +}: ChartComponentProps) => { + // Memoize Font Weight Mapping + const chartFontWeightMap = useMemo( + () => ({ + Light: "lighter" as const, + Regular: "normal" as const, + Bold: "bold" as const, + }), + [] + ); + + // Parse and Memoize Font Size + const fontSizeValue = useMemo( + () => (fontSize ? parseInt(fontSize) : 12), + [fontSize] + ); + + // Determine and Memoize Font Weight + const fontWeightValue = useMemo( + () => chartFontWeightMap[fontWeight], + [fontWeight, chartFontWeightMap] + ); + + // Memoize Chart Font Style + const chartFontStyle = useMemo( + () => ({ + family: fontFamily || "Arial", + size: fontSizeValue, + weight: fontWeightValue, + }), + [fontFamily, fontSizeValue, fontWeightValue] + ); + + const options = useMemo( + () => ({ + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: title, + font: chartFontStyle, + }, + legend: { + display: false, + }, + }, + scales: { + x: { + ticks: { + display: false, // This hides the x-axis labels + }, + }, + }, + }), + [title, chartFontStyle] + ); + + const chartData = { + labels: ["January", "February", "March", "April", "May", "June", "July"], + datasets: [ + { + label: "My First Dataset", + data: [65, 59, 80, 81, 56, 55, 40], + backgroundColor: "#6f42c1", + borderColor: "#ffffff", + borderWidth: 2, + fill: false, + }, + ], + }; + + return ; +}; + +export default LineGraphComponent; diff --git a/app/src/components/ui/charts/LineGraphComponent.tsx b/app/src/components/ui/charts/LineGraphComponent.tsx new file mode 100644 index 0000000..98b9d61 --- /dev/null +++ b/app/src/components/ui/charts/LineGraphComponent.tsx @@ -0,0 +1,93 @@ +import { useRef, useMemo } from "react"; +import { Line } from "react-chartjs-2"; + +interface ChartComponentProps { + type: any; + title: string; + fontFamily?: string; + fontSize?: string; + fontWeight?: "Light" | "Regular" | "Bold"; + data: any; +} + +const LineGraphComponent = ({ + title, + fontFamily, + fontSize, + fontWeight = "Regular", +}: ChartComponentProps) => { + // Memoize Font Weight Mapping + const chartFontWeightMap = useMemo( + () => ({ + Light: "lighter" as const, + Regular: "normal" as const, + Bold: "bold" as const, + }), + [] + ); + + // Parse and Memoize Font Size + const fontSizeValue = useMemo( + () => (fontSize ? parseInt(fontSize) : 12), + [fontSize] + ); + + // Determine and Memoize Font Weight + const fontWeightValue = useMemo( + () => chartFontWeightMap[fontWeight], + [fontWeight, chartFontWeightMap] + ); + + // Memoize Chart Font Style + const chartFontStyle = useMemo( + () => ({ + family: fontFamily || "Arial", + size: fontSizeValue, + weight: fontWeightValue, + }), + [fontFamily, fontSizeValue, fontWeightValue] + ); + + const options = useMemo( + () => ({ + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: title, + font: chartFontStyle, + }, + legend: { + display: false, + }, + }, + scales: { + x: { + ticks: { + display: false, // This hides the x-axis labels + }, + }, + }, + }), + [title, chartFontStyle] + ); + + const chartData = { + labels: ["January", "February", "March", "April", "May", "June", "July"], + datasets: [ + { + label: "My First Dataset", + data: [65, 59, 80, 81, 56, 55, 40], + backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple) + borderColor: "#ffffff", // Keeping border color white + borderWidth: 2, + fill: false, + }, + ], + }; + + return ; +}; + +export default LineGraphComponent; diff --git a/app/src/components/ui/charts/PieGraphComponent.tsx b/app/src/components/ui/charts/PieGraphComponent.tsx new file mode 100644 index 0000000..fb553fb --- /dev/null +++ b/app/src/components/ui/charts/PieGraphComponent.tsx @@ -0,0 +1,90 @@ +import { useRef, useMemo } from "react"; +import { Pie } from "react-chartjs-2"; + +interface ChartComponentProps { + type: any; + title: string; + fontFamily?: string; + fontSize?: string; + fontWeight?: "Light" | "Regular" | "Bold"; + data: any; +} + +const PieChartComponent = ({ + title, + fontFamily, + fontSize, + fontWeight = "Regular", +}: ChartComponentProps) => { + // Memoize Font Weight Mapping + const chartFontWeightMap = useMemo( + () => ({ + Light: "lighter" as const, + Regular: "normal" as const, + Bold: "bold" as const, + }), + [] + ); + + // Parse and Memoize Font Size + const fontSizeValue = useMemo( + () => (fontSize ? parseInt(fontSize) : 12), + [fontSize] + ); + + // Determine and Memoize Font Weight + const fontWeightValue = useMemo( + () => chartFontWeightMap[fontWeight], + [fontWeight, chartFontWeightMap] + ); + + // Memoize Chart Font Style + const chartFontStyle = useMemo( + () => ({ + family: fontFamily || "Arial", + size: fontSizeValue, + weight: fontWeightValue, + }), + [fontFamily, fontSizeValue, fontWeightValue] + ); + + // Access the CSS variable for the primary accent color + const accentColor = getComputedStyle(document.documentElement) + .getPropertyValue("--accent-color") + .trim(); + + const options = useMemo( + () => ({ + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: title, + font: chartFontStyle, + }, + legend: { + display: false, + }, + }, + }), + [title, chartFontStyle] + ); + + const chartData = { + labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], + datasets: [ + { + label: "Dataset", + data: [12, 19, 3, 5, 2, 3], + backgroundColor: ["#6f42c1"], + borderColor: "#ffffff", + borderWidth: 2, + }, + ], + }; + + return ; +}; + +export default PieChartComponent; diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/components/ui/componets/AddButtons.tsx index 0a810f7..4ff9ebb 100644 --- a/app/src/components/ui/componets/AddButtons.tsx +++ b/app/src/components/ui/componets/AddButtons.tsx @@ -11,7 +11,7 @@ type Side = "top" | "bottom" | "left" | "right"; // Define the type for the props passed to the Buttons component interface ButtonsProps { selectedZone: { - zoneName: string; // Add zoneName property + zoneName: string; activeSides: Side[]; panelOrder: Side[]; lockedPanels: Side[]; @@ -25,7 +25,7 @@ interface ButtonsProps { }; setSelectedZone: React.Dispatch< React.SetStateAction<{ - zoneName: string; // Ensure zoneName is included in the state type + zoneName: string; activeSides: Side[]; panelOrder: Side[]; lockedPanels: Side[]; @@ -38,12 +38,18 @@ interface ButtonsProps { }[]; }> >; + hiddenPanels: Side[]; // Add this prop for hidden panels + setHiddenPanels: React.Dispatch>; // Add this prop for updating hidden panels } const AddButtons: React.FC = ({ selectedZone, setSelectedZone, + setHiddenPanels, + hiddenPanels, }) => { + // Local state to track hidden panels + // Function to toggle lock/unlock a panel const toggleLockPanel = (side: Side) => { const newLockedPanels = selectedZone.lockedPanels.includes(side) @@ -61,18 +67,14 @@ const AddButtons: React.FC = ({ // Function to toggle visibility of a panel const toggleVisibility = (side: Side) => { - const newActiveSides = selectedZone.activeSides.includes(side) - ? selectedZone.activeSides.filter((s) => s !== side) - : [...selectedZone.activeSides, side]; - - const updatedZone = { - ...selectedZone, - activeSides: newActiveSides, - panelOrder: newActiveSides, - }; - - // Update the selectedZone state - setSelectedZone(updatedZone); + const isHidden = hiddenPanels.includes(side); + if (isHidden) { + // If the panel is already hidden, remove it from the hiddenPanels array + setHiddenPanels(hiddenPanels.filter((panel) => panel !== side)); + } else { + // If the panel is visible, add it to the hiddenPanels array + setHiddenPanels([...hiddenPanels, side]); + } }; // Function to clean all widgets from a panel @@ -145,8 +147,12 @@ const AddButtons: React.FC = ({
{/* Hide Panel */}
toggleVisibility(side)} > diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx new file mode 100644 index 0000000..b5fe0f0 --- /dev/null +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -0,0 +1,178 @@ +import React, { useEffect, useRef } from "react"; +import { useSelectedZoneStore } from "../../../store/useZoneStore"; +import { Widget } from "../../../store/useWidgetStore"; + +// Define the type for `Side` +type Side = "top" | "bottom" | "left" | "right"; + +interface DisplayZoneProps { + zonesData: { + [key: string]: { + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; + }; + }; + selectedZone: { + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: { + id: string; + type: string; + title: string; + panel: Side; + data: any; + }[]; + }; + setSelectedZone: React.Dispatch< + React.SetStateAction<{ + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: { + id: string; + type: string; + title: string; + panel: Side; + data: any; + }[]; + }> + >; +} + +const DisplayZone: React.FC = ({ + zonesData, + selectedZone, + setSelectedZone, +}) => { + // Ref for the container element + const containerRef = useRef(null); + + // Example state for selectedOption and options (adjust based on your actual use case) + const [selectedOption, setSelectedOption] = React.useState( + null + ); + const [options, setOptions] = React.useState([]); + + // Scroll to the selected option when it changes + useEffect(() => { + const container = containerRef.current; + + if (container && selectedOption) { + // Handle scrolling to the selected option + const index = options.findIndex((option) => { + const formattedOption = formatOptionName(option); + const selectedFormattedOption = + selectedOption?.split("_")[1] || selectedOption; + return formattedOption === selectedFormattedOption; + }); + + if (index !== -1) { + const optionElement = container.children[index] as HTMLElement; + if (optionElement) { + optionElement.scrollIntoView({ + behavior: "smooth", + block: "nearest", + inline: "center", + }); + } + } + } + }, [selectedOption, options]); + + useEffect(() => { + const container = containerRef.current; + + const handleWheel = (event: WheelEvent) => { + event.preventDefault(); + if (container) { + container.scrollBy({ + left: event.deltaY * 2, // Adjust the multiplier for faster scrolling + behavior: "smooth", + }); + } + }; + + let isDragging = false; + let startX: number; + let scrollLeft: number; + + const handleMouseDown = (event: MouseEvent) => { + isDragging = true; + startX = event.pageX - (container?.offsetLeft || 0); + scrollLeft = container?.scrollLeft || 0; + }; + + const handleMouseMove = (event: MouseEvent) => { + if (!isDragging || !container) return; + event.preventDefault(); + const x = event.pageX - (container.offsetLeft || 0); + const walk = (x - startX) * 2; // Adjust the multiplier for faster dragging + container.scrollLeft = scrollLeft - walk; + }; + + const handleMouseUp = () => { + isDragging = false; + }; + + const handleMouseLeave = () => { + isDragging = false; + }; + + if (container) { + container.addEventListener("wheel", handleWheel, { passive: false }); + container.addEventListener("mousedown", handleMouseDown); + container.addEventListener("mousemove", handleMouseMove); + container.addEventListener("mouseup", handleMouseUp); + container.addEventListener("mouseleave", handleMouseLeave); + } + + return () => { + if (container) { + container.removeEventListener("wheel", handleWheel); + container.removeEventListener("mousedown", handleMouseDown); + container.removeEventListener("mousemove", handleMouseMove); + container.removeEventListener("mouseup", handleMouseUp); + container.removeEventListener("mouseleave", handleMouseLeave); + } + }; + }, []); + + // Helper function to format option names (customize as needed) + const formatOptionName = (option: string): string => { + // Replace underscores with spaces and capitalize the first letter + return option.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase()); + }; + + return ( +
+ {Object.keys(zonesData).map((zoneName, index) => ( +
{ + setSelectedZone({ + zoneName, + ...zonesData[zoneName], + }); + }} + > + {zoneName} +
+ ))} +
+ ); +}; + +export default DisplayZone; \ No newline at end of file diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index 342f31f..6769890 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -1,217 +1,84 @@ import { useMemo, useState } from "react"; import ChartComponent from "../../layout/sidebarLeft/visualization/widgets/ChartComponent"; import { useWidgetStore } from "../../../store/useWidgetStore"; +import PieGraphComponent from "../charts/PieGraphComponent"; +import BarGraphComponent from "../charts/BarGraphComponent"; +import LineGraphComponent from "../charts/LineGraphComponent"; export const DraggableWidget = ({ widget }: { widget: any }) => { const { selectedChartId, setSelectedChartId } = useWidgetStore(); - // State for managing the popup visibility and customization options - const [isPopupOpen, setIsPopupOpen] = useState(false); - const [customizationOptions, setCustomizationOptions] = useState({ - templateBackground: "#ffffff", - cardBackground: "#ffffff", - cardOpacity: 1, - cardBlur: 0, - font: "Arial", - margin: 0, - radius: 5, - shadow: "Low", - }); - - // Handle pointer down to select the chart const handlePointerDown = () => { if (selectedChartId?.id !== widget.id) { setSelectedChartId(widget); } }; - // Handle double-click to open the popup - const handleDoubleClick = () => { - setIsPopupOpen(true); - }; - - // Close the popup - const handleClosePopup = () => { - setIsPopupOpen(false); - }; - - // Save the changes made in the popup - const handleSaveChanges = () => { - // Here you can save the customizationOptions to your store or API - console.log("Saved Customization Options:", customizationOptions); - setIsPopupOpen(false); - }; - - // Compute dynamic card styles based on customizationOptions - const cardStyle = useMemo(() => { - const shadowLevels = { - Low: "0px 2px 4px rgba(0, 0, 0, 0.1)", - Medium: "0px 4px 8px rgba(0, 0, 0, 0.2)", - High: "0px 8px 16px rgba(0, 0, 0, 0.3)", - }; - - return { - backgroundColor: customizationOptions.cardBackground, - opacity: customizationOptions.cardOpacity, - filter: `blur(${customizationOptions.cardBlur}px)`, - fontFamily: customizationOptions.font, - margin: `${customizationOptions.margin}px`, - borderRadius: `${customizationOptions.radius}px`, - // boxShadow: shadowLevels[customizationOptions.shadow], - }; - }, [customizationOptions]); - return ( <>
{widget.type === "progress" ? ( - // - <> + // + <> ) : ( - + <> + {widget.type === "line" && ( + + )} + {widget.type === "bar" && ( + + )} + {widget.type === "pie" && ( + + )} + )}
- - {/* Popup for Customizing Template Theme */} - {isPopupOpen && ( -
-
-

Customize Template Theme

-
- - - setCustomizationOptions((prev) => ({ - ...prev, - templateBackground: e.target.value, - })) - } - /> -
-
- - - setCustomizationOptions((prev) => ({ - ...prev, - cardBackground: e.target.value, - })) - } - /> -
-
- - - setCustomizationOptions((prev) => ({ - ...prev, - cardOpacity: parseFloat(e.target.value), - })) - } - /> -
-
- - - setCustomizationOptions((prev) => ({ - ...prev, - cardBlur: parseInt(e.target.value), - })) - } - /> -
-
- - -
-
- - - setCustomizationOptions((prev) => ({ - ...prev, - margin: parseInt(e.target.value), - })) - } - /> -
-
- - - setCustomizationOptions((prev) => ({ - ...prev, - radius: parseInt(e.target.value), - })) - } - /> -
-
- - -
-
- - -
-
-
- )} ); }; diff --git a/app/src/components/ui/componets/Panel.tsx b/app/src/components/ui/componets/Panel.tsx index 502114b..4a1305f 100644 --- a/app/src/components/ui/componets/Panel.tsx +++ b/app/src/components/ui/componets/Panel.tsx @@ -83,47 +83,54 @@ const Panel: React.FC = ({ selectedZone, setSelectedZone }) => { const handleDrop = (e: React.DragEvent, panel: Side) => { e.preventDefault(); const { draggedAsset } = useWidgetStore.getState(); - - if (draggedAsset) { - if (selectedZone.lockedPanels.includes(panel)) return; - - const currentWidgetsInPanel = selectedZone.widgets.filter( - (w) => w.panel === panel - ).length; - - const dimensions = panelDimensions[panel]; - const CHART_WIDTH = 200; - const CHART_HEIGHT = 200; - let maxCharts = 0; - - if (dimensions) { - if (panel === "top" || panel === "bottom") { - maxCharts = Math.floor(dimensions.width / CHART_WIDTH); - } else { - maxCharts = Math.floor(dimensions.height / CHART_HEIGHT); - } - } else { - maxCharts = panel === "top" || panel === "bottom" ? 5 : 3; - } - - if (currentWidgetsInPanel >= maxCharts) { - return; - } - - const updatedZone = { - ...selectedZone, - widgets: [ - ...selectedZone.widgets, - { - ...draggedAsset, - id: generateUniqueId(), - panel, - }, - ], - }; - - setSelectedZone(updatedZone); + + if (!draggedAsset) return; + if (isPanelLocked(panel)) return; + + const currentWidgetsCount = getCurrentWidgetCount(panel); + const maxCapacity = calculatePanelCapacity(panel); + + if (currentWidgetsCount >= maxCapacity) return; + + addWidgetToPanel(draggedAsset, panel); + }; + + // Helper functions + const isPanelLocked = (panel: Side) => + selectedZone.lockedPanels.includes(panel); + + const getCurrentWidgetCount = (panel: Side) => + selectedZone.widgets.filter(w => w.panel === panel).length; + + const calculatePanelCapacity = (panel: Side) => { + const CHART_WIDTH = 200; + const CHART_HEIGHT = 200; + const FALLBACK_HORIZONTAL_CAPACITY = 5; + const FALLBACK_VERTICAL_CAPACITY = 3; + + const dimensions = panelDimensions[panel]; + if (!dimensions) { + return panel === "top" || panel === "bottom" + ? FALLBACK_HORIZONTAL_CAPACITY + : FALLBACK_VERTICAL_CAPACITY; } + + return panel === "top" || panel === "bottom" + ? Math.floor(dimensions.width / CHART_WIDTH) + : Math.floor(dimensions.height / CHART_HEIGHT); + }; + + const addWidgetToPanel = (asset: any, panel: Side) => { + const newWidget = { + ...asset, + id: generateUniqueId(), + panel, + }; + + setSelectedZone(prev => ({ + ...prev, + widgets: [...prev.widgets, newWidget] + })); }; useEffect(() => { @@ -172,7 +179,7 @@ const Panel: React.FC = ({ selectedZone, setSelectedZone }) => { }} >
= ({ selectedZone, setSelectedZone }) => { opacity: selectedZone.lockedPanels.includes(side) ? "0.8" : "1", }} > + <>{} {selectedZone.widgets .filter((w) => w.panel === side) .map((widget) => ( @@ -193,5 +201,3 @@ const Panel: React.FC = ({ selectedZone, setSelectedZone }) => { }; export default Panel; -// only load selected template - diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index fb2d653..6daa075 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -3,6 +3,7 @@ import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import Panel from "./Panel"; import AddButtons from "./AddButtons"; import { useSelectedZoneStore } from "../../../store/useZoneStore"; +import DisplayZone from "./DisplayZone"; type Side = "top" | "bottom" | "left" | "right"; @@ -15,6 +16,7 @@ interface Widget { } const RealTimeVisulization: React.FC = () => { + const [hiddenPanels, setHiddenPanels] = React.useState([]); const containerRef = useRef(null); const [zonesData, setZonesData] = useState<{ [key: string]: { @@ -74,34 +76,19 @@ const RealTimeVisulization: React.FC = () => { style={{ height: isPlaying ? "100vh" : "", width: isPlaying ? "100%" : "", - left: isPlaying ? "50%" : "", + left: isPlaying ? "0%" : "", }} > -
- {Object.keys(zonesData).map((zoneName, index) => ( -
{ - setSelectedZone({ - zoneName, - ...zonesData[zoneName], - }); - }} - > - {zoneName} -
- ))} -
+ {!isPlaying && ( diff --git a/app/src/components/ui/inputs/MultiLevelDropDown.tsx b/app/src/components/ui/inputs/MultiLevelDropDown.tsx index 215bbd8..477111a 100644 --- a/app/src/components/ui/inputs/MultiLevelDropDown.tsx +++ b/app/src/components/ui/inputs/MultiLevelDropDown.tsx @@ -1,5 +1,4 @@ -import React, { useState } from "react"; - +import React, { useState, useRef, useEffect } from "react"; // Dropdown Item Component const DropdownItem = ({ @@ -27,9 +26,11 @@ const DropdownItem = ({ const NestedDropdown = ({ label, children, + onSelect, }: { label: string; children: React.ReactNode; + onSelect: (selectedLabel: string) => void; }) => { const [open, setOpen] = useState(false); @@ -37,36 +38,47 @@ const NestedDropdown = ({
{/* Dropdown Trigger */}
setOpen(!open)} // Toggle submenu on click > {label} {open ? "▼" : "▶"}
{/* Submenu */} - {open &&
{children}
} + {open && ( +
+ {React.Children.map(children, (child) => + React.cloneElement(child as React.ReactElement, { onSelect }) + )} +
+ )}
); }; // Recursive Function to Render Nested Data -const renderNestedData = (data: Record) => { +const renderNestedData = ( + data: Record, + onSelect: (selectedLabel: string) => void +) => { return Object.entries(data).map(([key, value]) => { if (typeof value === "object" && !Array.isArray(value)) { // If the value is an object, render it as a nested dropdown return ( - - {renderNestedData(value)} + + {renderNestedData(value, onSelect)} ); } else if (Array.isArray(value)) { // If the value is an array, render each item as a dropdown item return value.map((item, index) => ( - + onSelect(item)} /> )); } else { // If the value is a simple string, render it as a dropdown item - return ; + return ( + onSelect(value)} /> + ); } }); }; @@ -74,21 +86,52 @@ const renderNestedData = (data: Record) => { // Main Multi-Level Dropdown Component const MultiLevelDropdown = ({ data }: { data: Record }) => { const [open, setOpen] = useState(false); + const [selectedLabel, setSelectedLabel] = useState("Dropdown trigger"); + const dropdownRef = useRef(null); + + // Handle outer click to close the dropdown + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + // Handle selection of an item + const handleSelect = (selectedLabel: string) => { + setSelectedLabel(selectedLabel); // Update the dropdown trigger text + setOpen(false); // Close the dropdown + }; return ( -
+
{/* Dropdown Trigger Button */} {/* Dropdown Menu */} - {open &&
{renderNestedData(data)}
} + {open && ( +
+
+ {renderNestedData(data, handleSelect)} +
+
+ )}
); }; -export default MultiLevelDropdown; \ No newline at end of file +export default MultiLevelDropdown; diff --git a/app/src/modules/visualization/handleSaveTemplate.ts b/app/src/modules/visualization/handleSaveTemplate.ts index 290395a..c489688 100644 --- a/app/src/modules/visualization/handleSaveTemplate.ts +++ b/app/src/modules/visualization/handleSaveTemplate.ts @@ -2,35 +2,73 @@ import { Template } from "../../store/useTemplateStore"; import { captureVisualization } from "./captureVisualization"; type HandleSaveTemplateProps = { - addTemplate: (template: Template) => void; - selectedZone: { - panelOrder: string[]; // Adjust the type based on actual data structure - widgets: any[]; // Replace `any` with the actual widget type - }; + addTemplate: (template: Template) => void; + selectedZone: { + panelOrder: string[]; // Adjust the type based on actual data structure + widgets: any[]; // Replace `any` with the actual widget type + }; + templates?: Template[]; }; // Generate a unique ID (placeholder function) const generateUniqueId = (): string => { - return Math.random().toString(36).substring(2, 15); + return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`; }; // Refactored function export const handleSaveTemplate = async ({ - addTemplate, - selectedZone, + addTemplate, + selectedZone, + templates = [], }: HandleSaveTemplateProps): Promise => { - try { - const snapshot = await captureVisualization(); - const template: Template = { - id: generateUniqueId(), - name: `Template ${Date.now()}`, - panelOrder: selectedZone.panelOrder, - widgets: selectedZone.widgets, - snapshot, - }; - console.log('template: ', template); - addTemplate(template); - } catch (error) { - console.error('Failed to save template:', error); + try { + // Check if the selected zone has any widgets + if (!selectedZone.widgets || selectedZone.widgets.length === 0) { + console.warn("Cannot save an empty template."); + return; } + + // Check if the template already exists + const isDuplicate = templates.some((template) => { + const isSamePanelOrder = + JSON.stringify(template.panelOrder) === + JSON.stringify(selectedZone.panelOrder); + const isSameWidgets = + JSON.stringify(template.widgets) === + JSON.stringify(selectedZone.widgets); + return isSamePanelOrder && isSameWidgets; + }); + + if (isDuplicate) { + console.warn("This template already exists."); + return; + } + + // Capture visualization snapshot + const snapshot = await captureVisualization(); + if (!snapshot) { + console.error("Failed to capture visualization snapshot."); + return; + } + + // Create a new template + const newTemplate: Template = { + id: generateUniqueId(), + name: `Template ${Date.now()}`, + panelOrder: selectedZone.panelOrder, + widgets: selectedZone.widgets, + snapshot, + }; + + console.log("Saving template:", newTemplate); + + // Save the template + try { + addTemplate(newTemplate); + } catch (error) { + console.error("Failed to add template:", error); + } + } catch (error) { + console.error("Failed to save template:", error); + } }; diff --git a/app/src/styles/abstracts/variables.scss b/app/src/styles/abstracts/variables.scss index eaab86b..b5244a6 100644 --- a/app/src/styles/abstracts/variables.scss +++ b/app/src/styles/abstracts/variables.scss @@ -35,7 +35,7 @@ $highlight-accent-color-dark: #403e6a; // Highlighted accent for dark mode $background-color: #fcfdfd; // Main background color $background-color-dark: #19191d; // Main background color for dark mode $background-color-secondary: #e1e0ff80; // Secondary background color -$background-color-secondary-dark: #1f1f2399; // Secondary background color for dark mode +$background-color-secondary-dark: #39394f99; // Secondary background color for dark mode // Border colors $border-color: #e0dfff; // Default border color diff --git a/app/src/styles/components/_regularDropDown.scss b/app/src/styles/components/_regularDropDown.scss index 085a5cd..86ac078 100644 --- a/app/src/styles/components/_regularDropDown.scss +++ b/app/src/styles/components/_regularDropDown.scss @@ -36,9 +36,11 @@ overflow-y: auto; // Optional: Enable scrolling if content exceeds height left: 0; top: 104%; + .option { padding: 5px; cursor: pointer; + flex-direction: row !important; &:hover { background-color: var(--primary-color); // Optional: Hover effect diff --git a/app/src/styles/components/visualization/floating/energyConsumed.scss b/app/src/styles/components/visualization/floating/energyConsumed.scss new file mode 100644 index 0000000..daec86c --- /dev/null +++ b/app/src/styles/components/visualization/floating/energyConsumed.scss @@ -0,0 +1,110 @@ +@use "../../../abstracts/variables" as *; +@use "../../../abstracts/mixins" as *; + +.floatingWidgets-wrapper { + display: flex; + flex-direction: column; + gap: 6px; + padding-top: 12px; + padding: 6px; + + .floating { + + min-height: 170px; + background: var(--background-color); + border: 1.23px solid var(--border-color); + box-shadow: 0px 4.91px 4.91px 0px #0000001c; + border-radius: $border-radius-medium; + padding: 12px 6px; + } + + .working-state { + display: flex; + flex-direction: column; + gap: 6px; + + .state-working-top { + display: flex; + } + } +} + + + +.floatingWidgets-wrapper { + font-family: Arial, sans-serif; + color: #333; +} + +.floating.working-state { + width: 100%; + height: 283px; + background: #f5f5f5; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 20px; + box-sizing: border-box; + +} + +.state-working-top { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + // flex-direction: column; +} + +.state { + font-size: 24px; + font-weight: bold; +} + +.working-status { + display: flex; + align-items: center; + gap: 8px; +} + +.working { + font-size: 20px; + color: #4CAF50; +} + +.dot { + display: inline-block; + width: 10px; + height: 10px; + background: #4CAF50; + border-radius: 50%; +} + +.img img { + width: 150px; + height: 100px; + border-radius: 4px; + object-fit: cover; +} + +.state-working-data { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 15px; +} + +.data-row { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 16px; + padding: 4px 0; +} + +.data-key { + color: #666; +} + +.data-value { + font-weight: bold; + color: #333; +} \ No newline at end of file diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index 0daeca3..54ebf0d 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -78,14 +78,14 @@ display: flex; flex-direction: column; gap: 8px; - padding-right: 6px; + padding: 6px; flex-wrap: nowrap; overflow: auto; .chart { min-height: 170px; - background: var(--background-primary, #fcfdfd); - border: 1.23px solid var(--Grays-Gray-5, #e5e5ea); + background: var(--background-color); + border: 1.23px solid var(--border-color); box-shadow: 0px 4.91px 4.91px 0px #0000001c; border-radius: $border-radius-medium; padding: 12px 6px; @@ -107,7 +107,7 @@ .stock { padding: 13px 5px; - background-color: #e0dfff80; + background-color: var(--background-color-secondary); border-radius: 6.33px; display: flex; justify-content: space-between; @@ -291,8 +291,46 @@ gap: 12px; padding: 10px 12px; + .datas { + display: flex; + align-items: center; + justify-content: space-between; + + .datas__class { + display: flex; + align-items: center; + + .multi-level-dropdown { + + min-width: 100px; + + .dropdown-button { + display: flex; + justify-content: space-between; + gap: 6px; + } + } + } + + .datas__class { + display: flex; + gap: 12px; + + .datas__separator {} + + .disable { + cursor: not-allowed; + pointer-events: none; + /* Disables all mouse interactions */ + opacity: 0.5; + /* Optional: Makes the button look visually disabled */ + } + + } + } + .sideBarHeader { - color: #5c87df; + color: var(--accent-color); border-bottom: 1px solid var(--border-color); padding-bottom: 6px; } @@ -388,7 +426,7 @@ width: 100%; height: 150px; background: #f0f0f0; - border-radius: 8px; + // border-radius: 8px; } .optionsContainer { @@ -523,93 +561,119 @@ } } -/* Base styles */ + + + + + + + + + + + + .multi-level-dropdown { position: relative; display: inline-block; - text-align: left; .dropdown-button { - background-color: #3b82f6; /* Blue background */ - color: white; - padding: 0.5rem 1rem; - font-size: 0.875rem; - border: none; - border-radius: 0.375rem; + width: 100%; + background-color: var(--background-color) !important; + border: 1px solid var(--border-color) !important; + padding: 5px 10px; + + + // font-size: 12px; cursor: pointer; - display: flex; - align-items: center; - transition: background-color 0.2s ease; + border-radius: 5px; + transition: background-color 0.3s ease; &:hover { - background-color: #2563eb; /* Darker blue on hover */ + background-color: #333333; } - .icon { - margin-left: 0.5rem; + &.open { + background-color: #333333; } } .dropdown-menu { position: absolute; - top: calc(100% + 0.5rem); /* Add spacing below the button */ + top: 100%; left: 0; - width: 12rem; - background-color: white; - border: 1px solid #e5e7eb; /* Light gray border */ - border-radius: 0.375rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */ - z-index: 10; - } -} + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 5px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 1000; + min-width: 200px; + overflow: auto; + max-height: 600px; -/* Dropdown Item */ -.dropdown-item { - display: block; - padding: 0.5rem 1rem; - font-size: 0.875rem; - color: #4b5563; /* Gray text */ - text-decoration: none; - cursor: pointer; - transition: background-color 0.2s ease; + .dropdown-content { + display: flex; + flex-direction: column; + gap: 6px; - &:hover { - background-color: #f3f4f6; /* Light gray background on hover */ - } -} + .nested-dropdown { + // &:first-child{ + margin-left: 0; + // } + } -/* Nested Dropdown */ -.nested-dropdown { - position: relative; - - .dropdown-trigger { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.5rem 1rem; - font-size: 0.875rem; - color: #4b5563; /* Gray text */ - cursor: pointer; - transition: background-color 0.2s ease; - - &:hover { - background-color: #f3f4f6; /* Light gray background on hover */ + padding: 10px; } - .icon { - margin-left: 0.5rem; + .dropdown-item { + display: block; + padding: 5px 10px; + text-decoration: none; + color: #000000; + font-size: 14px; + cursor: pointer; + transition: background-color 0.3s ease; + + &:hover { + background-color: #f0f0f0; + } + } + + .nested-dropdown { + margin-left: 20px; + + .dropdown-trigger { + display: flex; + align-items: center; + justify-content: space-between; + padding: 5px 10px; + cursor: pointer; + font-size: 14px; + color: #000000; + transition: background-color 0.3s ease; + + &:hover { + background-color: #f0f0f0; + } + + &.open { + background-color: #e0e0e0; + } + + .icon { + font-size: 12px; + margin-left: 5px; + } + } + + .submenu { + margin-top: 5px; + padding-left: 20px; + border-left: 2px solid #cccccc; + display: flex; + flex-direction: column; + gap: 6px; + } } } - - .submenu { - position: absolute; - top: 0; - left: 100%; /* Position submenu to the right */ - width: 12rem; - background-color: white; - border: 1px solid #e5e7eb; /* Light gray border */ - border-radius: 0.375rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */ - z-index: 20; - } -} +} \ No newline at end of file diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index 68688f6..90f6419 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -19,6 +19,7 @@ @use 'components/templates'; @use 'components/tools'; @use 'components/regularDropDown'; +@use 'components/visualization/floating/energyConsumed'; // layout @use 'layout/sidebar'; diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 828cc7b..a7c1181 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -4,7 +4,7 @@ .realTime-viz { background-color: var(--background-color); border-radius: 20px; - box-shadow: 0px 4px 8px rgba(60, 60, 67, 0.1019607843); + box-shadow: $box-shadow-medium; width: calc(100% - (320px + 270px + 80px)); height: 600px; position: absolute; @@ -30,7 +30,7 @@ .zoon-wrapper { display: flex; - background-color: #e0dfff80; + background-color: var(--background-color); position: absolute; bottom: 10px; left: 50%; @@ -40,14 +40,14 @@ border-radius: 8px; max-width: 80%; overflow: auto; - + max-width: calc(100% - 450px); &::-webkit-scrollbar { display: none; } .zone { width: auto; - background-color: #fcfdfd; + background-color: var(--background-color); border-radius: 6px; padding: 4px 8px; white-space: nowrap; @@ -83,43 +83,6 @@ } } - @media (max-width: 768px) { - width: 90%; // Take even more width on very small screens - height: 400px; // Further reduce height - top: 45%; // Adjust vertical position slightly upward - - .panel { - &.top-panel, - &.bottom-panel { - .panel-content { - flex-direction: column; // Stack panels vertically on small screens - - .chart-container { - width: 100%; // Make charts full width - height: 150px; // Reduce chart height - } - } - } - } - } - - @media (max-width: 480px) { - width: 95%; // Take almost full width on very small devices - height: 350px; // Further reduce height - top: 40%; // Move slightly higher for better visibility - - .side-button-container { - flex-direction: row !important; // Force buttons into a row - gap: 4px; // Reduce spacing between buttons - - &.top, - &.bottom { - left: 50%; // Center horizontally - transform: translateX(-50%); - } - } - } - .content-container { display: flex; height: 100vh; @@ -189,6 +152,7 @@ display: flex; flex-direction: column; gap: 10px; + background-color: var(--background-color); &::-webkit-scrollbar { display: none; @@ -216,18 +180,23 @@ } } + &.top-panel, &.bottom-panel { left: 0; right: 0; + .fullScreen { + background-color: red; + } + .panel-content { display: flex; flex-direction: row; .chart-container { height: 100%; - width: 230px; + width: 200px; } } } @@ -247,7 +216,7 @@ .chart-container { width: 100%; - height: 200px; + height: 180px; } } @@ -275,6 +244,15 @@ .icon { display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border-radius: 4px; + } + + .active { + background-color: var(--accent-color); } &:hover { @@ -409,4 +387,4 @@ } } } -} +} \ No newline at end of file -- 2.49.1 From f6bde4f1184768617083fbe504312f633a59c4f8 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Thu, 20 Mar 2025 17:54:57 +0530 Subject: [PATCH 6/7] ui->merge->main --- .../layout/sidebarLeft/visualization/widgets/Widgets.tsx | 2 +- app/src/store/useModuleStore.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx index a96ff83..6f21667 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx @@ -5,7 +5,7 @@ import Widgets3D from "./Widgets3D"; import WidgetsFloating from "./WidgetsFloating"; const Widgets = () => { - const [activeOption, setActiveOption] = useState("Floating"); + const [activeOption, setActiveOption] = useState("2D"); const handleToggleClick = (option: string) => { setActiveOption(option); diff --git a/app/src/store/useModuleStore.ts b/app/src/store/useModuleStore.ts index 6d26a8e..c7e1e98 100644 --- a/app/src/store/useModuleStore.ts +++ b/app/src/store/useModuleStore.ts @@ -7,7 +7,7 @@ interface ModuleStore { } const useModuleStore = create((set) => ({ - activeModule: "visualization", // Initial state + activeModule: "builder", // Initial state setActiveModule: (module) => set({ activeModule: module }), // Update state })); -- 2.49.1 From 86409644e1d8c058a331d9abe30cde1b9467446c Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Fri, 21 Mar 2025 10:34:01 +0530 Subject: [PATCH 7/7] resolved build errors --- .../sidebarLeft/visualization/Templates.tsx | 1 - .../visualization/widgets/ChartComponent.tsx | 20 +- .../visualization/widgets/Widgets3D.tsx | 1 - .../visualization/widgets/WidgetsFloating.tsx | 198 ++------------ .../mechanics/MachineMechanics.tsx | 246 +++++++++--------- .../visualization/Visualization.tsx | 1 - .../sidebarRight/visualization/data/Data.tsx | 2 +- .../visualization/design/Design.tsx | 12 +- .../ui/charts/BarGraphComponent.tsx | 4 +- .../ui/charts/LineGraphComponent.tsx | 6 +- .../ui/charts/PieGraphComponent.tsx | 3 +- .../components/ui/componets/DisplayZone.tsx | 3 +- .../ui/componets/DraggableWidget.tsx | 2 - .../ui/componets/RealTimeVisulization.tsx | 1 + .../ui/inputs/InputWithDropDown.tsx | 53 ++++ .../components/ui/inputs/LabledDropdown.tsx | 28 ++ .../ui/inputs/MultiLevelDropDown.tsx | 12 +- app/src/functions/handleResizePannel.ts | 24 ++ 18 files changed, 296 insertions(+), 321 deletions(-) create mode 100644 app/src/components/ui/inputs/InputWithDropDown.tsx create mode 100644 app/src/components/ui/inputs/LabledDropdown.tsx create mode 100644 app/src/functions/handleResizePannel.ts diff --git a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx index 4528c06..5c47a6f 100644 --- a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx @@ -1,4 +1,3 @@ -import React from "react"; import useTemplateStore from "../../../../store/useTemplateStore"; import { useSelectedZoneStore } from "../../../../store/useZoneStore"; diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx index 39da08d..e716668 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useMemo } from "react"; import { Chart } from "chart.js/auto"; -import { useThemeStore } from "../../../../../store/useThemeStore"; +// import { useThemeStore } from "../../../../../store/useThemeStore"; // Define Props Interface interface ChartComponentProps { @@ -29,17 +29,17 @@ const ChartComponent = ({ data: propsData, }: ChartComponentProps) => { const canvasRef = useRef(null); - const { themeColor } = useThemeStore(); + // const { themeColor } = useThemeStore(); // Memoize Theme Colors to Prevent Unnecessary Recalculations - const buttonActionColor = useMemo( - () => themeColor[0] || "#5c87df", - [themeColor] - ); - const buttonAbortColor = useMemo( - () => themeColor[1] || "#ffffff", - [themeColor] - ); + // const buttonActionColor = useMemo( + // () => themeColor[0] || "#5c87df", + // [themeColor] + // ); + // const buttonAbortColor = useMemo( + // () => themeColor[1] || "#ffffff", + // [themeColor] + // ); // Memoize Font Weight Mapping const chartFontWeightMap = useMemo( diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx index 9a14410..7fc6bba 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets3D.tsx @@ -1,4 +1,3 @@ -import React from 'react' const Widgets3D = () => { return ( diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx index 9247508..3cde77b 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx @@ -1,191 +1,41 @@ -import React, { - useState, - DragEvent, - MouseEvent, - useRef, - useEffect, -} from "react"; +import React, { useState } from "react"; -const WidgetsFloating: React.FC = () => { - const stateWorking = [ - { "Oil Tank": "24/341" }, - { "Oil Refin": "36.023" }, - { Transmission: "36.023" }, - { Fuel: "36732" }, - { Power: "1300" }, - { Time: "13-9-2023" }, - ]; +interface Widget { + id: string; + name: string; +} - // State for storing the dragged widget and its position - const [draggedFloating, setDraggedFloating] = useState(null); - const [position, setPosition] = useState<{ x: number; y: number }>({ - x: 0, - y: 0, - }); - - // State to store all placed widgets with their positions - const [placedWidgets, setPlacedWidgets] = useState< - { name: string; x: number; y: number }[] - >([]); - - const canvasRef = useRef(null); - - // Handle the drag start event +const WidgetsFloating = () => { + const [widgets, setWidgets] = useState([ + { id: "1", name: "Working State Widget" }, + { id: "2", name: "Floating Widget 2" }, + { id: "3", name: "Floating Widget 3" }, + { id: "4", name: "Floating Widget 4" }, + ]); + + console.log('setWidgets: ', setWidgets); + // Function to handle drag start const handleDragStart = ( - event: DragEvent, - widget: string + e: React.DragEvent, + widget: Widget ) => { - setDraggedFloating(widget); - // Initialize position to the current position when drag starts - setPosition({ x: event.clientX, y: event.clientY }); + e.dataTransfer.setData("application/json", JSON.stringify(widget)); }; - // Handle the drag move event - const handleDragMove = (event: MouseEvent) => { - if (!draggedFloating) return; - - // Calculate new position and update it - const canvas = canvasRef.current; - if (canvas) { - const canvasRect = canvas.getBoundingClientRect(); - const newX = event.clientX - canvasRect.left; - const newY = event.clientY - canvasRect.top; - setPosition({ x: newX, y: newY }); - } - }; - - // Handle the drag end event - const handleDragEnd = () => { - if (draggedFloating) { - // Store the final position of the dragged widget - setPlacedWidgets((prevWidgets) => [ - ...prevWidgets, - { name: draggedFloating, x: position.x, y: position.y }, - ]); - // Reset the dragged floating widget after dragging is completed - setDraggedFloating(null); - } - }; - - useEffect(() => { - console.log("position: ", position); - console.log("draggedFloating: ", draggedFloating); - }, [draggedFloating, position]); - return ( -
- {/* The floating widget that's being dragged */} - {draggedFloating && ( +
+ {widgets.map((widget) => (
- {draggedFloating} -
- )} - - {/* Render all placed widgets */} - {placedWidgets.map((widget, index) => ( -
handleDragStart(e, widget)} > {widget.name}
))} - - {/* The rest of your floating widgets */} -
handleDragStart(e, "working-state")} - style={{ position: "absolute", top: "50px", left: "50px" }} - > -
-
-
State
-
- Working - -
-
-
- Factory -
-
-
- {stateWorking.map((state, index) => { - const key = Object.keys(state)[0]; - const value = state[key]; - return ( -
- {key}: - {value} -
- ); - })} -
-
- - {/* Other floating widgets */} -
handleDragStart(e, "floating-2")} - style={{ position: "absolute", top: "120px", left: "150px" }} - > - floating-2 -
-
handleDragStart(e, "floating-3")} - style={{ position: "absolute", top: "200px", left: "250px" }} - > - floating-3 -
-
handleDragStart(e, "floating-4")} - style={{ position: "absolute", top: "300px", left: "350px" }} - > - floating-4 -
); }; -export default WidgetsFloating; \ No newline at end of file +export default WidgetsFloating; diff --git a/app/src/components/layout/sidebarRight/mechanics/MachineMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/MachineMechanics.tsx index a240de8..803c96a 100644 --- a/app/src/components/layout/sidebarRight/mechanics/MachineMechanics.tsx +++ b/app/src/components/layout/sidebarRight/mechanics/MachineMechanics.tsx @@ -1,10 +1,15 @@ -import React, { useState } from "react"; +import React, { useRef, useState } from "react"; import { AddIcon, InfoIcon, RemoveIcon, ResizeHeightIcon, } from "../../../icons/ExportCommonIcons"; +import RenameInput from "../../../ui/inputs/RenameInput"; +import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; +import LabledDropdown from "../../../ui/inputs/LabledDropdown"; +import RegularDropDown from "../../../ui/inputs/RegularDropDown"; +import { handleResize } from "../../../../functions/handleResizePannel"; const MachineMechanics: React.FC = () => { const [actionList, setActionList] = useState([]); @@ -13,7 +18,9 @@ const MachineMechanics: React.FC = () => { type: "action" | "trigger"; name: string; } | null>(null); - const [editedName, setEditedName] = useState(""); + + const actionsContainerRef = useRef(null); + const triggersContainerRef = useRef(null); const handleAddAction = () => { setActionList([...actionList, `Action ${actionList.length + 1}`]); @@ -30,7 +37,6 @@ const MachineMechanics: React.FC = () => { selectedItem.name === actionList[index] ) { setSelectedItem(null); - setEditedName(""); } }; @@ -41,133 +47,135 @@ const MachineMechanics: React.FC = () => { selectedItem.name === triggerList[index] ) { setSelectedItem(null); - setEditedName(""); } }; const handleSelectItem = (type: "action" | "trigger", name: string) => { setSelectedItem({ type, name }); - setEditedName(name); - }; - - const handleSave = () => { - if (!selectedItem) return; - - if (selectedItem.type === "action") { - setActionList( - actionList.map((action) => - action === selectedItem.name ? editedName : action - ) - ); - } else if (selectedItem.type === "trigger") { - setTriggerList( - triggerList.map((trigger) => - trigger === selectedItem.name ? editedName : trigger - ) - ); - } - - setSelectedItem({ ...selectedItem, name: editedName }); }; return (
-
-
-
Actions
-
- Add -
-
-
-
- {actionList.map((action, index) => ( -
-
handleSelectItem("action", action)}> - {action} -
-
handleRemoveAction(index)} - > - -
-
- ))} -
-
- -
-
+
Selected Object
+
+
Process:
+ {}} + />
-
-
-
Triggers
-
- Add -
-
-
-
- {triggerList.map((trigger, index) => ( -
-
handleSelectItem("trigger", trigger)}> - {trigger} -
-
handleRemoveTrigger(index)} - > - -
-
- ))} -
-
- -
-
-
-
- {selectedItem && ( - <> -
- - setEditedName(e.target.value)} - /> +
+
+
+
Actions
+
+ Add
- {/* Add other Properties Like: - * Object Selection Dropdown - * Buffer Time - * Get Value From Object - * Action - * etc. - */} -
Update
{/* remove this */} - - )} -
-
- - By Selecting Path, you can create Object Triggers. +
+
+
+ {actionList.map((action, index) => ( +
+
handleSelectItem("action", action)} + > + +
+
handleRemoveAction(index)} + > + +
+
+ ))} +
+
handleResize(e, actionsContainerRef)} + > + +
+
+
+
+
+
Triggers
+
+ Add +
+
+
+
+ {triggerList.map((trigger, index) => ( +
+
handleSelectItem("trigger", trigger)} + > + +
+
handleRemoveTrigger(index)} + > + +
+
+ ))} +
+
handleResize(e, triggersContainerRef)} + > + +
+
+
+
+ {selectedItem && ( + <> +
{selectedItem.name}
+ + + + )} +
+
+ + By Selecting Path, you can create Object Triggers. +
); diff --git a/app/src/components/layout/sidebarRight/visualization/Visualization.tsx b/app/src/components/layout/sidebarRight/visualization/Visualization.tsx index c29aef2..f47cc27 100644 --- a/app/src/components/layout/sidebarRight/visualization/Visualization.tsx +++ b/app/src/components/layout/sidebarRight/visualization/Visualization.tsx @@ -1,5 +1,4 @@ import { useState } from "react"; -import Search from "../../../ui/inputs/Search"; import ToggleHeader from "../../../ui/inputs/ToggleHeader"; import Data from "./data/Data"; import Design from "./design/Design"; diff --git a/app/src/components/layout/sidebarRight/visualization/data/Data.tsx b/app/src/components/layout/sidebarRight/visualization/data/Data.tsx index e08c4d9..cd20aea 100644 --- a/app/src/components/layout/sidebarRight/visualization/data/Data.tsx +++ b/app/src/components/layout/sidebarRight/visualization/data/Data.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; import { AddIcon, RemoveIcon } from "../../../../icons/ExportCommonIcons"; import MultiLevelDropDown from "../../../../ui/inputs/MultiLevelDropDown"; diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx index 93332dd..89e1682 100644 --- a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx +++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import { useState } from "react"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent"; import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; @@ -25,10 +25,20 @@ interface Widget { const Design = () => { const [selectedName, setSelectedName] = useState("drop down"); + console.log("selectedName: ", selectedName); + const [selectedElement, setSelectedElement] = useState("drop down"); + console.log("selectedElement: ", selectedElement); + const [selectedFont, setSelectedFont] = useState("drop down"); + console.log("selectedFont: ", selectedFont); + const [selectedSize, setSelectedSize] = useState("drop down"); + console.log("selectedSize: ", selectedSize); + const [selectedWeight, setSelectedWeight] = useState("drop down"); + console.log("selectedWeight: ", selectedWeight); + const [elementColor, setElementColor] = useState("#6f42c1"); // Default color for elements const [showColorPicker, setShowColorPicker] = useState(false); // Manage visibility of the color picker diff --git a/app/src/components/ui/charts/BarGraphComponent.tsx b/app/src/components/ui/charts/BarGraphComponent.tsx index 466d4df..9a07473 100644 --- a/app/src/components/ui/charts/BarGraphComponent.tsx +++ b/app/src/components/ui/charts/BarGraphComponent.tsx @@ -1,6 +1,6 @@ -import { useRef, useMemo } from "react"; +import { useMemo } from "react"; -import { Bar, Line } from "react-chartjs-2"; +import { Bar } from "react-chartjs-2"; interface ChartComponentProps { type: any; diff --git a/app/src/components/ui/charts/LineGraphComponent.tsx b/app/src/components/ui/charts/LineGraphComponent.tsx index 98b9d61..cf1a47f 100644 --- a/app/src/components/ui/charts/LineGraphComponent.tsx +++ b/app/src/components/ui/charts/LineGraphComponent.tsx @@ -1,4 +1,4 @@ -import { useRef, useMemo } from "react"; +import { useMemo } from "react"; import { Line } from "react-chartjs-2"; interface ChartComponentProps { @@ -79,8 +79,8 @@ const LineGraphComponent = ({ { label: "My First Dataset", data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple) - borderColor: "#ffffff", // Keeping border color white + backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple) + borderColor: "#ffffff", // Keeping border color white borderWidth: 2, fill: false, }, diff --git a/app/src/components/ui/charts/PieGraphComponent.tsx b/app/src/components/ui/charts/PieGraphComponent.tsx index fb553fb..b52ee30 100644 --- a/app/src/components/ui/charts/PieGraphComponent.tsx +++ b/app/src/components/ui/charts/PieGraphComponent.tsx @@ -1,4 +1,4 @@ -import { useRef, useMemo } from "react"; +import { useMemo } from "react"; import { Pie } from "react-chartjs-2"; interface ChartComponentProps { @@ -53,6 +53,7 @@ const PieChartComponent = ({ .getPropertyValue("--accent-color") .trim(); + console.log("accentColor: ", accentColor); const options = useMemo( () => ({ responsive: true, diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index b5fe0f0..3bf6b8f 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useRef } from "react"; -import { useSelectedZoneStore } from "../../../store/useZoneStore"; import { Widget } from "../../../store/useWidgetStore"; // Define the type for `Side` @@ -56,7 +55,9 @@ const DisplayZone: React.FC = ({ const [selectedOption, setSelectedOption] = React.useState( null ); + console.log('setSelectedOption: ', setSelectedOption); const [options, setOptions] = React.useState([]); + console.log('setOptions: ', setOptions); // Scroll to the selected option when it changes useEffect(() => { diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index 6769890..0f13266 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -1,5 +1,3 @@ -import { useMemo, useState } from "react"; -import ChartComponent from "../../layout/sidebarLeft/visualization/widgets/ChartComponent"; import { useWidgetStore } from "../../../store/useWidgetStore"; import PieGraphComponent from "../charts/PieGraphComponent"; import BarGraphComponent from "../charts/BarGraphComponent"; diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 6daa075..7905769 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -100,3 +100,4 @@ const RealTimeVisulization: React.FC = () => { }; export default RealTimeVisulization; + diff --git a/app/src/components/ui/inputs/InputWithDropDown.tsx b/app/src/components/ui/inputs/InputWithDropDown.tsx new file mode 100644 index 0000000..ca51112 --- /dev/null +++ b/app/src/components/ui/inputs/InputWithDropDown.tsx @@ -0,0 +1,53 @@ +import React, { useState } from "react"; + +type InputWithDropDownProps = { + label: string; + value: string; + options?: string[]; // Array of dropdown options + activeOption?: string; // The currently active dropdown option +}; + +const InputWithDropDown: React.FC = ({ + label, + value, + options, + activeOption, +}) => { + const separatedWords = label + .split(/(?=[A-Z])/) + .map((word) => word.trim()) + .toString(); + + const [openDropdown, setOpenDropdown] = useState(false); + + return ( +
+ +
+ + +
{ + setOpenDropdown(true); + }} + > +
{activeOption}
+ {options && openDropdown && ( +
+ {options.map((option, index) => ( +
+ {option} +
+ ))} +
+ )} +
+
+
+ ); +}; + +export default InputWithDropDown; diff --git a/app/src/components/ui/inputs/LabledDropdown.tsx b/app/src/components/ui/inputs/LabledDropdown.tsx new file mode 100644 index 0000000..1d35d85 --- /dev/null +++ b/app/src/components/ui/inputs/LabledDropdown.tsx @@ -0,0 +1,28 @@ +import React, { useState } from "react"; +import RegularDropDown from "./RegularDropDown"; + +type LabledDropdownProps = { + defaultOption: string; // Initial active option + options: string[]; // Array of dropdown options +}; + +const LabledDropdown: React.FC = ({ defaultOption, options }) => { + const [activeOption, setActiveOption] = useState(defaultOption); // State for active option + + const handleSelect = (option: string) => { + setActiveOption(option); // Update the active option state + }; + + return ( +
+
Type
+ +
+ ); +}; + +export default LabledDropdown; diff --git a/app/src/components/ui/inputs/MultiLevelDropDown.tsx b/app/src/components/ui/inputs/MultiLevelDropDown.tsx index 477111a..8ea8d89 100644 --- a/app/src/components/ui/inputs/MultiLevelDropDown.tsx +++ b/app/src/components/ui/inputs/MultiLevelDropDown.tsx @@ -47,9 +47,13 @@ const NestedDropdown = ({ {/* Submenu */} {open && (
- {React.Children.map(children, (child) => - React.cloneElement(child as React.ReactElement, { onSelect }) - )} + {React.Children.map(children, (child) => { + if (React.isValidElement(child)) { + // Clone the element and pass the `onSelect` prop only if it's expected + return React.cloneElement(child as React.ReactElement, { onSelect }); + } + return child; // Return non-element children as-is + })}
)}
@@ -134,4 +138,4 @@ const MultiLevelDropdown = ({ data }: { data: Record }) => { ); }; -export default MultiLevelDropdown; +export default MultiLevelDropdown; \ No newline at end of file diff --git a/app/src/functions/handleResizePannel.ts b/app/src/functions/handleResizePannel.ts new file mode 100644 index 0000000..235e97d --- /dev/null +++ b/app/src/functions/handleResizePannel.ts @@ -0,0 +1,24 @@ +export const handleResize = ( + e: React.MouseEvent, + containerRef: React.RefObject +) => { + if (!containerRef.current) return; // Ensure containerRef is not null + const startY = e.clientY; + const startHeight = containerRef.current.offsetHeight; + + const onMouseMove = (moveEvent: MouseEvent) => { + const newHeight = Math.max( + 120, + Math.min(400, startHeight + moveEvent.clientY - startY) + ); + containerRef.current!.style.height = `${newHeight}px`; + }; + + const onMouseUp = () => { + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + }; + + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); +}; -- 2.49.1