2025-03-25 06:17:41 +00:00
|
|
|
import { useWidgetStore } from "../../../store/useWidgetStore";
|
2025-03-25 10:25:48 +00:00
|
|
|
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";
|
2025-03-27 05:24:40 +00:00
|
|
|
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;
|
|
|
|
}
|
2025-03-25 06:17:41 +00:00
|
|
|
|
2025-03-25 10:25:48 +00:00
|
|
|
export const DraggableWidget = ({
|
|
|
|
widget,
|
|
|
|
hiddenPanels, // Add this prop to track hidden panels
|
2025-03-27 05:24:40 +00:00
|
|
|
index,
|
|
|
|
onReorder,
|
|
|
|
openKebabId,
|
|
|
|
setOpenKebabId,
|
|
|
|
selectedZone,
|
|
|
|
setSelectedZone,
|
2025-03-25 10:25:48 +00:00
|
|
|
}: {
|
2025-03-27 05:24:40 +00:00
|
|
|
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[];
|
|
|
|
}>
|
|
|
|
>;
|
2025-03-25 10:25:48 +00:00
|
|
|
widget: any;
|
|
|
|
hiddenPanels: string[]; // Array of hidden panel names
|
2025-03-27 05:24:40 +00:00
|
|
|
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
|
2025-03-25 10:25:48 +00:00
|
|
|
}) => {
|
2025-03-25 06:17:41 +00:00
|
|
|
const { selectedChartId, setSelectedChartId } = useWidgetStore();
|
|
|
|
|
|
|
|
const handlePointerDown = () => {
|
|
|
|
if (selectedChartId?.id !== widget.id) {
|
|
|
|
setSelectedChartId(widget);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-03-25 10:25:48 +00:00
|
|
|
// Determine if the widget's panel is hidden
|
|
|
|
const isPanelHidden = hiddenPanels.includes(widget.panel);
|
|
|
|
|
2025-03-26 06:58:22 +00:00
|
|
|
const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
|
2025-03-27 05:24:40 +00:00
|
|
|
event.dataTransfer.setData("text/plain", index.toString()); // Store the index of the dragged widget
|
2025-03-26 06:58:22 +00:00
|
|
|
};
|
|
|
|
const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
|
|
|
|
event.preventDefault(); // Allow drop
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
|
|
|
|
event.preventDefault(); // Allow drop
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
|
|
|
event.preventDefault();
|
2025-03-27 05:24:40 +00:00
|
|
|
const fromIndex = parseInt(event.dataTransfer.getData("text/plain"), 10); // Get the dragged widget's index
|
2025-03-26 06:58:22 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
};
|
2025-03-27 05:24:40 +00:00
|
|
|
// Handle kebab icon click to toggle kebab options
|
|
|
|
const handleKebabClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
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
|
|
|
|
);
|
2025-03-26 06:58:22 +00:00
|
|
|
|
2025-03-27 05:24:40 +00:00
|
|
|
// 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<HTMLDivElement | null>(null);
|
2025-03-26 06:58:22 +00:00
|
|
|
|
2025-03-27 05:24:40 +00:00
|
|
|
// 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);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2025-03-25 06:17:41 +00:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div
|
2025-03-26 06:58:22 +00:00
|
|
|
draggable
|
2025-03-25 06:17:41 +00:00
|
|
|
key={widget.id}
|
2025-03-27 05:24:40 +00:00
|
|
|
className={`chart-container ${
|
|
|
|
selectedChartId?.id === widget.id && "activeChart"
|
|
|
|
}`}
|
2025-03-25 06:17:41 +00:00
|
|
|
onPointerDown={handlePointerDown}
|
2025-03-26 06:58:22 +00:00
|
|
|
onDragStart={handleDragStart}
|
|
|
|
onDragEnter={handleDragEnter}
|
|
|
|
onDragOver={handleDragOver}
|
|
|
|
onDrop={handleDrop}
|
2025-03-25 10:25:48 +00:00
|
|
|
style={{
|
|
|
|
opacity: isPanelHidden ? 0 : 1, // Set opacity to 0 if the panel is hidden
|
|
|
|
pointerEvents: isPanelHidden ? "none" : "auto", // Disable interaction when hidden
|
|
|
|
}}
|
2025-03-25 06:17:41 +00:00
|
|
|
>
|
2025-03-27 05:24:40 +00:00
|
|
|
{/* Kebab Icon */}
|
|
|
|
<div className="icon kebab" onClick={handleKebabClick}>
|
|
|
|
<KebabIcon />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Kebab Options */}
|
|
|
|
{openKebabId === widget.id && (
|
|
|
|
<div
|
|
|
|
className="kebab-options"
|
|
|
|
ref={widgetRef} // Attach the ref to the widget container
|
|
|
|
>
|
|
|
|
<div className="edit btn">
|
|
|
|
<div className="icon">
|
|
|
|
<DublicateIcon />
|
|
|
|
</div>
|
|
|
|
<div className="label">Duplicate</div>
|
|
|
|
</div>
|
|
|
|
<div className="edit btn" onClick={deleteSelectedChart}>
|
|
|
|
<div className="icon">
|
|
|
|
<DeleteIcon />
|
|
|
|
</div>
|
|
|
|
<div className="label">Delete</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
2025-03-25 10:25:48 +00:00
|
|
|
{/* Render charts based on widget type */}
|
|
|
|
{widget.type === "progress" && (
|
|
|
|
<ProgressCard title={widget.title} data={widget.data} />
|
|
|
|
)}
|
|
|
|
{widget.type === "line" && (
|
|
|
|
<LineGraphComponent
|
|
|
|
type={widget.type}
|
|
|
|
title={widget.title}
|
|
|
|
fontSize={widget.fontSize}
|
|
|
|
fontWeight={widget.fontWeight}
|
|
|
|
data={{
|
|
|
|
measurements: [
|
|
|
|
{ name: "testDevice", fields: "powerConsumption" },
|
|
|
|
{ name: "furnace", fields: "powerConsumption" },
|
|
|
|
],
|
|
|
|
interval: 1000,
|
|
|
|
duration: "1h",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{widget.type === "bar" && (
|
|
|
|
<BarGraphComponent
|
|
|
|
type={widget.type}
|
|
|
|
title={widget.title}
|
|
|
|
fontSize={widget.fontSize}
|
|
|
|
fontWeight={widget.fontWeight}
|
|
|
|
data={{
|
|
|
|
measurements: [
|
|
|
|
{ name: "testDevice", fields: "powerConsumption" },
|
|
|
|
{ name: "furnace", fields: "powerConsumption" },
|
|
|
|
],
|
|
|
|
interval: 1000,
|
|
|
|
duration: "1h",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{/* {widget.type === "radar" && (
|
|
|
|
<RadarGraphComponent
|
|
|
|
type={widget.type}
|
|
|
|
title={widget.title}
|
|
|
|
fontSize={widget.fontSize}
|
|
|
|
fontWeight={widget.fontWeight}
|
|
|
|
data={widget.data.measurements.map((item: any) => item.fields)}
|
|
|
|
/>
|
|
|
|
)} */}
|
|
|
|
{widget.type === "pie" && (
|
|
|
|
<PieGraphComponent
|
|
|
|
type={widget.type}
|
|
|
|
title={widget.title}
|
|
|
|
fontSize={widget.fontSize}
|
|
|
|
fontWeight={widget.fontWeight}
|
|
|
|
data={{
|
|
|
|
measurements: [
|
|
|
|
{ name: "testDevice", fields: "powerConsumption" },
|
|
|
|
{ name: "furnace", fields: "powerConsumption" },
|
|
|
|
],
|
|
|
|
interval: 1000,
|
|
|
|
duration: "1h",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{widget.type === "doughnut" && (
|
|
|
|
<DoughnutGraphComponent
|
|
|
|
type={widget.type}
|
|
|
|
title={widget.title}
|
|
|
|
fontSize={widget.fontSize}
|
|
|
|
fontWeight={widget.fontWeight}
|
|
|
|
data={{
|
|
|
|
measurements: [
|
|
|
|
{ name: "testDevice", fields: "powerConsumption" },
|
|
|
|
{ name: "furnace", fields: "powerConsumption" },
|
|
|
|
],
|
|
|
|
interval: 1000,
|
|
|
|
duration: "1h",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{widget.type === "polarArea" && (
|
|
|
|
<PolarAreaGraphComponent
|
|
|
|
type={widget.type}
|
|
|
|
title={widget.title}
|
|
|
|
fontSize={widget.fontSize}
|
|
|
|
fontWeight={widget.fontWeight}
|
|
|
|
data={{
|
|
|
|
measurements: [
|
|
|
|
{ name: "testDevice", fields: "powerConsumption" },
|
|
|
|
{ name: "furnace", fields: "powerConsumption" },
|
|
|
|
],
|
|
|
|
interval: 1000,
|
|
|
|
duration: "1h",
|
|
|
|
}}
|
|
|
|
/>
|
2025-03-25 06:17:41 +00:00
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|