From a73c893040129f9bb11d4bf6f98da694e2eb49ab Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Thu, 27 Mar 2025 10:54:40 +0530 Subject: [PATCH] Added kebab for delete widgets --- .../components/icons/ExportCommonIcons.tsx | 96 +++++++ .../visualization/Visualization.tsx | 2 +- .../ui/charts/PieGraphComponent.tsx | 1 - .../components/ui/componets/DisplayZone.tsx | 242 +++++++++--------- .../ui/componets/DraggableWidget.tsx | 148 ++++++++++- app/src/components/ui/componets/Panel.tsx | 27 +- .../ui/componets/RealTimeVisulization.tsx | 92 ++++++- app/src/components/ui/list/DropDownList.tsx | 2 +- app/src/components/ui/list/List.tsx | 1 - .../visualization/ui/styledWidgets.scss | 2 +- app/src/styles/pages/realTimeViz.scss | 122 ++++++++- 11 files changed, 580 insertions(+), 155 deletions(-) diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index dd170f8..4386198 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/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/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/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index e699f27..ea6f62a 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState, useCallback } from "react"; import { Widget } from "../../../store/useWidgetStore"; // Define the type for `Side` @@ -51,135 +51,139 @@ 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); - } - }; + console.log("isOverflowing:", isOverflowing); + console.log("canScrollLeft:", canScrollLeft); + console.log("canScrollRight:", canScrollRight); + + 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) => ( -
{ - console.log('zoneName: ', zoneName); +
+ {/* Left Arrow */} + {showLeftArrow && ( + + )} - setSelectedZone({ - zoneName, - activeSides: zonesData[zoneName].activeSides || [], - panelOrder: zonesData[zoneName].panelOrder || [], - lockedPanels: zonesData[zoneName].lockedPanels || [], - widgets: zonesData[zoneName].widgets || [], - }) - // setSelectedZone({ - // zoneName, - // ...zonesData[zoneName], - // }); - console.log(selectedZone); - }} - > - {zoneName} -
- ))} + {/* Zones Wrapper */} +
+ {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 || [], + }); + }} + > + {zoneName} +
+ ))} +
+ + {/* Right Arrow */} + {showRightArrow && ( + + )}
); }; diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/components/ui/componets/DraggableWidget.tsx index 1157292..d25c8ec 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/components/ui/componets/DraggableWidget.tsx @@ -3,20 +3,58 @@ 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"; +import { + DeleteIcon, + DublicateIcon, + KebabIcon, +} from "../../icons/ExportCommonIcons"; +import { useEffect, useRef } 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, // Add this prop to track hidden panels - index, onReorder + 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[]; + widgets: Widget[]; + }> + >; widget: any; hiddenPanels: string[]; // Array of hidden panel names - index: number; onReorder: (fromIndex: number, toIndex: number) => void + index: number; + onReorder: (fromIndex: number, toIndex: number) => void; + openKebabId: string | null; // ID of the widget with an open kebab menu + setOpenKebabId: (id: string | null) => void; // Function to update the open kebab menu }) => { - const { selectedChartId, setSelectedChartId } = useWidgetStore(); const handlePointerDown = () => { @@ -29,7 +67,7 @@ export const DraggableWidget = ({ 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 + event.dataTransfer.setData("text/plain", index.toString()); // Store the index of the dragged widget }; const handleDragEnter = (event: React.DragEvent) => { event.preventDefault(); // Allow drop @@ -41,21 +79,89 @@ export const DraggableWidget = ({ const handleDrop = (event: React.DragEvent) => { event.preventDefault(); - const fromIndex = parseInt(event.dataTransfer.getData('text/plain'), 10); // Get the dragged widget's index + 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 } }; + // Handle kebab icon click to toggle kebab options + const handleKebabClick = (event: React.MouseEvent) => { + event.stopPropagation(); // Prevent click from propagating to parent elements + if (openKebabId === widget.id) { + // If the current kebab is already open, close it + setOpenKebabId(null); + } else { + // Open the kebab for the clicked widget and close others + setOpenKebabId(widget.id); + } + }; + + const deleteSelectedChart = () => { + // Filter out the widget to be deleted + const updatedWidgets = selectedZone.widgets.filter( + (w: Widget) => w.id !== widget.id + ); + + // Update the selectedZone state + setSelectedZone((prevZone: any) => ({ + ...prevZone, + widgets: updatedWidgets, // Replace the widgets array with the updated one + })); + + // Close the kebab menu after deletion + setOpenKebabId(null); + }; + const widgetRef = useRef(null); + + // Handle click outside to close the kebab menu + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + widgetRef.current && + !widgetRef.current.contains(event.target as Node) + ) { + setOpenKebabId(null); // Close the kebab menu if the click is outside + } + }; + + document.addEventListener("mousedown", handleClickOutside); + + // Cleanup event listener on component unmount + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [setOpenKebabId]); + const duplicateWidget = () => { + // Create a copy of the current widget with a new unique ID + const duplicatedWidget: Widget = { + ...widget, + id: `${widget.id}-copy-${Date.now()}`, // Generate a unique ID using a timestamp + }; + + // Add the duplicated widget to the selectedZone's widgets array + setSelectedZone((prevZone: any) => ({ + ...prevZone, + widgets: [...prevZone.widgets, duplicatedWidget], + })); + + // Close the kebab menu after duplication + setOpenKebabId(null); + + console.log("Duplicated widget with ID:", duplicatedWidget.id); + }; + + return ( <> @@ -241,5 +248,3 @@ const Panel: React.FC = ({ }; export default Panel; - - diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 385ae5d..f80b9ae 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -8,7 +8,6 @@ import Scene from "../../../modules/scene/scene"; import useModuleStore from "../../../store/useModuleStore"; import { getZonesApi } from "../../../services/realTimeVisulization/zoneData/getZones"; - type Side = "top" | "bottom" | "left" | "right"; type FormattedZoneData = Record< @@ -41,7 +40,87 @@ const RealTimeVisulization: React.FC = () => { const { isPlaying } = usePlayButtonStore(); const { activeModule } = useModuleStore(); - const [zonesData, setZonesData] = useState({}); + 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: [], + }, + "Inventory 1": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 2": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 3": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 4": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 5": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 6": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + "Inventory 7": { + activeSides: [], + panelOrder: [], + lockedPanels: [], + widgets: [], + }, + }); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); // useEffect(() => { @@ -73,10 +152,7 @@ const RealTimeVisulization: React.FC = () => { // GetZoneData(); // }, []); - useEffect(() => { - - console.log('zonesData: ', zonesData); - }, [zonesData]); + useEffect(() => {}, [zonesData]); // useEffect(() => { // setZonesData((prev) => { @@ -111,7 +187,8 @@ const RealTimeVisulization: React.FC = () => { style={{ height: "100%", width: "100%", - borderRadius: isPlaying || activeModule !== "visualization" ? "" : "6px", + borderRadius: + isPlaying || activeModule !== "visualization" ? "" : "6px", }} > @@ -137,6 +214,7 @@ const RealTimeVisulization: React.FC = () => { selectedZone={selectedZone} setSelectedZone={setSelectedZone} hiddenPanels={hiddenPanels} + setZonesData={setZonesData} /> )} diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index a61054b..98c357e 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -39,7 +39,7 @@ const DropDownList: React.FC = ({ useEffect(() => { async function GetZoneData() { const response = await getZonesApi("hexrfactory") - console.log('response: ', response.data); + setZoneDataList([{ id: "1", name: "zone1" }, { id: "2", name: "Zone 2" },]) } diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index bdf4749..1bf326f 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -8,7 +8,6 @@ interface ListProps { } const List: React.FC = ({ items = [] }) => { - console.log('items: ', items); return ( <> {items.length > 0 ? ( 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/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index f4c9ab9..29650e1 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -1,4 +1,5 @@ @use "../abstracts/variables.scss" as *; +@use "../abstracts/mixins.scss" as *; // Main Container .realTime-viz { @@ -43,15 +44,28 @@ left: 50%; transform: translate(-50%, 0); gap: 6px; - padding: 4px; + border-radius: 8px; max-width: 80%; overflow: auto; - max-width: calc(100% - 450px); + // max-width: calc(100% - 450px); + &::-webkit-scrollbar { display: none; } + .arrow { + background-color: var(--accent-color); + color: var(--background-color); + } + + .zones-wrapper { + &::-webkit-scrollbar { + display: none; + } + + } + .zone { width: auto; background-color: var(--background-color); @@ -155,7 +169,8 @@ position: relative; height: 100%; padding: 10px; - overflow: auto; + + display: flex; flex-direction: column; gap: 10px; @@ -176,6 +191,65 @@ 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; + + @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); + + .label { + &:hover { + 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); + } + } + } + } + } } .close-btn { @@ -236,11 +310,12 @@ } } -.playingFlase{ - .zoon-wrapper{ +.playingFlase { + .zoon-wrapper { bottom: 300px !important; } } + // Side Buttons .side-button-container { position: absolute; @@ -401,3 +476,40 @@ } } } + + + + + +.arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + cursor: pointer; + font-size: 20px; + padding: 6px; + z-index: 10; +} + +.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; +} \ No newline at end of file