diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index dd170f8..d2c9970 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -511,3 +511,99 @@ export function AI_Icon() { ); } + +export const KebabIcon = () => { + return ( + + + + + + ); +}; + +export const DublicateIcon = () => { + return ( + + + + ); +}; + +export const DeleteIcon = () => { + return ( + + + + + + + + ); +}; diff --git a/app/src/components/icons/SimulationIcons.tsx b/app/src/components/icons/SimulationIcons.tsx index 5bc3296..28ce559 100644 --- a/app/src/components/icons/SimulationIcons.tsx +++ b/app/src/components/icons/SimulationIcons.tsx @@ -93,3 +93,114 @@ export function SimulationIcon({ isActive }: { isActive: boolean }) { ); } + +// simulation player icons + +export function ResetIcon() { + return ( + + + + + + ); +} + +export function PlayStopIcon() { + return ( + + + + ); +} + +export function ExitIcon() { + return ( + + + + ); +} + +export function MoveArrowRight() { + return ( + + + + ); +} + +export function MoveArrowLeft() { + return ( + + + + ); +} diff --git a/app/src/components/layout/sidebarRight/visualization/Visualization.tsx b/app/src/components/layout/sidebarRight/visualization/Visualization.tsx index ec4d3ad..c2ac477 100644 --- a/app/src/components/layout/sidebarRight/visualization/Visualization.tsx +++ b/app/src/components/layout/sidebarRight/visualization/Visualization.tsx @@ -13,7 +13,7 @@ const Visualization = () => { return (
diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 9d06df9..abe7ba8 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -285,9 +285,13 @@ const Tools: React.FC = () => {
) : ( -
setIsPlaying(false)}> - X -
+ <> + {activeModule !== "simulation" && ( +
setIsPlaying(false)}> + X +
+ )} + )} ); diff --git a/app/src/components/ui/charts/PieGraphComponent.tsx b/app/src/components/ui/charts/PieGraphComponent.tsx index 0066ec3..7c8bb1d 100644 --- a/app/src/components/ui/charts/PieGraphComponent.tsx +++ b/app/src/components/ui/charts/PieGraphComponent.tsx @@ -53,7 +53,6 @@ const PieChartComponent = ({ .getPropertyValue("--accent-color") .trim(); - console.log("accentColor: ", accentColor); const options = useMemo( () => ({ responsive: true, diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/components/ui/componets/AddButtons.tsx index d7ed21d..7185fc6 100644 --- a/app/src/components/ui/componets/AddButtons.tsx +++ b/app/src/components/ui/componets/AddButtons.tsx @@ -1,211 +1,220 @@ -import React from "react"; -import { - CleanPannel, - EyeIcon, - LockIcon, -} from "../../icons/RealTimeVisulationIcons"; -import { panelData } from "../../../services/realTimeVisulization/zoneData/panel"; - -// 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; - activeSides: Side[]; - panelOrder: Side[]; - lockedPanels: Side[]; - zoneId: string; - zoneViewPortTarget: number[]; - zoneViewPortPosition: number[] - widgets: { - id: string; - type: string; - title: string; - panel: Side; - data: any; - }[]; - }; - setSelectedZone: React.Dispatch< - React.SetStateAction<{ - zoneName: string; - activeSides: Side[]; - panelOrder: Side[]; - lockedPanels: Side[]; - zoneId: string; - zoneViewPortTarget: number[]; - zoneViewPortPosition: number[] - widgets: { - id: string; - type: string; - title: string; - panel: Side; - data: any; - }[]; - }> - >; - 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) - ? 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 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 - 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, - }; - - // Delete the selectedZone state - console.log('updatedZone: ', updatedZone); - setSelectedZone(updatedZone); - } else { - // If the panel is not active, activate it - const newActiveSides = [...selectedZone.activeSides, side]; - - const updatedZone = { - ...selectedZone, - activeSides: newActiveSides, - panelOrder: newActiveSides, - }; - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; - // let response = panelData(organization, selectedZone.zoneId, newActiveSides) - // console.log('response: ', response); - - // Update the selectedZone state - console.log("updatedZone: ", updatedZone); - setSelectedZone(updatedZone); - } - }; - - return ( - <> -
- {(["top", "right", "bottom", "left"] as Side[]).map((side) => ( -
- {/* "+" Button */} - - - {/* Extra Buttons */} - {selectedZone.activeSides.includes(side) && ( -
- {/* Hide Panel */} -
toggleVisibility(side)} - > - -
- - {/* Clean Panel */} -
cleanPanel(side)} - > - -
- - {/* Lock/Unlock Panel */} -
toggleLockPanel(side)} - > - -
-
- )} -
- ))} -
- - ); -}; - -export default AddButtons; +import React from "react"; +import { + CleanPannel, + EyeIcon, + LockIcon, +} from "../../icons/RealTimeVisulationIcons"; +import { panelData } from "../../../services/realTimeVisulization/zoneData/panel"; +import { AddIcon } from "../../icons/ExportCommonIcons"; + +// 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; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + zoneId: string; + zoneViewPortTarget: number[]; + zoneViewPortPosition: number[]; + widgets: { + id: string; + type: string; + title: string; + panel: Side; + data: any; + }[]; + }; + setSelectedZone: React.Dispatch< + React.SetStateAction<{ + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + zoneId: string; + zoneViewPortTarget: number[]; + zoneViewPortPosition: number[]; + widgets: { + id: string; + type: string; + title: string; + panel: Side; + data: any; + }[]; + }> + >; + 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) + ? 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 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 + 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, + }; + + // Delete the selectedZone state + console.log("updatedZone: ", updatedZone); + setSelectedZone(updatedZone); + } else { + // If the panel is not active, activate it + const newActiveSides = [...selectedZone.activeSides, side]; + + const updatedZone = { + ...selectedZone, + activeSides: newActiveSides, + panelOrder: newActiveSides, + }; + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; + // let response = panelData(organization, selectedZone.zoneId, newActiveSides) + // console.log('response: ', response); + + // Update the selectedZone state + console.log("updatedZone: ", updatedZone); + setSelectedZone(updatedZone); + } + }; + + return ( + <> +
+ {(["top", "right", "bottom", "left"] as Side[]).map((side) => ( +
+ {/* "+" Button */} + + + {/* Extra Buttons */} + {selectedZone.activeSides.includes(side) && ( +
+ {/* Hide Panel */} +
toggleVisibility(side)} + > + +
+ + {/* Clean Panel */} +
cleanPanel(side)} + > + +
+ + {/* Lock/Unlock Panel */} +
toggleLockPanel(side)} + > + +
+
+ )} +
+ ))} +
+ + ); +}; + +export default AddButtons; diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index 56c67bb..03dbedc 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -1,5 +1,7 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState, useCallback } from "react"; import { Widget } from "../../../store/useWidgetStore"; +import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons"; +import { InfoIcon } from "../../icons/ExportCommonIcons"; // Define the type for `Side` type Side = "top" | "bottom" | "left" | "right"; @@ -60,138 +62,135 @@ const DisplayZone: React.FC = ({ // 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 - ); - // console.log('setSelectedOption: ', setSelectedOption); - const [options, setOptions] = React.useState([]); - // console.log('setOptions: ', setOptions); + // State to track overflow visibility + const [showLeftArrow, setShowLeftArrow] = useState(false); + const [showRightArrow, setShowRightArrow] = useState(false); - // Scroll to the selected option when it changes - useEffect(() => { + // Function to calculate overflow state + const updateOverflowState = useCallback(() => { 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); - } + const isOverflowing = container.scrollWidth > container.clientWidth; + const canScrollLeft = container.scrollLeft > 0; + const canScrollRight = + container.scrollLeft + container.clientWidth < container.scrollWidth; - return () => { - if (container) { - container.removeEventListener("wheel", handleWheel); - container.removeEventListener("mousedown", handleMouseDown); - container.removeEventListener("mousemove", handleMouseMove); - container.removeEventListener("mouseup", handleMouseUp); - container.removeEventListener("mouseleave", handleMouseLeave); - } - }; + setShowLeftArrow(isOverflowing && canScrollLeft); + setShowRightArrow(isOverflowing && canScrollRight); + } }, []); - // 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()); + useEffect(() => { + const container = containerRef.current; + + if (container) { + // Initial calculation after the DOM has been rendered + const handleInitialRender = () => { + requestAnimationFrame(updateOverflowState); + }; + + handleInitialRender(); + + // Update on window resize or scroll + const handleResize = () => updateOverflowState(); + const handleScroll = () => updateOverflowState(); + + // Add mouse wheel listener for horizontal scrolling + const handleWheel = (event: WheelEvent) => { + event.preventDefault(); // Prevent default vertical scrolling + if (container) { + container.scrollBy({ + left: event.deltaY * 2, // Translate vertical scroll to horizontal scroll + behavior: "smooth", + }); + } + }; + + container.addEventListener("scroll", handleScroll); + window.addEventListener("resize", handleResize); + container.addEventListener("wheel", handleWheel, { passive: false }); + + return () => { + container.removeEventListener("scroll", handleScroll); + window.removeEventListener("resize", handleResize); + container.removeEventListener("wheel", handleWheel); + }; + } + }, [updateOverflowState]); + + // Handle scrolling with navigation arrows + const handleScrollLeft = () => { + const container = containerRef.current; + if (container) { + container.scrollBy({ + left: -200, // Scroll left by 200px + behavior: "smooth", + }); + } + }; + + const handleScrollRight = () => { + const container = containerRef.current; + if (container) { + container.scrollBy({ + left: 200, // Scroll right by 200px + behavior: "smooth", + }); + } }; return ( -
- {Object.keys(zonesData).map((zoneName, index) => ( -
{ +
+ {/* Left Arrow */} + {showLeftArrow && ( + + )} - - setSelectedZone({ - zoneName, - activeSides: zonesData[zoneName].activeSides || [], - panelOrder: zonesData[zoneName].panelOrder || [], - lockedPanels: zonesData[zoneName].lockedPanels || [], - widgets: zonesData[zoneName].widgets || [], - zoneId: zonesData[zoneName]?.zoneId || "", - zoneViewPortTarget: zonesData[zoneName].zoneViewPortTarget || [], - zoneViewPortPosition: - zonesData[zoneName].zoneViewPortPosition || [], - }); - // setSelectedZone({ - // zoneName, - // ...zonesData[zoneName], - // }); - }} - > - {zoneName} + {/* Zones Wrapper */} + {Object.keys(zonesData).length !== 0 ? ( +
+ {Object.keys(zonesData).map((zoneName, index) => ( +
{ + console.log("zoneName: ", zoneName); + setSelectedZone({ + zoneName, + activeSides: zonesData[zoneName].activeSides || [], + panelOrder: zonesData[zoneName].panelOrder || [], + lockedPanels: zonesData[zoneName].lockedPanels || [], + widgets: zonesData[zoneName].widgets || [], + zoneId: zonesData[zoneName]?.zoneId || "", + zoneViewPortTarget: + zonesData[zoneName].zoneViewPortTarget || [], + zoneViewPortPosition: + zonesData[zoneName].zoneViewPortPosition || [], + }); + }} + > + {zoneName} +
+ ))}
- ))} + ) : ( +
+ + No zones? Create one! +
+ )} + + {/* Right Arrow */} + {showRightArrow && ( + + )}
); }; diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index 9172fa4..339e7a6 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -1,164 +1,317 @@ -import { useWidgetStore } from "../../../store/useWidgetStore"; -import ProgressCard from "../realTimeVis/charts/ProgressCard"; -import PieGraphComponent from "../realTimeVis/charts/PieGraphComponent"; -import BarGraphComponent from "../realTimeVis/charts/BarGraphComponent"; -import LineGraphComponent from "../realTimeVis/charts/LineGraphComponent"; -import RadarGraphComponent from "../realTimeVis/charts/RadarGraphComponent"; -import DoughnutGraphComponent from "../realTimeVis/charts/DoughnutGraphComponent"; -import PolarAreaGraphComponent from "../realTimeVis/charts/PolarAreaGraphComponent"; - -export const DraggableWidget = ({ - widget, - hiddenPanels, // Add this prop to track hidden panels - index, onReorder -}: { - widget: any; - hiddenPanels: string[]; // Array of hidden panel names - index: number; onReorder: (fromIndex: number, toIndex: number) => void -}) => { - const { selectedChartId, setSelectedChartId } = useWidgetStore(); - - const handlePointerDown = () => { - if (selectedChartId?.id !== widget.id) { - setSelectedChartId(widget); - } - }; - - // Determine if the widget's panel is hidden - const isPanelHidden = hiddenPanels.includes(widget.panel); - - const handleDragStart = (event: React.DragEvent) => { - event.dataTransfer.setData('text/plain', index.toString()); // Store the index of the dragged widget - }; - const handleDragEnter = (event: React.DragEvent) => { - event.preventDefault(); // Allow drop - }; - - const handleDragOver = (event: React.DragEvent) => { - event.preventDefault(); // Allow drop - }; - - const handleDrop = (event: React.DragEvent) => { - event.preventDefault(); - const fromIndex = parseInt(event.dataTransfer.getData('text/plain'), 10); // Get the dragged widget's index - const toIndex = index; // The index of the widget where the drop occurred - if (fromIndex !== toIndex) { - onReorder(fromIndex, toIndex); // Call the reorder function passed as a prop - } - }; - - - return ( - <> - - - ); -}; +import { useWidgetStore } from "../../../store/useWidgetStore"; +import ProgressCard from "../realTimeVis/charts/ProgressCard"; +import PieGraphComponent from "../realTimeVis/charts/PieGraphComponent"; +import BarGraphComponent from "../realTimeVis/charts/BarGraphComponent"; +import LineGraphComponent from "../realTimeVis/charts/LineGraphComponent"; +import DoughnutGraphComponent from "../realTimeVis/charts/DoughnutGraphComponent"; +import PolarAreaGraphComponent from "../realTimeVis/charts/PolarAreaGraphComponent"; +import { + DeleteIcon, + DublicateIcon, + KebabIcon, +} from "../../icons/ExportCommonIcons"; +import { useEffect, useRef, useState } from "react"; + +type Side = "top" | "bottom" | "left" | "right"; + +interface Widget { + id: string; + type: string; + title: string; + panel: Side; + data: any; +} + +export const DraggableWidget = ({ + widget, + hiddenPanels, + index, + onReorder, + openKebabId, + setOpenKebabId, + selectedZone, + setSelectedZone, +}: { + selectedZone: { + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + widgets: Widget[]; + }; + setSelectedZone: React.Dispatch< + React.SetStateAction<{ + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + zoneId: string; + zoneViewPortTarget: number[]; + zoneViewPortPosition: number[]; + widgets: { + id: string; + type: string; + title: string; + panel: Side; + data: any; + }[]; + }> + >; + + widget: any; + hiddenPanels: string[]; + index: number; + onReorder: (fromIndex: number, toIndex: number) => void; + openKebabId: string | null; + setOpenKebabId: (id: string | null) => void; +}) => { + const { selectedChartId, setSelectedChartId } = useWidgetStore(); + const [panelDimensions, setPanelDimensions] = useState<{ + [side in Side]?: { width: number; height: number }; + }>({}); + const handlePointerDown = () => { + if (selectedChartId?.id !== widget.id) { + setSelectedChartId(widget); + } + }; + + const isPanelHidden = hiddenPanels.includes(widget.panel); + + const deleteSelectedChart = () => { + const updatedWidgets = selectedZone.widgets.filter( + (w: Widget) => w.id !== widget.id + ); + + setSelectedZone((prevZone: any) => ({ + ...prevZone, + widgets: updatedWidgets, + })); + + setOpenKebabId(null); + }; + + const getCurrentWidgetCount = (panel: Side) => + selectedZone.widgets.filter((w) => w.panel === panel).length; + + const calculatePanelCapacity = (panel: Side) => { + const CHART_WIDTH = 150; + const CHART_HEIGHT = 150; + 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 isPanelFull = (panel: Side) => { + const currentWidgetCount = getCurrentWidgetCount(panel); + const panelCapacity = calculatePanelCapacity(panel); + return currentWidgetCount >= panelCapacity; + }; + + const duplicateWidget = () => { + const duplicatedWidget: Widget = { + ...widget, + id: `${widget.id}-copy-${Date.now()}`, + }; + + setSelectedZone((prevZone: any) => ({ + ...prevZone, + widgets: [...prevZone.widgets, duplicatedWidget], + })); + + setOpenKebabId(null); + + console.log("Duplicated widget with ID:", duplicatedWidget.id); + }; + + const handleKebabClick = (event: React.MouseEvent) => { + event.stopPropagation(); + if (openKebabId === widget.id) { + setOpenKebabId(null); + } else { + setOpenKebabId(widget.id); + } + }; + + const widgetRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + widgetRef.current && + !widgetRef.current.contains(event.target as Node) + ) { + setOpenKebabId(null); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [setOpenKebabId]); + + const handleDragStart = (event: React.DragEvent) => { + event.dataTransfer.setData("text/plain", index.toString()); // Store the index of the dragged widget + }; + const handleDragEnter = (event: React.DragEvent) => { + event.preventDefault(); // Allow drop + }; + + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); // Allow drop + }; + + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + const fromIndex = parseInt(event.dataTransfer.getData("text/plain"), 10); // Get the dragged widget's index + const toIndex = index; // The index of the widget where the drop occurred + if (fromIndex !== toIndex) { + onReorder(fromIndex, toIndex); // Call the reorder function passed as a prop + } + }; + + return ( + <> +
+ {/* Kebab Icon */} +
+ +
+ + {/* Kebab Options */} + {openKebabId === widget.id && ( +
+
+
+ +
+
Duplicate
+
+
+
+ +
+
Delete
+
+
+ )} + + {/* Render charts based on widget type */} + {widget.type === "progress" && ( + + )} + {widget.type === "line" && ( + + )} + {widget.type === "bar" && ( + + )} + {widget.type === "pie" && ( + + )} + {widget.type === "doughnut" && ( + + )} + {widget.type === "polarArea" && ( + + )} +
+ + ); +}; diff --git a/app/src/components/ui/componets/Panel.tsx b/app/src/components/ui/componets/Panel.tsx index a764f09..3fee476 100644 --- a/app/src/components/ui/componets/Panel.tsx +++ b/app/src/components/ui/componets/Panel.tsx @@ -1,251 +1,256 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; -import { useWidgetStore } from "../../../store/useWidgetStore"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -import { DraggableWidget } from "./DraggableWidget"; -import { arrayMove } from "@dnd-kit/sortable"; - -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[]; - zoneId: string; - zoneViewPortTarget: number[]; - zoneViewPortPosition: number[] - widgets: Widget[]; - }; - setSelectedZone: React.Dispatch< - React.SetStateAction<{ - zoneName: string; - activeSides: Side[]; - panelOrder: Side[]; - lockedPanels: Side[]; - zoneId: string; - zoneViewPortTarget: number[]; - zoneViewPortPosition: number[] - widgets: Widget[]; - }> - >; - hiddenPanels: string[]; -} - -const generateUniqueId = () => - `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - -const Panel: React.FC = ({ - selectedZone, - setSelectedZone, - hiddenPanels, -}) => { - const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({}); - const [panelDimensions, setPanelDimensions] = useState<{ - [side in Side]?: { width: number; height: number }; - }>({}); - - const { isPlaying } = usePlayButtonStore(); - - 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"); - const panelSize = isPlaying ? 300 : 210; - - switch (side) { - case "top": - case "bottom": - return { - width: `calc(100% - ${(leftActive ? panelSize : 0) + (rightActive ? panelSize : 0) - }px)`, - height: `${panelSize - 2}px`, - left: leftActive ? `${panelSize}px` : "0", - right: rightActive ? `${panelSize}px` : "0", - [side]: "0", - }; - case "left": - case "right": - return { - width: `${panelSize - 2}px`, - height: `calc(100% - ${(topActive ? panelSize : 0) + (bottomActive ? panelSize : 0) - }px)`, - top: topActive ? `${panelSize}px` : "0", - bottom: bottomActive ? `${panelSize}px` : "0", - [side]: "0", - }; - default: - return {}; - } - }, - [selectedZone.panelOrder, isPlaying] - ); - - const handleDrop = (e: React.DragEvent, panel: Side) => { - e.preventDefault(); - const { draggedAsset } = useWidgetStore.getState(); - if (!draggedAsset) return; - if (isPanelLocked(panel)) return; - - const currentWidgetsCount = getCurrentWidgetCount(panel); - const maxCapacity = calculatePanelCapacity(panel); - - if (currentWidgetsCount >= maxCapacity) return; - - console.log('draggedAsset: ', draggedAsset); - console.log('panel: ', panel); - addWidgetToPanel(draggedAsset, panel); - }; - - 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 = 150; - const CHART_HEIGHT = 150; - 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(() => { - 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 handleReorder = (fromIndex: number, toIndex: number, panel: Side) => { - if (!selectedZone) return; // Ensure selectedZone is not null - console.log('selectedZone: ', selectedZone); - - setSelectedZone((prev) => { - if (!prev) return prev; // Ensure prev is not null - - // Filter widgets for the specified panel - const widgetsInPanel = prev.widgets.filter((w) => w.panel === panel); - - // Reorder widgets within the same panel - const reorderedWidgets = arrayMove(widgetsInPanel, fromIndex, toIndex); - - // Merge the reordered widgets back into the full list while preserving the order - const updatedWidgets = prev.widgets - .filter((widget) => widget.panel !== panel) // Keep widgets from other panels - .concat(reorderedWidgets); // Add the reordered widgets for the specified panel - - return { - ...prev, - widgets: updatedWidgets, - }; - }); - }; - - - - - 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, index) => ( - - handleReorder(fromIndex, toIndex, side) - } - /> - ))} -
-
- ))} - - ); -}; - -export default Panel; - - +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { useWidgetStore } from "../../../store/useWidgetStore"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { DraggableWidget } from "./DraggableWidget"; +import { arrayMove } from "@dnd-kit/sortable"; + +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[]; + zoneId: string; + zoneViewPortTarget: number[]; + zoneViewPortPosition: number[] + widgets: Widget[]; + }; + setSelectedZone: React.Dispatch< + React.SetStateAction<{ + zoneName: string; + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + zoneId: string; + zoneViewPortTarget: number[]; + zoneViewPortPosition: number[] + widgets: Widget[]; + }> + >; + hiddenPanels: string[]; + setZonesData: React.Dispatch>; // Add this line +} + +const generateUniqueId = () => + `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + +const Panel: React.FC = ({ + selectedZone, + setSelectedZone, + hiddenPanels, + setZonesData, +}) => { + const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({}); + const [panelDimensions, setPanelDimensions] = useState<{ + [side in Side]?: { width: number; height: number }; + }>({}); + const [openKebabId, setOpenKebabId] = useState(null); + + const { isPlaying } = usePlayButtonStore(); + + 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"); + const panelSize = isPlaying ? 300 : 210; + + switch (side) { + case "top": + case "bottom": + return { + width: `calc(100% - ${ + (leftActive ? panelSize : 0) + (rightActive ? panelSize : 0) + }px)`, + height: `${panelSize - 2}px`, + left: leftActive ? `${panelSize}px` : "0", + right: rightActive ? `${panelSize}px` : "0", + [side]: "0", + }; + case "left": + case "right": + return { + width: `${panelSize - 2}px`, + height: `calc(100% - ${ + (topActive ? panelSize : 0) + (bottomActive ? panelSize : 0) + }px)`, + top: topActive ? `${panelSize}px` : "0", + bottom: bottomActive ? `${panelSize}px` : "0", + [side]: "0", + }; + default: + return {}; + } + }, + [selectedZone.panelOrder, isPlaying] + ); + + const handleDrop = (e: React.DragEvent, panel: Side) => { + e.preventDefault(); + const { draggedAsset } = useWidgetStore.getState(); + if (!draggedAsset) return; + if (isPanelLocked(panel)) return; + + const currentWidgetsCount = getCurrentWidgetCount(panel); + const maxCapacity = calculatePanelCapacity(panel); + + if (currentWidgetsCount >= maxCapacity) return; + + console.log("draggedAsset: ", draggedAsset); + console.log("panel: ", panel); + addWidgetToPanel(draggedAsset, panel); + }; + + 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 = 150; + const CHART_HEIGHT = 150; + 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); + }; + + // while dublicate check this and add + const addWidgetToPanel = (asset: any, panel: Side) => { + const newWidget = { + ...asset, + id: generateUniqueId(), + panel, + }; + + setSelectedZone((prev) => ({ + ...prev, + widgets: [...prev.widgets, newWidget], + })); + }; + + 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 handleReorder = (fromIndex: number, toIndex: number, panel: Side) => { + if (!selectedZone) return; // Ensure selectedZone is not null + console.log("selectedZone: ", selectedZone); + + setSelectedZone((prev) => { + if (!prev) return prev; // Ensure prev is not null + + // Filter widgets for the specified panel + const widgetsInPanel = prev.widgets.filter((w) => w.panel === panel); + + // Reorder widgets within the same panel + const reorderedWidgets = arrayMove(widgetsInPanel, fromIndex, toIndex); + + // Merge the reordered widgets back into the full list while preserving the order + const updatedWidgets = prev.widgets + .filter((widget) => widget.panel !== panel) // Keep widgets from other panels + .concat(reorderedWidgets); // Add the reordered widgets for the specified panel + + return { + ...prev, + widgets: updatedWidgets, + }; + }); + }; + + 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, index) => ( + + handleReorder(fromIndex, toIndex, side) + } + openKebabId={openKebabId} + setOpenKebabId={setOpenKebabId} + selectedZone={selectedZone} + setSelectedZone={setSelectedZone} + /> + ))} +
+
+ ))} + + ); +}; + +export default Panel; diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index f5e858d..7f7dc2f 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -1,168 +1,168 @@ -import React, { useEffect, useState, useRef } from "react"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -import Panel from "./Panel"; -import AddButtons from "./AddButtons"; -import { useSelectedZoneStore } from "../../../store/useZoneStore"; -import DisplayZone from "./DisplayZone"; -import Scene from "../../../modules/scene/scene"; -import useModuleStore from "../../../store/useModuleStore"; -import { useDroppedObjectsStore, useZones } from "../../../store/store"; -import DroppedObjects from "./DroppedFloatingWidgets"; - - - -type Side = "top" | "bottom" | "left" | "right"; - -type FormattedZoneData = Record< - string, - { - activeSides: Side[]; - panelOrder: Side[]; - lockedPanels: Side[]; - zoneId: string; - zoneViewPortTarget: number[]; - zoneViewPortPosition: number[] - widgets: Widget[]; - } ->; -type Widget = { - id: string; - type: string; - title: string; - panel: Side; - data: any; -}; - -const RealTimeVisulization: React.FC = () => { - const [hiddenPanels, setHiddenPanels] = React.useState([]); - const containerRef = useRef(null); - const { isPlaying } = usePlayButtonStore(); - const { activeModule } = useModuleStore(); - const [droppedObjects, setDroppedObjects] = useState([]); - const [zonesData, setZonesData] = useState({}); - const { selectedZone, setSelectedZone } = useSelectedZoneStore(); - const { zones } = useZones() - - useEffect(() => { - const data = Array.isArray(zones) ? zones : []; - - const formattedData = data.reduce((acc, zone) => { - acc[zone.zoneName] = { - activeSides: [], - panelOrder: [], - lockedPanels: [], - zoneId: zone.zoneId, - zoneViewPortTarget: zone.viewPortCenter, - zoneViewPortPosition: zone.viewPortposition, - widgets: [], - }; - return acc; - }, {}); - - setZonesData(formattedData); - }, [zones]); - - useEffect(() => { - setZonesData((prev) => { - if (!selectedZone) return prev; - return { - ...prev, - [selectedZone.zoneName]: { - ...prev[selectedZone.zoneName], // Keep existing properties - activeSides: selectedZone.activeSides || [], - panelOrder: selectedZone.panelOrder || [], - lockedPanels: selectedZone.lockedPanels || [], - zoneId: selectedZone.zoneId || "", - zoneViewPortTarget: selectedZone.zoneViewPortTarget || [], - zoneViewPortPosition: selectedZone.zoneViewPortPosition || [], - widgets: selectedZone.widgets || [], - }, - }; - }); - }, [selectedZone]); - - // const handleDrop = (event: React.DragEvent) => { - // console.log("Drop event fired! ✅"); - // event.preventDefault(); - - // const data = event.dataTransfer.getData("text/plain"); - // if (!data) { - // console.log("❌ No data received on drop!"); - // return; - // } - - // try { - // const droppedData = JSON.parse(data); - // console.log("✅ Dropped Data:", droppedData); - - // console.log('droppedData: ', droppedData); - // setDroppedObjects((prev) => [...prev, droppedData]); // ✅ Add to state - // console.log(droppedObjects); - // } catch (error) { - // console.error("❌ Error parsing dropped data:", error); - // } - // }; - const handleDrop = (event: React.DragEvent) => { - event.preventDefault(); - const data = event.dataTransfer.getData("text/plain"); // Use "text/plain" to match the drag event - - if (data) { - const droppedData = JSON.parse(data); - useDroppedObjectsStore.getState().addObject(droppedData); // Add to Zustand store - } - }; - - return ( -
-
handleDrop(event)} - onDragOver={(event) => event.preventDefault()} - > - - -
- {activeModule === "visualization" && ( - <> - - - {!isPlaying && selectedZone?.zoneName !== "" && ( - - )} - - - - )} -
- ); -}; - -export default RealTimeVisulization; +import React, { useEffect, useState, useRef } from "react"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import Panel from "./Panel"; +import AddButtons from "./AddButtons"; +import { useSelectedZoneStore } from "../../../store/useZoneStore"; +import DisplayZone from "./DisplayZone"; +import Scene from "../../../modules/scene/scene"; +import useModuleStore from "../../../store/useModuleStore"; +import { useDroppedObjectsStore, useZones } from "../../../store/store"; +import DroppedObjects from "./DroppedFloatingWidgets"; + + +type Side = "top" | "bottom" | "left" | "right"; + +type FormattedZoneData = Record< + string, + { + activeSides: Side[]; + panelOrder: Side[]; + lockedPanels: Side[]; + zoneId: string; + zoneViewPortTarget: number[]; + zoneViewPortPosition: number[] + widgets: Widget[]; + } +>; +type Widget = { + id: string; + type: string; + title: string; + panel: Side; + data: any; +}; + +const RealTimeVisulization: React.FC = () => { + const [hiddenPanels, setHiddenPanels] = React.useState([]); + const containerRef = useRef(null); + const { isPlaying } = usePlayButtonStore(); + const { activeModule } = useModuleStore(); + const [droppedObjects, setDroppedObjects] = useState([]); + const [zonesData, setZonesData] = useState({}); + const { selectedZone, setSelectedZone } = useSelectedZoneStore(); + const { zones } = useZones() + + useEffect(() => { + const data = Array.isArray(zones) ? zones : []; + + const formattedData = data.reduce((acc, zone) => { + acc[zone.zoneName] = { + activeSides: [], + panelOrder: [], + lockedPanels: [], + zoneId: zone.zoneId, + zoneViewPortTarget: zone.viewPortCenter, + zoneViewPortPosition: zone.viewPortposition, + widgets: [], + }; + return acc; + }, {}); + + setZonesData(formattedData); + }, [zones]); + + useEffect(() => { + setZonesData((prev) => { + if (!selectedZone) return prev; + return { + ...prev, + [selectedZone.zoneName]: { + ...prev[selectedZone.zoneName], // Keep existing properties + activeSides: selectedZone.activeSides || [], + panelOrder: selectedZone.panelOrder || [], + lockedPanels: selectedZone.lockedPanels || [], + zoneId: selectedZone.zoneId || "", + zoneViewPortTarget: selectedZone.zoneViewPortTarget || [], + zoneViewPortPosition: selectedZone.zoneViewPortPosition || [], + widgets: selectedZone.widgets || [], + }, + }; + }); + }, [selectedZone]); + + // const handleDrop = (event: React.DragEvent) => { + // console.log("Drop event fired! ✅"); + // event.preventDefault(); + + // const data = event.dataTransfer.getData("text/plain"); + // if (!data) { + // console.log("❌ No data received on drop!"); + // return; + // } + + // try { + // const droppedData = JSON.parse(data); + // console.log("✅ Dropped Data:", droppedData); + + // console.log('droppedData: ', droppedData); + // setDroppedObjects((prev) => [...prev, droppedData]); // ✅ Add to state + // console.log(droppedObjects); + // } catch (error) { + // console.error("❌ Error parsing dropped data:", error); + // } + // }; + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + const data = event.dataTransfer.getData("text/plain"); // Use "text/plain" to match the drag event + + if (data) { + const droppedData = JSON.parse(data); + useDroppedObjectsStore.getState().addObject(droppedData); // Add to Zustand store + } + }; + + return ( +
+
handleDrop(event)} + onDragOver={(event) => event.preventDefault()} + > + + +
+ {activeModule === "visualization" && ( + <> + + + {!isPlaying && selectedZone?.zoneName !== "" && ( + + )} + + + + )} +
+ ); +}; + +export default RealTimeVisulization; diff --git a/app/src/components/ui/inputs/MultiLevelDropDown.tsx b/app/src/components/ui/inputs/MultiLevelDropDown.tsx index ef74bcf..3385a5b 100644 --- a/app/src/components/ui/inputs/MultiLevelDropDown.tsx +++ b/app/src/components/ui/inputs/MultiLevelDropDown.tsx @@ -141,6 +141,7 @@ // export default MultiLevelDropdown; import React, { useState, useRef, useEffect } from "react"; +import { ArrowIcon } from "../../icons/ExportCommonIcons"; // Dropdown Item Component const DropdownItem = ({ @@ -173,7 +174,13 @@ const NestedDropdown = ({ className={`dropdown-trigger ${open ? "open" : ""}`} onClick={() => setOpen(!open)} > - {label} {open ? "▼" : "▶"} + {label} +
+ +
{open && (
@@ -199,11 +206,11 @@ interface MultiLevelDropdownProps { } // Main Multi-Level Dropdown Component -const MultiLevelDropdown = ({ - data, - onSelect, +const MultiLevelDropdown = ({ + data, + onSelect, onUnselect, - selectedValue + selectedValue, }: MultiLevelDropdownProps) => { const [open, setOpen] = useState(false); const dropdownRef = useRef(null); @@ -236,7 +243,7 @@ const MultiLevelDropdown = ({ }; // Determine the display label - const displayLabel = selectedValue + const displayLabel = selectedValue ? `${selectedValue.name} - ${selectedValue.fields}` : "Dropdown trigger"; @@ -270,4 +277,3 @@ const MultiLevelDropdown = ({ }; export default MultiLevelDropdown; - diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx new file mode 100644 index 0000000..85f2c54 --- /dev/null +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -0,0 +1,133 @@ +import React, { useState, useRef, useEffect } from "react"; +import { ExitIcon, PlayStopIcon, ResetIcon } from "../../icons/SimulationIcons"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; + +const SimulationPlayer: React.FC = () => { + const [speed, setSpeed] = useState(1); + const [playSimulation, setPlaySimulation] = useState(false); + const { setIsPlaying } = usePlayButtonStore(); + const sliderRef = useRef(null); + const isDragging = useRef(false); + + // Button functions + const handleReset = () => { + setSpeed(1); + }; + const handlePlayStop = () => { + setPlaySimulation(!playSimulation); + }; + const handleExit = () => { + setPlaySimulation(false); + setIsPlaying(false); + }; + + // Slider functions starts + const handleSpeedChange = (event: React.ChangeEvent) => { + setSpeed(parseFloat(event.target.value)); + }; + + const calculateHandlePosition = () => { + return ((speed - 0.5) / (50 - 0.5)) * 100; + }; + + const handleMouseDown = () => { + isDragging.current = true; + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + }; + + const handleMouseMove = (e: MouseEvent) => { + if (!isDragging.current || !sliderRef.current) return; + + const sliderRect = sliderRef.current.getBoundingClientRect(); + const offsetX = e.clientX - sliderRect.left; + const percentage = Math.min(Math.max(offsetX / sliderRect.width, 0), 1); + const newValue = 0.5 + percentage * (50 - 0.5); + setSpeed(parseFloat(newValue.toFixed(1))); + }; + + const handleMouseUp = () => { + isDragging.current = false; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + useEffect(() => { + return () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + }, []); + // Slider function ends + + return ( +
+
+
+
{ + handleReset(); + }} + > + + Reset +
+
{ + handlePlayStop(); + }} + > + + {playSimulation ? "Play" : "Stop"} +
+
{ + handleExit(); + }} + > + + Exit +
+
+
+
0.5x
+
+
+
+
+
+
+
+
+
+
+
+
+ {speed.toFixed(1)}x +
+ +
+
+
50x
+
+
+
+ ); +}; + +export default SimulationPlayer; diff --git a/app/src/modules/market/AssetPreview.tsx b/app/src/modules/market/AssetPreview.tsx index a3c1cb0..be474a2 100644 --- a/app/src/modules/market/AssetPreview.tsx +++ b/app/src/modules/market/AssetPreview.tsx @@ -37,6 +37,7 @@ const AssetPreview: React.FC = ({
+ {/* Add canvas here */}
diff --git a/app/src/modules/market/Card.tsx b/app/src/modules/market/Card.tsx index 3df2e1f..646fe39 100644 --- a/app/src/modules/market/Card.tsx +++ b/app/src/modules/market/Card.tsx @@ -8,24 +8,51 @@ import { } from "../../components/icons/marketPlaceIcons"; import assetImage from "../../assets/image/image.png"; -const Card: React.FC = () => { + +interface CardProps { + assetName: string; + uploadedOn: string; + price: number; + rating: number; + views: number; + onSelectCard: (cardData: { + assetName: string; + uploadedOn: string; + price: number; + rating: number; + views: number; + }) => void; +} + +const Card: React.FC = ({ + assetName, + uploadedOn, + price, + rating, + views, + onSelectCard, +}) => { + const handleCardSelect = () => { + onSelectCard({ assetName, uploadedOn, price, rating, views }); + }; + return (
- + {assetName}
-
Asset name
-
Uploaded on-12 Jan 23
+
{assetName}
+
{uploadedOn}
- 1.5k + {views}
@@ -39,17 +66,17 @@ const Card: React.FC = () => {
- - - - - + {[...Array(5)].map((_, index) => ( + + ))}
- ₹ 36,500/unit + ₹ {price}/unit
-
Buy now
+
+ Buy now +
); }; diff --git a/app/src/modules/market/CardsContainer.tsx b/app/src/modules/market/CardsContainer.tsx index 27d4a6b..96a47e4 100644 --- a/app/src/modules/market/CardsContainer.tsx +++ b/app/src/modules/market/CardsContainer.tsx @@ -1,15 +1,149 @@ -import React from "react"; +import React, { useState } from "react"; import Card from "./Card"; +import AssetPreview from "./AssetPreview"; +import RenderOverlay from "../../components/templates/Overlay"; const CardsContainer: React.FC = () => { - const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + const array = [ + { + id: 1, + name: "Asset 1", + uploadedOn: "12 Jan 23", + price: 36500, + rating: 4.5, + views: 500, + }, + { + id: 2, + name: "Asset 2", + uploadedOn: "14 Jan 23", + price: 45000, + rating: 4.0, + views: 500, + }, + { + id: 3, + name: "Asset 3", + uploadedOn: "15 Jan 23", + price: 52000, + rating: 4.8, + views: 500, + }, + { + id: 4, + name: "Asset 4", + uploadedOn: "18 Jan 23", + price: 37000, + rating: 3.9, + views: 500, + }, + { + id: 5, + name: "Asset 5", + uploadedOn: "20 Jan 23", + price: 60000, + rating: 5.0, + views: 500, + }, + { + id: 6, + name: "Asset 6", + uploadedOn: "22 Jan 23", + price: 46000, + rating: 4.2, + views: 500, + }, + { + id: 7, + name: "Asset 7", + uploadedOn: "25 Jan 23", + price: 38000, + rating: 4.3, + views: 500, + }, + { + id: 8, + name: "Asset 8", + uploadedOn: "27 Jan 23", + price: 41000, + rating: 4.1, + views: 500, + }, + { + id: 9, + name: "Asset 9", + uploadedOn: "30 Jan 23", + price: 55000, + rating: 4.6, + views: 500, + }, + { + id: 10, + name: "Asset 10", + uploadedOn: "2 Feb 23", + price: 49000, + rating: 4.4, + views: 500, + }, + { + id: 11, + name: "Asset 11", + uploadedOn: "5 Feb 23", + price: 62000, + rating: 5.0, + views: 500, + }, + { + id: 12, + name: "Asset 12", + uploadedOn: "7 Feb 23", + price: 53000, + rating: 4.7, + views: 500, + }, + ]; + + const [selectedCard, setSelectedCard] = useState<{ + assetName: string; + uploadedOn: string; + price: number; + rating: number; + views: number; + } | null>(null); + + const handleCardSelect = (cardData: { + assetName: string; + uploadedOn: string; + price: number; + rating: number; + views: number; + }) => { + setSelectedCard(cardData); + }; + return (
Products You May Like
- {array.map((index) => ( - + {array.map((asset) => ( + ))} + {/* */} + {selectedCard && ( + + )} + {/* */}
); diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index d349a1a..d3f2923 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { Canvas } from "@react-three/fiber"; import { Environment, KeyboardControls } from "@react-three/drei"; @@ -15,6 +15,8 @@ import background from "../../assets/textures/hdr/mudroadpuresky2k.hdr"; import SelectionControls from "./controls/selection/selectionControls"; import MeasurementTool from "./tools/measurementTool"; import Simulation from "../simulation/simulation"; +import DroppedObjects from "../../components/ui/componets/DroppedFloatingWidgets"; + // import Simulation from "./simulationtemp/simulation"; import ZoneCentreTarget from "../../components/ui/componets/zoneCameraTarget"; @@ -27,6 +29,9 @@ export default function Scene() { { name: "right", keys: ["ArrowRight", "d", "D"] }, ], []) + + + return ( { e.preventDefault(); }} + > + + {/* */} diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 4d52e9f..1046ea2 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -17,9 +17,9 @@ import { } from "../store/store"; import { useNavigate } from "react-router-dom"; import { usePlayButtonStore } from "../store/usePlayButtonStore"; -import SimulationUI from "../modules/simulation/simulationUI"; import MarketPlace from "../modules/market/MarketPlace"; import LoadingPage from "../components/templates/LoadingPage"; +import SimulationPlayer from "../components/ui/simulation/simulationPlayer"; const Project: React.FC = () => { let navigate = useNavigate(); @@ -65,6 +65,7 @@ const Project: React.FC = () => { {activeModule !== "market" && } {/* */} + {isPlaying && activeModule === "simulation" && }
); }; diff --git a/app/src/styles/base/reset.scss b/app/src/styles/base/reset.scss index b37f940..82d286e 100644 --- a/app/src/styles/base/reset.scss +++ b/app/src/styles/base/reset.scss @@ -1,7 +1,14 @@ * { - margin: 0; - padding: 0; - box-sizing: border-box; - user-select: none; - font-size: var(--font-size-regular); + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; + font-size: var(--font-size-regular); +} + +input[type="password"]::-ms-reveal, /* For Microsoft Edge */ +input[type="password"]::-ms-clear, /* For Edge clear button */ +input[type="password"]::-webkit-clear-button, /* For Chrome/Safari clear button */ +input[type="password"]::-webkit-inner-spin-button { /* Just in case */ + display: none; } diff --git a/app/src/styles/components/input.scss b/app/src/styles/components/input.scss index 3020c7d..36ac8ff 100644 --- a/app/src/styles/components/input.scss +++ b/app/src/styles/components/input.scss @@ -280,28 +280,24 @@ input { .dropdown-menu { position: absolute; - top: 100%; - left: 0; - background-color: #ffffff; - border: 1px solid #cccccc; + top: 110%; + right: -16px; + background-color: var(--background-color); + border: 1px solid var(--border-color); border-radius: 5px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + box-shadow: #{$box-shadow-medium}; z-index: 1000; min-width: 200px; overflow: auto; - max-height: 600px; + max-height: 400px; .dropdown-content { display: flex; flex-direction: column; gap: 6px; - .nested-dropdown { - // &:first-child{ margin-left: 0; - // } } - padding: 10px; } @@ -309,13 +305,13 @@ input { display: block; padding: 5px 10px; text-decoration: none; - color: #000000; + color: var(--text-color); font-size: 14px; cursor: pointer; transition: background-color 0.3s ease; &:hover { - background-color: #f0f0f0; + background-color: var(--background-color); } } @@ -329,15 +325,20 @@ input { padding: 5px 10px; cursor: pointer; font-size: 14px; - color: #000000; + color: var(--text-color); transition: background-color 0.3s ease; + border-radius: #{$border-radius-small}; + .arrow-container{ + @include flex-center; + } &:hover { - background-color: #f0f0f0; + background-color: var(--background-color); } &.open { - background-color: #e0e0e0; + color: var(--accent-color); + background-color: var(--highlight-accent-color); } .icon { @@ -349,7 +350,7 @@ input { .submenu { margin-top: 5px; padding-left: 20px; - border-left: 2px solid #cccccc; + border-left: 2px solid var(--border-color); display: flex; flex-direction: column; gap: 6px; @@ -576,26 +577,26 @@ input { color: var(--text-disabled); } } - .entered-emails{ + .entered-emails { @include flex-center; gap: 2px; background: var(--background-color-gray); padding: 0 4px; border-radius: #{$border-radius-large}; - span{ + span { height: 14px; width: 14px; line-height: 12px; text-align: center; border-radius: #{$border-radius-small}; - &:hover{ + &:hover { background: var(--accent-color); color: var(--primary-color); } } } } - .invite-button{ + .invite-button { padding: 4px 12px; border-radius: #{$border-radius-large}; background: var(--accent-color); diff --git a/app/src/styles/components/marketPlace/marketPlace.scss b/app/src/styles/components/marketPlace/marketPlace.scss index 2ae2790..cac366e 100644 --- a/app/src/styles/components/marketPlace/marketPlace.scss +++ b/app/src/styles/components/marketPlace/marketPlace.scss @@ -2,186 +2,356 @@ @use "../../abstracts/mixins.scss" as *; .marketplace-wrapper { - height: 100vh; - width: 100vw; - z-index: #{$z-index-marketplace}; - background-color: var(--background-color-secondary); - position: absolute; - left: 0; - padding: 100px 50px; + height: 100vh; + width: 100vw; + z-index: #{$z-index-marketplace}; + background-color: var(--background-color-secondary); + position: absolute; + left: 0; + padding: 100px 50px; + padding-bottom: 32px; + backdrop-filter: blur(6px); - .marketplace-container { - padding: 20px 2px; - height: calc(100vh - 120px); - background-color: var(--background-color); - box-shadow: #{$box-shadow-medium}; - border-radius: #{$border-radius-extra-large}; + .marketplace-container { + position: relative; + padding: 20px 2px; + height: 100%; + background-color: var(--background-color); + box-shadow: #{$box-shadow-medium}; + border-radius: #{$border-radius-extra-large}; + } + + .marketPlace { + width: 100%; + height: 100%; + overflow: auto; + left: calc(120px / 2); + top: 100px; + padding: 14px; + padding-bottom: 60px; + display: flex; + flex-direction: column; + gap: 24px; + + .filter-search-container { + width: 100%; + display: flex; + align-items: center; + gap: 12px; + + .search-wrapper { + min-width: 60%; + max-width: 684px; + padding: 0; + border-radius: $border-radius-large; + + .search-container { + border: none !important; + box-shadow: $box-shadow-medium; + border-radius: $border-radius-large; + + input { + border: none !important; + outline: none; + } + } + } + + .regularDropdown-container { + max-width: 159px; + } + + .button { + padding: 5px 20px; + border: 1px solid var(--accent-color); + border-radius: 14px; + } + + .rating-container { + display: flex; + align-items: center; + gap: 6px; + + .stars { + display: flex; + align-items: center; + } + } } - .marketPlace { - width: 100%; - height: 100%; - overflow: auto; - left: calc(120px / 2); - top: 100px; - padding: 14px; - padding-bottom: 60px; - display: flex; - flex-direction: column; - gap: 24px; + .cards-container-container { + padding: 0px 20px; + display: flex; + flex-direction: column; + gap: 6px; - .filter-search-container { + .header { + color: var(--text-color); + font-weight: $medium-weight; + font-size: $xlarge; + } + + .cards-wrapper-container { + display: flex; + flex-wrap: wrap; + gap: 28px; + + .card-container { + width: calc(25% - 23px); + border-radius: 18px; + padding: 12px; + box-shadow: 0px 2px 10.5px 0px #0000000d; + border: 1px solid var(--background-accent-transparent, #e0dfff80); + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + gap: 6px; + + .icon { + position: absolute; + top: 12px; + left: 12px; + width: 30px; + height: 30px; + border-radius: 10px; + padding: 5px; + background-color: var(--accent-color); + } + + .image-container { width: 100%; display: flex; - align-items: center; - gap: 12px; + justify-content: center; + } - .search-wrapper { - min-width: 60%; - max-width: 684px; - padding: 0; - border-radius: $border-radius-large ; + .assets-container { + display: flex; + justify-content: space-between; - .search-container { - border: none !important; - box-shadow: $box-shadow-medium; - border-radius: $border-radius-large ; + .name-container { + display: flex; + flex-direction: column; + gap: 3px; - input { - border: none !important; - outline: none; + .asstes-container { + font-weight: #{$bold-weight}; + font-size: $regular; + } - } - } + .assets-date { + color: var(--accent-color); + font-size: $small; + } } - .regularDropdown-container { - max-width: 159px; - } + .details { + display: flex; + align-items: center; + gap: 10px; - .button { - padding: 5px 20px; - border: 1px solid var(--accent-color); - border-radius: 14px; - } - - .rating-container { + .content { display: flex; align-items: center; gap: 6px; - - .stars { - display: flex; - align-items: center; - - } + } } - } + } - .cards-container-container { - padding: 0px 20px; + .vendor-icon { + font-weight: #{$bold-weight}; + font-size: $regular; + } + + .stars-container { display: flex; - flex-direction: column; - gap: 6px; + justify-content: space-between; + } - .header { - color: var(--text-color); - font-weight: $medium-weight; - font-size: $xlarge; - } - - .cards-wrapper-container { - display: flex; - flex-wrap: wrap; - gap: 28px; - - .card-container { - width: calc(25% - 23px); - border-radius: 18px; - padding: 12px; - box-shadow: 0px 2px 10.5px 0px #0000000D; - border: 1px solid var(--background-accent-transparent, #E0DFFF80); - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - gap: 6px; - - .icon { - position: absolute; - top: 12px; - left: 12px; - width: 30px; - height: 30px; - border-radius: 10px; - padding: 5px; - background-color: var(--accent-color); - } - - .image-container { - width: 100%; - display: flex; - justify-content: center; - } - - .assets-container { - display: flex; - justify-content: space-between; - - .name-container { - display: flex; - flex-direction: column; - gap: 3px; - - .asstes-container { - font-weight: #{$bold-weight}; - font-size: $regular ; - } - - .assets-date { - color: var(--accent-color); - font-size: $small; - } - } - - .details { - display: flex; - align-items: center; - gap: 10px; - - .content { - display: flex; - align-items: center; - gap: 6px; - } - } - } - - .vendor-icon { - - font-weight: #{$bold-weight}; - font-size: $regular ; - } - - .stars-container { - display: flex; - justify-content: space-between; - } - - .buy-now-button { - width: 100%; - background-color: var(--background-color-secondary); - border-radius: $border-radius-extra-large ; - padding: 8px 0; - @include flex-center; - color: var(--accent-color); - - &:hover { - cursor: pointer; - } - } - } + .buy-now-button { + width: 100%; + background-color: var(--background-color-secondary); + border-radius: $border-radius-extra-large; + padding: 8px 0; + @include flex-center; + color: var(--accent-color); + + &:hover { + cursor: pointer; } + } } + } } + } +} + +.assetPreview-wrapper { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + z-index: 3; + + .assetPreview { + width: 100%; + height: 100%; + background-color: var(--background-color); + display: flex; + gap: 12px; + z-index: 100; + border-radius: 20px; + } + + // Image Preview Section + .image-preview { + width: 50%; + height: 100%; + + img { + width: 100%; + height: 100%; + object-fit: contain; + } + } + + // Asset Details Section + .asset-details-preview { + width: 50%; + padding: 50px 20px; + overflow-y: auto; + } + + // Organization Section (Top part with image and details) + .organization { + display: flex; + align-items: center; + margin-bottom: 20px; + gap: 10px; + + .image { + @include flex-center; + height: 30px; + width: 30px; + min-height: 26px; + min-width: 26px; + border-radius: 50%; + font-weight: var(--font-weight-bold); + color: var(--background-color); + background-color: var(--accent-color); + } + + .organization-details { + display: flex; + flex-direction: column; + + .organization-name { + font-weight: bold; + margin-bottom: 5px; + + font-weight: #{$bold-weight}; + font-size: $regular; + } + + .follow { + color: var(--accent-color); + cursor: pointer; + } + } + } + + // Asset Details + .asset-details { + margin-top: 20px; + + .asset-name { + font-size: 1.5em; + font-weight: bold; + margin-bottom: 10px; + font-weight: #{$bold-weight}; + font-size: $large; + } + + .asset-description { + margin-bottom: 20px; + color: #666; + } + + .asset-review { + width: fit-content; + padding: 5px 10px; + display: flex; + align-items: center; + margin-bottom: 20px; + outline: 1px solid #909090cc; + border-radius: 6px; + + .asset-rating { + display: flex; + align-items: center; + gap: 4px; + margin-right: 10px; + font-weight: bold; + position: relative; + + font-weight: #{$bold-weight}; + font-size: $regular; + + &::after { + margin-left: 5px; + content: ""; + display: block; + width: 2px; + height: 12px; + background-color: #ccc; + } + } + + .asset-view { + font-weight: #{$bold-weight}; + font-size: $regular; + } + } + + .asset-price { + font-size: $xxlarge; + font-weight: bold; + margin-bottom: 20px; + } + } + + // Button Container and Button Styles + .button-container { + display: flex; + gap: 10px; + } + + .button { + color: white; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + text-align: center; + + &:first-child { + outline: 1px solid var(--accent-color); + color: var(--accent-color); + } + + &:last-child { + background-color: var(--accent-color); + color: var(--background-color); + } + } + + .closeButton { + color: var(--accent-color); + position: absolute; + top: 18px; + left: 18px; + @include flex-center; + cursor: pointer; + font-size: var(--font-size-large); + } } diff --git a/app/src/styles/components/simulation/simulation.scss b/app/src/styles/components/simulation/simulation.scss new file mode 100644 index 0000000..eb109c2 --- /dev/null +++ b/app/src/styles/components/simulation/simulation.scss @@ -0,0 +1,114 @@ +@use "../../abstracts/variables" as *; +@use "../../abstracts/mixins" as *; + +.simulation-player-wrapper { + position: fixed; + bottom: 32px; + left: 50%; + z-index: 2; + transform: translate(-50%, 0); + .simulation-player-container { + .controls-container { + @include flex-center; + gap: 12px; + margin-bottom: 4px; + .simulation-button-container { + @include flex-center; + gap: 2px; + padding: 6px 8px; + min-width: 64px; + background-color: var(--background-color); + border-radius: #{$border-radius-small}; + cursor: pointer; + &:hover { + background-color: var(--highlight-accent-color); + color: var(--accent-color); + path { + stroke: var(--accent-color); + } + } + } + } + .speed-control-container { + @include flex-center; + gap: 18px; + padding: 5px 16px; + background: var(--background-color); + border-radius: #{$border-radius-medium}; + box-sizing: #{$box-shadow-medium}; + .min-value, + .max-value { + font-weight: var(--font-weight-bold); + } + .slider-container { + width: 580px; + max-width: 80vw; + height: 28px; + background: var(--background-color-gray); + border-radius: #{$border-radius-small}; + position: relative; + padding: 4px 26px; + .custom-slider { + height: 100%; + width: 100%; + position: relative; + .slider-input { + position: absolute; + width: 100%; + height: 100%; + opacity: 0; + z-index: 3; + cursor: pointer; + } + .slider-handle { + position: absolute; + width: 42px; + line-height: 20px; + text-align: center; + background: var(--accent-color); + color: var(--primary-color); + border-radius: #{$border-radius-small}; + transform: translateX(-50%); + cursor: pointer; + z-index: 2; + } + } + .marker{ + position: absolute; + background-color: var(--text-disabled); + width: 2px; + height: 12px; + border-radius: 1px; + top: 8px; + } + .marker.marker-10{ + left: 10%; + } + .marker.marker-20{ + left: 20%; + } + .marker.marker-30{ + left: 30%; + } + .marker.marker-40{ + left: 40%; + } + .marker.marker-50{ + left: 50%; + } + .marker.marker-60{ + left: 60%; + } + .marker.marker-70{ + left: 70%; + } + .marker.marker-80{ + left: 80%; + } + .marker.marker-90{ + left: 90%; + } + } + } + } +} diff --git a/app/src/styles/components/visualization/ui/styledWidgets.scss b/app/src/styles/components/visualization/ui/styledWidgets.scss index 6006308..9c72fae 100644 --- a/app/src/styles/components/visualization/ui/styledWidgets.scss +++ b/app/src/styles/components/visualization/ui/styledWidgets.scss @@ -8,7 +8,7 @@ flex-direction: column; gap: 6px; width: 100%; - min-width: 250px; + // min-width: 1450px; .header { display: flex; justify-content: center; diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index fa6ce51..34c0074 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -20,8 +20,9 @@ @use 'components/tools'; @use 'components/visualization/floating/energyConsumed'; @use 'components/visualization/ui/styledWidgets'; -@use './components/visualization/floating/common'; -@use './components/marketPlace/marketPlace.scss'; +@use 'components/visualization/floating/common'; +@use 'components/marketPlace/marketPlace'; +@use 'components/simulation/simulation'; // layout @use 'layout/loading'; diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 2d40d64..43724ea 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -1,403 +1,519 @@ -@use "../abstracts/variables.scss" as *; - -// Main Container -.realTime-viz { - background-color: var(--background-color); - border-radius: 20px; - box-shadow: $box-shadow-medium; - width: calc(100% - (320px + 270px + 90px)); - height: calc(100% - (200px + 80px)); - position: absolute; - top: 50%; - left: calc(270px + 45px); - transform: translate(0, -50%); - border-radius: #{$border-radius-medium}; - transition: all 0.2s; - z-index: #{$z-index-default}; - - .scene-container { - overflow: hidden; - } - - .icon { - display: flex; - align-items: center; - position: relative; - } - - .icons-container { - .icon { - &:first-child { - &::after { - display: none; - } - } - } - } - - .zoon-wrapper { - display: flex; - background-color: var(--background-color); - position: absolute; - bottom: 10px; - left: 50%; - transform: translate(-50%, 0); - gap: 6px; - padding: 4px; - border-radius: 8px; - max-width: 80%; - overflow: auto; - max-width: calc(100% - 450px); - &::-webkit-scrollbar { - display: none; - } - - .zone { - width: auto; - background-color: var(--background-color); - 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 - } - } - } - - .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; - background-color: var(--background-color); - - &::-webkit-scrollbar { - display: none; - } - - .chart-container { - width: 100%; - height: 24% !important; - - min-height: 150px; - 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; - height: 100%; - - .chart-container { - height: 100% !important; - width: 20%; - min-width: 150px; - } - } - } - - &.top-panel { - top: 0; - } - - &.bottom-panel { - bottom: 0; - } - - &.left-panel { - left: 0; - top: 0; - bottom: 0; - - .chart-container { - width: 100%; - height: 180px; - } - } - - &.right-panel { - right: 0; - top: 0; - bottom: 0; - } - } -} - -.playingFlase{ - .zoon-wrapper{ - bottom: 300px !important; - } -} -// Side Buttons -.side-button-container { - position: absolute; - display: flex; - background-color: var(--background-color); - padding: 5px; - border-radius: 8px; - transition: transform 0.3s ease; - - .extra-Bs { - display: flex; - align-items: center; - gap: 12px; - - .icon { - display: flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - border-radius: 4px; - } - - .active { - background-color: var(--accent-color); - } - - &: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; - } - - &.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%; - } - } - } -} +@use "../abstracts/variables.scss" as *; +@use "../abstracts/mixins.scss" as *; + +// Main Container +.realTime-viz { + background-color: var(--background-color); + border-radius: 20px; + box-shadow: $box-shadow-medium; + width: calc(100% - (320px + 270px + 90px)); + height: calc(100% - (200px + 80px)); + position: absolute; + top: 50%; + left: calc(270px + 45px); + transform: translate(0, -50%); + border-radius: #{$border-radius-medium}; + transition: all 0.2s; + z-index: #{$z-index-default}; + + .scene-container { + overflow: hidden; + } + + .icon { + display: flex; + align-items: center; + position: relative; + } + + .icons-container { + .icon { + &:first-child { + &::after { + display: none; + } + } + } + } + + .zone-wrapper { + display: flex; + background-color: var(--background-color); + position: absolute; + bottom: 10px; + left: 50%; + transform: translate(-50%, 0); + gap: 6px; + + border-radius: 8px; + max-width: 80%; + overflow: auto; + // max-width: calc(100% - 450px); + + &::-webkit-scrollbar { + display: none; + } + + .arrow { + background-color: var(--highlight-accent-color); + color: var(--background-color); + } + + .zones-wrapper { + padding: 6px; + display: flex; + gap: 6px; + border-radius: #{$border-radius-medium}; + overflow-x: auto; + &::-webkit-scrollbar { + display: none; + } + } + .no-zone { + @include flex-center; + gap: 4px; + padding: 4px; + color: var(--text-disabled); + } + .zone { + width: auto; + background-color: var(--background-color); + 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; + } + } + + .zone-wrapper.bottom { + bottom: 210px; + } + + .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; + + .zone-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; + z-index: $z-index-tools; + + .panel-content { + position: relative; + height: 100%; + padding: 10px; + display: flex; + flex-direction: column; + gap: 6px; + background-color: var(--background-color); + + &::-webkit-scrollbar { + display: none; + } + + .chart-container { + width: 100%; + height: 25% !important; + min-height: 150px; + 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; + position: relative; + + .kebab { + width: 30px; + height: 30px; + position: absolute; + top: 0px; + right: 0px; + z-index: 10; + cursor: pointer; + @include flex-center; + } + + .kebab-options { + position: absolute; + top: 12px; + right: -100px; + transform: translate(0px, 0); + background-color: var(--background-color); + z-index: 10; + + display: flex; + flex-direction: column; + gap: 6px; + border-radius: 4px; + + box-shadow: var(--box-shadow-medium); + + .btn { + display: flex; + gap: 6px; + align-items: center; + padding: 5px 10px; + color: var(--text-color); + + &:hover { + .label { + color: var(--accent-color); + } + } + + &:hover { + background-color: var(--highlight-accent-color); + width: 100%; + + svg { + &:first-child { + fill: var(--accent-color); + } + + &:last-child { + fill: auto; + stroke: var(--accent-color); + } + } + } + } + + .btn-blur { + color: var(--text-disabled); + cursor: not-allowed; + pointer-events: none; + } + } + } + + .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; + height: 100%; + + .chart-container { + height: 100% !important; + width: 20%; + min-width: 150px; + } + } + } + + &.top-panel { + top: 0; + } + + &.bottom-panel { + bottom: 0; + } + + &.left-panel { + left: 0; + top: 0; + bottom: 0; + + .chart-container { + width: 100%; + height: 180px; + } + } + + &.right-panel { + right: 0; + top: 0; + bottom: 0; + } + } +} + +.playingFlase { + .zone-wrapper.bottom { + bottom: 300px; + } +} + +// Side Buttons +.side-button-container { + position: absolute; + display: flex; + background-color: var(--background-color); + padding: 2px; + border-radius: 2px; + transition: transform 0.3s ease; + box-shadow: #{$box-shadow-medium}; + + .extra-Bs { + display: flex; + align-items: center; + gap: 12px; + + .icon { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border-radius: 4px; + } + + .active { + background-color: var(--accent-color); + } + + &: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; + .add-icon { + @include flex-center; + transition: rotate 0.2s; + } + path { + stroke: var(--primary-color); + stroke-width: 2; + } + } + .active { + background: #ffe3e0; + .add-icon { + rotate: 45deg; + path { + stroke: #f65648; + stroke-width: 2; + } + } + } + + &.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%; + } + } + } +} + +.arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + border: none; + cursor: pointer; + z-index: 10; + height: 100%; +} + +.left-arrow { + left: 0; +} + +.right-arrow { + right: 0; +} + +.zone { + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + cursor: pointer; +} + +.zone.active { + background-color: #007bff; + color: white; +}