updated realTimeViz componet sturucture and data
This commit is contained in:
parent
92676cd12c
commit
b818a00c64
|
@ -1474,3 +1474,31 @@ export function RemoveIcon() {
|
|||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlayIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.9111 8.51444C17.363 9.37988 17.363 11.6201 15.9111 12.4856L7.14505 17.7109C5.73403 18.552 4 17.4572 4 15.7253V5.27468C4 3.54276 5.73403 2.44801 7.14505 3.28911L15.9111 8.51444Z"
|
||||
stroke="#1C274C"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommentIcon() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.15482 16.3138L3.30127 17.1673H4.50838H10H10.0004C11.6584 17.166 13.2646 16.5899 14.5455 15.5371C15.8263 14.4843 16.7025 13.02 17.0248 11.3937C17.3471 9.76732 17.0955 8.07954 16.3129 6.61789C15.5303 5.15623 14.2651 4.01113 12.7329 3.37768C11.2007 2.74423 9.4963 2.66161 7.91002 3.14392C6.32375 3.62622 4.95377 4.6436 4.0335 6.02272C3.11322 7.40184 2.69958 9.05738 2.86306 10.7073L2.86513 10.7282L2.86895 10.7489C3.0346 11.6451 3.22308 12.3554 3.53795 13.0301C3.85334 13.7059 4.28373 14.3209 4.90409 15.0431L4.91809 15.0594L4.93343 15.0744C4.99514 15.1349 5.03091 15.2169 5.03333 15.3032C5.03257 15.3457 5.02367 15.3877 5.0071 15.4269L5.46767 15.6215L5.0071 15.4269C4.98993 15.4676 4.96488 15.5044 4.93338 15.5352L4.93336 15.5352L4.92982 15.5388L4.15482 16.3138Z" stroke="#2B3344"/>
|
||||
<path d="M6.66663 8.33398H13.3333" stroke="#2B3344" stroke-linecap="round"/>
|
||||
<path d="M6.66663 11.666H13.3333" stroke="#2B3344" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
|
|
|
@ -76,6 +76,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.activeChart {
|
||||
// outline: 1px solid #4a90e2;
|
||||
border-color: #4a90e2 !important;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
@ -83,6 +88,18 @@
|
|||
background-color: rgb(235, 235, 235);
|
||||
margin: 0 30px;
|
||||
|
||||
.realTimeViz-tools {
|
||||
position: absolute;
|
||||
top: -20%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
box-shadow: 0px 4px 8px 0px #3C3C431A;
|
||||
background: #FCFDFD;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.zoon-wrapper {
|
||||
display: flex;
|
||||
background-color: #E0DFFF80;
|
||||
|
@ -448,7 +465,28 @@
|
|||
align-items: center;
|
||||
background-color: #FCFDFD;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
|
||||
.icon {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
fill: none;
|
||||
background-color: #4a90e2;
|
||||
|
||||
path {
|
||||
fill: var(--primary-color);
|
||||
stroke: var(--primary-color);
|
||||
// stroke-width: .5px;
|
||||
}
|
||||
}
|
||||
|
||||
.extra-Bs {
|
||||
align-items: center;
|
||||
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.side-button {
|
||||
|
@ -488,10 +526,12 @@
|
|||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
|
||||
.extra-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -500,6 +540,10 @@
|
|||
right: -42px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.bottom.side-button-container {
|
||||
|
@ -507,10 +551,12 @@
|
|||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
|
||||
.extra-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -519,4 +565,50 @@
|
|||
left: -42px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
||||
.extra-Bs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Add transitions to smoothen state changes */
|
||||
.content-container {
|
||||
transition: all 0.3s ease;
|
||||
/* Adjust duration and easing */
|
||||
}
|
||||
|
||||
.main-container {
|
||||
transition: height 0.3s ease, margin 0.3s ease;
|
||||
/* Smooth transition for height and margin */
|
||||
}
|
||||
|
||||
.zoon-wrapper {
|
||||
transition: transform 0.3s ease;
|
||||
/* Smooth transition for transform */
|
||||
}
|
||||
|
||||
.side-bar {
|
||||
transition: transform 0.3s ease;
|
||||
/* Slide in/out transition */
|
||||
}
|
||||
|
||||
.side-bar.hidden {
|
||||
transform: translateX(-100%);
|
||||
/* Move sidebar off-screen when hidden */
|
||||
}
|
||||
|
||||
.side-bar.visible {
|
||||
transform: translateX(0);
|
||||
/* Move sidebar back in */
|
||||
}
|
||||
|
||||
.realTimeViz-tools {
|
||||
transition: top 0.3s ease;
|
||||
/* Smooth transition for tool position */
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
|
||||
interface DropdownProps {
|
||||
header: string;
|
||||
|
@ -13,6 +13,7 @@ const RegularDropDown: React.FC<DropdownProps> = ({
|
|||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null); // Create a ref for the dropdown container
|
||||
|
||||
// Reset selectedOption when the dropdown closes
|
||||
useEffect(() => {
|
||||
|
@ -26,6 +27,22 @@ const RegularDropDown: React.FC<DropdownProps> = ({
|
|||
setSelectedOption(null); // Ensure the dropdown reflects the updated header
|
||||
}, [header]);
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false); // Close the dropdown if clicked outside
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
// Cleanup the event listener on component unmount
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
@ -37,7 +54,7 @@ const RegularDropDown: React.FC<DropdownProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="regularDropdown-container">
|
||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||
{/* Dropdown Header */}
|
||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||
<div className="key">{selectedOption || header}</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import RegularDropDown from "../../inputs/regularDropDown";
|
||||
import { LinkIcon, RemoveIcon } from "../../../../assets/images/svgExports";
|
||||
import { RemoveIcon } from "../../../../assets/images/svgExports";
|
||||
import { useWidgetStore } from "../../../../store/store";
|
||||
|
||||
interface Child {
|
||||
|
@ -16,7 +16,7 @@ interface Group {
|
|||
|
||||
const Data = () => {
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const [selectedWidget] = useState("Widget 1");
|
||||
useEffect(() => {}, [selectedChartId]);
|
||||
|
||||
// State to store groups for all widgets (using Widget.id as keys)
|
||||
const [chartDataGroups, setChartDataGroups] = useState<
|
||||
|
@ -24,8 +24,6 @@ const Data = () => {
|
|||
>({});
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Selected Chart ID:", selectedChartId);
|
||||
|
||||
// Initialize data groups for the newly selected widget if it doesn't exist
|
||||
if (selectedChartId && !chartDataGroups[selectedChartId.id]) {
|
||||
setChartDataGroups((prev) => ({
|
||||
|
@ -45,14 +43,28 @@ const Data = () => {
|
|||
}
|
||||
}, [selectedChartId]);
|
||||
|
||||
// Handle linking children between groups
|
||||
const handleLinkClick = (childId: number) => {};
|
||||
// Handle adding a new child to the group
|
||||
const handleAddClick = (groupId: number) => {
|
||||
setChartDataGroups((prevGroups) => {
|
||||
const currentGroups = prevGroups[selectedChartId.id] || [];
|
||||
const group = currentGroups.find((g) => g.id === groupId);
|
||||
|
||||
if (group && group.children.length < 7) {
|
||||
const newChild = { id: Date.now(), easing: "Linear" };
|
||||
return {
|
||||
...prevGroups,
|
||||
[selectedChartId.id]: currentGroups.map((g) =>
|
||||
g.id === groupId ? { ...g, children: [...g.children, newChild] } : g
|
||||
),
|
||||
};
|
||||
}
|
||||
return prevGroups;
|
||||
});
|
||||
};
|
||||
|
||||
// Remove a child from a group
|
||||
const removeChild = (groupId: number, childId: number) => {
|
||||
setChartDataGroups((currentGroups) => {
|
||||
if (!selectedChartId) return currentGroups;
|
||||
|
||||
const currentChartData = currentGroups[selectedChartId.id] || [];
|
||||
|
||||
return {
|
||||
|
@ -61,8 +73,8 @@ const Data = () => {
|
|||
group.id === groupId
|
||||
? {
|
||||
...group,
|
||||
children: group.children.map((child) =>
|
||||
child.id === childId ? { ...child, easing: "Linear" } : child
|
||||
children: group.children.filter(
|
||||
(child) => child.id !== childId
|
||||
),
|
||||
}
|
||||
: group
|
||||
|
@ -71,7 +83,6 @@ const Data = () => {
|
|||
});
|
||||
};
|
||||
|
||||
console.log("selectedChartId: ", selectedChartId?.title);
|
||||
return (
|
||||
<div className="dataSideBar">
|
||||
{selectedChartId?.title && (
|
||||
|
@ -80,50 +91,30 @@ const Data = () => {
|
|||
{selectedChartId &&
|
||||
chartDataGroups[selectedChartId.id]?.map((group) => (
|
||||
<div key={group.id} className="selectedMain-container">
|
||||
{/* <div className="selectedMain">
|
||||
<span className="bulletPoint">•</span>
|
||||
<main>Data from</main>
|
||||
<RegularDropDown
|
||||
header={group.easing}
|
||||
options={[
|
||||
"Connecter 1",
|
||||
"Connecter 2",
|
||||
"Connecter 3",
|
||||
"Connecter 4",
|
||||
]}
|
||||
onSelect={(easing) => {
|
||||
setChartDataGroups((prev) => ({
|
||||
...prev,
|
||||
[selectedChartId.id]: prev[selectedChartId.id].map((g) =>
|
||||
g.id === group.id ? { ...g, easing } : g
|
||||
),
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div> */}
|
||||
{/* Render children only if there is a selected chart */}
|
||||
{group.children.map((child) => (
|
||||
{group.children.map((child, index) => (
|
||||
<div key={child.id} className="selectedMain child">
|
||||
<main>Input</main>
|
||||
<main>Input {index + 1}</main>
|
||||
<RegularDropDown
|
||||
header={child.easing}
|
||||
options={["Linear", "Ease In", "Ease Out", "Ease In-Out"]}
|
||||
onSelect={(easing) => {
|
||||
setChartDataGroups((prev) => ({
|
||||
...prev,
|
||||
[selectedChartId.id]: prev[selectedChartId.id].map(
|
||||
(g) => ({
|
||||
[selectedChartId.id]: prev[selectedChartId.id].map((g) =>
|
||||
g.id === group.id
|
||||
? {
|
||||
...g,
|
||||
children: g.children.map((c) =>
|
||||
c.id === child.id ? { ...c, easing } : c
|
||||
),
|
||||
})
|
||||
}
|
||||
: g
|
||||
),
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
<div className="icon" onClick={() => handleLinkClick(child.id)}>
|
||||
<LinkIcon />
|
||||
<div className="icon" onClick={() => handleAddClick(group.id)}>
|
||||
+
|
||||
</div>
|
||||
<div
|
||||
className="icon"
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useWidgetStore } from "../../../../store/store";
|
|||
import ChartComponent from "./chartComponent";
|
||||
|
||||
// Define Props Interface
|
||||
interface DesignProps {}
|
||||
|
||||
interface Widget {
|
||||
id: string;
|
||||
type: string; // Chart type (e.g., "bar", "line")
|
||||
|
@ -78,13 +78,13 @@ const Design = () => {
|
|||
|
||||
{/* Chart Component */}
|
||||
<div className="reviewChart">
|
||||
{/* {selectedChartId && (
|
||||
{selectedChartId && (
|
||||
<ChartComponent
|
||||
type={selectedChartId.type}
|
||||
title={selectedChartId.title}
|
||||
data={selectedChartId.data || defaultChartData} // Use widget data or default
|
||||
/>
|
||||
)} */}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Options Container */}
|
||||
|
|
|
@ -61,7 +61,8 @@ const Widgets = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Chart Widgets */}
|
||||
{/* Chart Widgets */}.
|
||||
{viewMode === "2D" && (
|
||||
<div className="chart-container">
|
||||
{chartTypes.map((type, index) => {
|
||||
const widgetTitle = `Widget ${index + 1}`;
|
||||
|
@ -187,10 +188,11 @@ const Widgets = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{viewMode === "3D" && <></>}
|
||||
{viewMode === "Floating" && <></>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Widgets;
|
||||
|
||||
// along with my charts i need to additionallly drag and drop my 2 widget 7 and widget 8 styled card to pannel
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
import React from "react";
|
||||
import { CleanPannel, EyeIcon, LockIcon } from "../../assets/images/svgExports";
|
||||
|
||||
// Define the type for `Side`
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
// Define the type for the props passed to the Buttons component
|
||||
interface ButtonsProps {
|
||||
selectedZone: {
|
||||
zoneName: string; // Add zoneName property
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
};
|
||||
setSelectedZone: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
zoneName: string; // Ensure zoneName is included in the state type
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
const AddButtons: React.FC<ButtonsProps> = ({
|
||||
selectedZone,
|
||||
setSelectedZone,
|
||||
}) => {
|
||||
// Function to toggle lock/unlock a panel
|
||||
const toggleLockPanel = (side: Side) => {
|
||||
const newLockedPanels = selectedZone.lockedPanels.includes(side)
|
||||
? selectedZone.lockedPanels.filter((panel) => panel !== side)
|
||||
: [...selectedZone.lockedPanels, side];
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
lockedPanels: newLockedPanels,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
};
|
||||
|
||||
// Function to toggle visibility of a panel
|
||||
const toggleVisibility = (side: Side) => {
|
||||
const newActiveSides = selectedZone.activeSides.includes(side)
|
||||
? selectedZone.activeSides.filter((s) => s !== side)
|
||||
: [...selectedZone.activeSides, side];
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
activeSides: newActiveSides,
|
||||
panelOrder: newActiveSides,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
};
|
||||
|
||||
// Function to clean all widgets from a panel
|
||||
const cleanPanel = (side: Side) => {
|
||||
const cleanedWidgets = selectedZone.widgets.filter(
|
||||
(widget) => widget.panel !== side
|
||||
);
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
widgets: cleanedWidgets,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
};
|
||||
|
||||
// Function to handle "+" button click
|
||||
const handlePlusButtonClick = (side: Side) => {
|
||||
if (selectedZone.activeSides.includes(side)) {
|
||||
// If the panel is already active, remove all widgets and close the panel
|
||||
const cleanedWidgets = selectedZone.widgets.filter(
|
||||
(widget) => widget.panel !== side
|
||||
);
|
||||
const newActiveSides = selectedZone.activeSides.filter((s) => s !== side);
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
widgets: cleanedWidgets,
|
||||
activeSides: newActiveSides,
|
||||
panelOrder: newActiveSides,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
} else {
|
||||
// If the panel is not active, activate it
|
||||
const newActiveSides = [...selectedZone.activeSides, side];
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
activeSides: newActiveSides,
|
||||
panelOrder: newActiveSides,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(["top", "right", "bottom", "left"] as Side[]).map((side) => (
|
||||
<div key={side} className={`side-button-container ${side}`}>
|
||||
{/* "+" Button */}
|
||||
<button
|
||||
className={`side-button ${side}`}
|
||||
onClick={() => handlePlusButtonClick(side)}
|
||||
title={
|
||||
selectedZone.activeSides.includes(side)
|
||||
? `Remove all items and close ${side} panel`
|
||||
: `Activate ${side} panel`
|
||||
}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
|
||||
{/* Extra Buttons */}
|
||||
<div
|
||||
className="extra-Bs"
|
||||
style={{
|
||||
display: selectedZone.activeSides.includes(side)
|
||||
? "flex"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
{/* Hide Panel */}
|
||||
<div
|
||||
className="icon"
|
||||
title="Hide Panel"
|
||||
onClick={() => toggleVisibility(side)}
|
||||
>
|
||||
<EyeIcon />
|
||||
</div>
|
||||
|
||||
{/* Clean Panel */}
|
||||
<div
|
||||
className="icon"
|
||||
title="Clean Panel"
|
||||
onClick={() => cleanPanel(side)}
|
||||
>
|
||||
<CleanPannel />
|
||||
</div>
|
||||
|
||||
{/* Lock/Unlock Panel */}
|
||||
<div
|
||||
className={`icon ${
|
||||
selectedZone.lockedPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
title={
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "Unlock Panel"
|
||||
: "Lock Panel"
|
||||
}
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<LockIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddButtons;
|
|
@ -0,0 +1,240 @@
|
|||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import ChartComponent from "../../components/ui/sideBar/realTimeViz/chartComponent";
|
||||
import { ProgressCard } from "./progressCard";
|
||||
import { useWidgetStore } from "../../store/store";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: widget.id });
|
||||
const { selectedChartId, setSelectedChartId } = useWidgetStore();
|
||||
|
||||
// State for managing the popup visibility and customization options
|
||||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||||
const [customizationOptions, setCustomizationOptions] = useState({
|
||||
templateBackground: "#ffffff",
|
||||
cardBackground: "#ffffff",
|
||||
cardOpacity: 1,
|
||||
cardBlur: 0,
|
||||
font: "Arial",
|
||||
margin: 0,
|
||||
radius: 5,
|
||||
shadow: "Low",
|
||||
});
|
||||
|
||||
const style = useMemo(
|
||||
() => ({
|
||||
transform: transform
|
||||
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
|
||||
: undefined,
|
||||
transition: transition || "transform 200ms ease",
|
||||
}),
|
||||
[transform, transition]
|
||||
);
|
||||
|
||||
const handlePointerDown = () => {
|
||||
if (!isDragging) {
|
||||
setSelectedChartId(widget);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle double-click to open the popup
|
||||
const handleDoubleClick = () => {
|
||||
setIsPopupOpen(true);
|
||||
};
|
||||
|
||||
// Close the popup
|
||||
const handleClosePopup = () => {
|
||||
setIsPopupOpen(false);
|
||||
};
|
||||
|
||||
// Save the changes made in the popup
|
||||
const handleSaveChanges = () => {
|
||||
// Here you can save the customizationOptions to your store or API
|
||||
console.log("Saved Customization Options:", customizationOptions);
|
||||
setIsPopupOpen(false);
|
||||
};
|
||||
|
||||
// Compute dynamic card styles based on customizationOptions
|
||||
const cardStyle = useMemo(() => {
|
||||
const shadowLevels = {
|
||||
Low: "0px 2px 4px rgba(0, 0, 0, 0.1)",
|
||||
Medium: "0px 4px 8px rgba(0, 0, 0, 0.2)",
|
||||
High: "0px 8px 16px rgba(0, 0, 0, 0.3)",
|
||||
};
|
||||
|
||||
return {
|
||||
backgroundColor: customizationOptions.cardBackground,
|
||||
opacity: customizationOptions.cardOpacity,
|
||||
filter: `blur(${customizationOptions.cardBlur}px)`,
|
||||
fontFamily: customizationOptions.font,
|
||||
margin: `${customizationOptions.margin}px`,
|
||||
borderRadius: `${customizationOptions.radius}px`,
|
||||
// boxShadow: shadowLevels[customizationOptions.shadow],
|
||||
};
|
||||
}, [customizationOptions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={widget.id}
|
||||
className={`chart-container ${
|
||||
selectedChartId?.id === widget.id && "activeChart"
|
||||
}`}
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
style={{ ...style, ...cardStyle }} // Apply dynamic card styles here
|
||||
onPointerDown={handlePointerDown}
|
||||
onDoubleClick={handleDoubleClick} // Add double-click event
|
||||
>
|
||||
{widget.type === "progress" ? (
|
||||
<ProgressCard title={widget.title} data={widget.data} />
|
||||
) : (
|
||||
<ChartComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontFamily={customizationOptions.font} // Pass font customization to ChartComponent
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={widget.data}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Popup for Customizing Template Theme */}
|
||||
{/* {isPopupOpen && (
|
||||
<div className="popup-overlay">
|
||||
<div className="popup-content">
|
||||
<h2>Customize Template Theme</h2>
|
||||
<div className="form-group">
|
||||
<label>Template Background</label>
|
||||
<input
|
||||
type="color"
|
||||
value={customizationOptions.templateBackground}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
templateBackground: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Card Background</label>
|
||||
<input
|
||||
type="color"
|
||||
value={customizationOptions.cardBackground}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
cardBackground: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Card Opacity</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={customizationOptions.cardOpacity}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
cardOpacity: parseFloat(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Card Blur</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="10"
|
||||
value={customizationOptions.cardBlur}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
cardBlur: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Font</label>
|
||||
<select
|
||||
value={customizationOptions.font}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
font: e.target.value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="Times New Roman">Times New Roman</option>
|
||||
<option value="Courier New">Courier New</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Margin</label>
|
||||
<input
|
||||
type="number"
|
||||
value={customizationOptions.margin}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
margin: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Radius</label>
|
||||
<input
|
||||
type="number"
|
||||
value={customizationOptions.radius}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
radius: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Shadow</label>
|
||||
<select
|
||||
value={customizationOptions.shadow}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
shadow: e.target.value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="Low">Low</option>
|
||||
<option value="Medium">Medium</option>
|
||||
<option value="High">High</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="popup-actions">
|
||||
<button onClick={handleClosePopup}>Cancel</button>
|
||||
<button onClick={handleSaveChanges}>Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,196 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { DraggableWidget } from "./draggableWidget";
|
||||
import { useWidgetStore } from "../../store/store";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface PanelProps {
|
||||
selectedZone: {
|
||||
zoneName: string; // Add zoneName property
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
};
|
||||
setSelectedZone: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
zoneName: string; // Ensure zoneName is included in the state type
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
const generateUniqueId = () =>
|
||||
`${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({});
|
||||
const [panelDimensions, setPanelDimensions] = useState<{
|
||||
[side in Side]?: { width: number; height: number };
|
||||
}>({});
|
||||
|
||||
const getPanelStyle = useMemo(
|
||||
() => (side: Side) => {
|
||||
const currentIndex = selectedZone.panelOrder.indexOf(side);
|
||||
const previousPanels = selectedZone.panelOrder.slice(0, currentIndex);
|
||||
const leftActive = previousPanels.includes("left");
|
||||
const rightActive = previousPanels.includes("right");
|
||||
const topActive = previousPanels.includes("top");
|
||||
const bottomActive = previousPanels.includes("bottom");
|
||||
|
||||
switch (side) {
|
||||
case "top":
|
||||
case "bottom":
|
||||
return {
|
||||
width: `calc(100% - ${
|
||||
(leftActive ? 204 : 0) + (rightActive ? 204 : 0)
|
||||
}px)`,
|
||||
left: leftActive ? "204px" : "0",
|
||||
right: rightActive ? "204px" : "0",
|
||||
[side]: "0",
|
||||
height: "200px",
|
||||
};
|
||||
case "left":
|
||||
case "right":
|
||||
return {
|
||||
height: `calc(100% - ${
|
||||
(topActive ? 204 : 0) + (bottomActive ? 204 : 0)
|
||||
}px)`,
|
||||
top: topActive ? "204px" : "0",
|
||||
bottom: bottomActive ? "204px" : "0",
|
||||
[side]: "0",
|
||||
width: "200px",
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
},
|
||||
[selectedZone.panelOrder]
|
||||
);
|
||||
|
||||
const handleDrop = (e: React.DragEvent, panel: Side) => {
|
||||
e.preventDefault();
|
||||
const { draggedAsset } = useWidgetStore.getState();
|
||||
if (draggedAsset) {
|
||||
if (selectedZone.lockedPanels.includes(panel)) return;
|
||||
|
||||
const currentWidgetsInPanel = selectedZone.widgets.filter(
|
||||
(w) => w.panel === panel
|
||||
).length;
|
||||
|
||||
const dimensions = panelDimensions[panel];
|
||||
const CHART_WIDTH = 200; // Width of each chart for top/bottom panels
|
||||
const CHART_HEIGHT = 200; // Height of each chart for left/right panels
|
||||
let maxCharts = 0;
|
||||
|
||||
if (dimensions) {
|
||||
if (panel === "top" || panel === "bottom") {
|
||||
maxCharts = Math.floor(dimensions.width / CHART_WIDTH); // Use width for top/bottom
|
||||
} else {
|
||||
maxCharts = Math.floor(dimensions.height / CHART_HEIGHT); // Use height for left/right
|
||||
}
|
||||
} else {
|
||||
maxCharts = panel === "top" || panel === "bottom" ? 5 : 3; // Default values
|
||||
}
|
||||
|
||||
if (currentWidgetsInPanel >= maxCharts) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
widgets: [
|
||||
...selectedZone.widgets,
|
||||
{
|
||||
...draggedAsset,
|
||||
id: generateUniqueId(),
|
||||
panel,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const observers: ResizeObserver[] = [];
|
||||
const currentPanelRefs = panelRefs.current;
|
||||
|
||||
selectedZone.activeSides.forEach((side) => {
|
||||
const element = currentPanelRefs[side];
|
||||
if (element) {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setPanelDimensions((prev) => ({
|
||||
...prev,
|
||||
[side]: { width, height },
|
||||
}));
|
||||
}
|
||||
});
|
||||
observer.observe(element);
|
||||
observers.push(observer);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
observers.forEach((observer) => observer.disconnect());
|
||||
};
|
||||
}, [selectedZone.activeSides]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedZone.activeSides.map((side) => (
|
||||
<div
|
||||
key={side}
|
||||
className={`panel ${side}-panel absolute`}
|
||||
style={getPanelStyle(side)} // Pass `side` to `getPanelStyle`
|
||||
onDrop={(e) => handleDrop(e, side)}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
ref={(el) => {
|
||||
if (el) {
|
||||
panelRefs.current[side] = el;
|
||||
} else {
|
||||
delete panelRefs.current[side];
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="panel-content"
|
||||
style={{
|
||||
pointerEvents: selectedZone.lockedPanels.includes(side)
|
||||
? "none"
|
||||
: "auto",
|
||||
opacity: selectedZone.lockedPanels.includes(side) ? "0.8" : "1",
|
||||
}}
|
||||
>
|
||||
{selectedZone.widgets
|
||||
.filter((w) => w.panel === side)
|
||||
.map((widget) => (
|
||||
<DraggableWidget widget={widget} key={widget.id} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Panel;
|
|
@ -1,6 +1,9 @@
|
|||
import React from "react";
|
||||
|
||||
export const ProgressCard = ({ title, data }: {
|
||||
export const ProgressCard = ({
|
||||
title,
|
||||
data,
|
||||
}: {
|
||||
title: string;
|
||||
data: { stocks: Array<{ key: string; value: number; description: string }> };
|
||||
}) => (
|
|
@ -1,94 +1,14 @@
|
|||
import React, { useMemo, useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
SortableContext,
|
||||
verticalListSortingStrategy,
|
||||
arrayMove,
|
||||
useSortable,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { useWidgetStore } from "../../store/store";
|
||||
import ChartComponent from "../../components/ui/sideBar/realTimeViz/chartComponent";
|
||||
import { useState, useEffect } from "react";
|
||||
import { usePlayButtonStore, useWidgetStore } from "../../store/store";
|
||||
import SideBar from "../../components/layout/sideBar";
|
||||
import {
|
||||
CleanPannel,
|
||||
DisableSorting,
|
||||
EyeIcon,
|
||||
LockIcon,
|
||||
} from "../../assets/images/svgExports";
|
||||
import { ProgressCard } from "./progressCard ";
|
||||
import Panel from "./panel";
|
||||
import AddButtons from "./addButtons";
|
||||
import { CommentIcon, PlayIcon } from "../../assets/images/svgExports";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
const generateUniqueId = () =>
|
||||
`${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: widget.id });
|
||||
const { setSelectedChartId } = useWidgetStore();
|
||||
|
||||
const style = useMemo(
|
||||
() => ({
|
||||
transform: transform
|
||||
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
|
||||
: undefined,
|
||||
transition: transition || "transform 200ms ease",
|
||||
}),
|
||||
[transform, transition]
|
||||
);
|
||||
|
||||
const handlePointerDown = () => {
|
||||
if (!isDragging) {
|
||||
setSelectedChartId(widget);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={widget.id}
|
||||
className="chart-container"
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
style={style}
|
||||
onPointerDown={handlePointerDown}
|
||||
>
|
||||
{widget.type === "progress" ? (
|
||||
<ProgressCard title={widget.title} data={widget.data} />
|
||||
) : (
|
||||
<ChartComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontFamily={widget.fontFamily}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={widget.data}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RealTimeVisualization = () => {
|
||||
const [selectedZone, setSelectedZone] = useState("Manufacturing unit");
|
||||
const [panelDimensions, setPanelDimensions] = useState<{
|
||||
[side in Side]?: { width: number; height: number };
|
||||
}>({});
|
||||
const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({});
|
||||
|
||||
// Dummy database for all zones
|
||||
const [zonesData, setZonesData] = useState<{
|
||||
[key: string]: {
|
||||
activeSides: Side[];
|
||||
|
@ -135,352 +55,153 @@ const RealTimeVisualization = () => {
|
|||
},
|
||||
});
|
||||
|
||||
useEffect(() => {}, [zonesData]);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const observers: ResizeObserver[] = [];
|
||||
const currentPanelRefs = panelRefs.current;
|
||||
|
||||
zonesData[selectedZone].activeSides.forEach((side) => {
|
||||
const element = currentPanelRefs[side];
|
||||
if (element) {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setPanelDimensions((prev) => ({
|
||||
...prev,
|
||||
[side]: { width, height },
|
||||
}));
|
||||
}
|
||||
});
|
||||
observer.observe(element);
|
||||
observers.push(observer);
|
||||
}
|
||||
// State to hold the currently selected zone's data, including the zone name
|
||||
const [selectedZone, setSelectedZone] = useState<{
|
||||
zoneName: string; // Add zoneName property
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
}>({
|
||||
zoneName: "Manufacturing unit",
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
});
|
||||
|
||||
return () => {
|
||||
observers.forEach((observer) => observer.disconnect());
|
||||
};
|
||||
}, [zonesData[selectedZone].activeSides, selectedZone]);
|
||||
useEffect(() => {}, [selectedZone]);
|
||||
|
||||
const toggleSide = (side: Side) => {
|
||||
setZonesData((prev) => {
|
||||
const zoneData = prev[selectedZone];
|
||||
const newActiveSides = zoneData.activeSides.includes(side)
|
||||
? zoneData.activeSides.filter((s) => s !== side)
|
||||
: [...zoneData.activeSides, side];
|
||||
return {
|
||||
...prev,
|
||||
[selectedZone]: {
|
||||
...zoneData,
|
||||
activeSides: newActiveSides,
|
||||
panelOrder: newActiveSides,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
const { selectedChartId, setSelectedChartId } = useWidgetStore();
|
||||
|
||||
const toggleLockPanel = (side: Side) => {
|
||||
setZonesData((prev) => {
|
||||
const zoneData = prev[selectedZone];
|
||||
const newLockedPanels = zoneData.lockedPanels.includes(side)
|
||||
? zoneData.lockedPanels.filter((panel) => panel !== side)
|
||||
: [...zoneData.lockedPanels, side];
|
||||
return {
|
||||
...prev,
|
||||
[selectedZone]: {
|
||||
...zoneData,
|
||||
lockedPanels: newLockedPanels,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const getPanelStyle = useMemo(
|
||||
() => (side: Side) => {
|
||||
const currentIndex = zonesData[selectedZone].panelOrder.indexOf(side);
|
||||
const previousPanels = zonesData[selectedZone].panelOrder.slice(
|
||||
0,
|
||||
currentIndex
|
||||
);
|
||||
const leftActive = previousPanels.includes("left");
|
||||
const rightActive = previousPanels.includes("right");
|
||||
const topActive = previousPanels.includes("top");
|
||||
const bottomActive = previousPanels.includes("bottom");
|
||||
|
||||
switch (side) {
|
||||
case "top":
|
||||
case "bottom":
|
||||
return {
|
||||
width: `calc(100% - ${
|
||||
(leftActive ? 204 : 0) + (rightActive ? 204 : 0)
|
||||
}px)`,
|
||||
left: leftActive ? "204px" : "0",
|
||||
right: rightActive ? "204px" : "0",
|
||||
[side]: "0",
|
||||
height: "200px",
|
||||
};
|
||||
case "left":
|
||||
case "right":
|
||||
return {
|
||||
height: `calc(100% - ${
|
||||
(topActive ? 204 : 0) + (bottomActive ? 204 : 0)
|
||||
}px)`,
|
||||
top: topActive ? "204px" : "0",
|
||||
bottom: bottomActive ? "204px" : "0",
|
||||
[side]: "0",
|
||||
width: "200px",
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
},
|
||||
[zonesData, selectedZone]
|
||||
);
|
||||
|
||||
const handleDrop = (e: React.DragEvent, panel: Side) => {
|
||||
e.preventDefault();
|
||||
const { draggedAsset } = useWidgetStore.getState();
|
||||
if (draggedAsset) {
|
||||
if (zonesData[selectedZone].lockedPanels.includes(panel)) return;
|
||||
|
||||
const currentWidgetsInPanel = zonesData[selectedZone].widgets.filter(
|
||||
(w) => w.panel === panel
|
||||
).length;
|
||||
|
||||
const dimensions = panelDimensions[panel];
|
||||
const CHART_WIDTH = 200; // Width of each chart for top/bottom panels
|
||||
const CHART_HEIGHT = 200; // Height of each chart for left/right panels
|
||||
let maxCharts = 0;
|
||||
|
||||
if (dimensions) {
|
||||
if (panel === "top" || panel === "bottom") {
|
||||
maxCharts = Math.floor(dimensions.width / CHART_WIDTH); // Use width for top/bottom
|
||||
} else {
|
||||
maxCharts = Math.floor(dimensions.height / CHART_HEIGHT); // Use height for left/right
|
||||
}
|
||||
} else {
|
||||
maxCharts = panel === "top" || panel === "bottom" ? 5 : 3; // Default values
|
||||
}
|
||||
|
||||
if (currentWidgetsInPanel >= maxCharts) {
|
||||
// Function to delete the selected chart
|
||||
const deleteSelectedChart = () => {
|
||||
if (!selectedChartId) {
|
||||
return;
|
||||
}
|
||||
setZonesData((prev) => {
|
||||
const updatedWidgets = selectedZone.widgets.filter(
|
||||
(widget) => widget.id !== selectedChartId.id
|
||||
);
|
||||
return {
|
||||
...prev,
|
||||
[selectedZone.zoneName]: {
|
||||
...selectedZone,
|
||||
widgets: updatedWidgets,
|
||||
},
|
||||
};
|
||||
});
|
||||
setSelectedChartId(null);
|
||||
};
|
||||
|
||||
// Handle keyboard events for delete functionality
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.key === "Delete" || e.key === "Backspace") && selectedChartId) {
|
||||
deleteSelectedChart();
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [selectedChartId]);
|
||||
|
||||
useEffect(() => {
|
||||
setZonesData((prev) => ({
|
||||
...prev,
|
||||
[selectedZone]: {
|
||||
...prev[selectedZone],
|
||||
widgets: [
|
||||
...prev[selectedZone].widgets,
|
||||
{
|
||||
...draggedAsset,
|
||||
id: generateUniqueId(),
|
||||
panel,
|
||||
},
|
||||
],
|
||||
},
|
||||
[selectedZone.zoneName]: selectedZone,
|
||||
}));
|
||||
}
|
||||
};
|
||||
}, [selectedZone]);
|
||||
|
||||
const handleDragEnd = (event: any) => {
|
||||
const { active, over } = event;
|
||||
if (!over) return;
|
||||
const { isPlaying, setIsPlaying } = usePlayButtonStore(); // Access the store's state and setter function
|
||||
|
||||
setZonesData((prev) => {
|
||||
const zoneData = prev[selectedZone];
|
||||
const oldIndex = zoneData.widgets.findIndex(
|
||||
(widget) => widget.id === active.id
|
||||
);
|
||||
const newPanel =
|
||||
zoneData.widgets.find((widget) => widget.id === over.id)?.panel ||
|
||||
active.panel;
|
||||
|
||||
if (active.panel === newPanel) {
|
||||
const newIndex = zoneData.widgets.findIndex(
|
||||
(widget) => widget.id === over.id
|
||||
);
|
||||
const reorderedWidgets = arrayMove(
|
||||
zoneData.widgets,
|
||||
oldIndex,
|
||||
newIndex
|
||||
);
|
||||
return {
|
||||
...prev,
|
||||
[selectedZone]: {
|
||||
...zoneData,
|
||||
widgets: reorderedWidgets,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const updatedWidgets = zoneData.widgets.map((widget) =>
|
||||
widget.id === active.id ? { ...widget, panel: newPanel } : widget
|
||||
);
|
||||
const widgetsInNewPanel = updatedWidgets.filter(
|
||||
(w) => w.panel === newPanel
|
||||
);
|
||||
const newIndex = widgetsInNewPanel.findIndex((w) => w.id === over.id);
|
||||
const reorderedWidgets = arrayMove(updatedWidgets, oldIndex, newIndex);
|
||||
return {
|
||||
...prev,
|
||||
[selectedZone]: {
|
||||
...zoneData,
|
||||
widgets: reorderedWidgets,
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
const isZonesDataEmpty = Object.keys(zonesData).length === 0;
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<>
|
||||
{zonesData ? (
|
||||
<div className="content-container realTime-viz flex">
|
||||
{/* Sidebar for navigation */}
|
||||
{!isPlaying && (
|
||||
<SideBar
|
||||
header={["Overview", "Widgets", "Templates"]}
|
||||
defaultActive={"Widgets"}
|
||||
/>
|
||||
<div className="main-container relative">
|
||||
)}
|
||||
{/* Main container */}
|
||||
<div
|
||||
className="main-container relative"
|
||||
style={{
|
||||
height: !isPlaying ? "600px" : "100%",
|
||||
margin: !isPlaying ? "0 30px" : "0",
|
||||
}} // If isPlaying is true, set height to "100%", else set it to "auto"
|
||||
>
|
||||
<div
|
||||
className="realTimeViz-tools"
|
||||
style={{
|
||||
top: !isPlaying ? "-20%" : "10px",
|
||||
}}
|
||||
>
|
||||
<div className="icon comment">
|
||||
<CommentIcon />
|
||||
</div>
|
||||
<div
|
||||
className="play icon"
|
||||
onClick={() => setIsPlaying(!isPlaying)}
|
||||
>
|
||||
<PlayIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`zoon-wrapper ${
|
||||
zonesData[selectedZone].activeSides.includes("bottom") && "bottom"
|
||||
selectedZone.activeSides.includes("bottom") && "bottom"
|
||||
}`}
|
||||
>
|
||||
{Object.keys(zonesData).map((zone, index) => (
|
||||
{/* Display zones as selectable buttons */}
|
||||
{Object.keys(zonesData).map((zoneName, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`zone ${selectedZone === zone && "active"}`}
|
||||
onClick={() => setSelectedZone(zone)}
|
||||
>
|
||||
{zone}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{(["top", "right", "bottom", "left"] as Side[]).map((side) => (
|
||||
<div key={side} className={`side-button-container ${side}`}>
|
||||
<button
|
||||
className={`side-button ${side}`}
|
||||
onClick={() => toggleSide(side)}
|
||||
title={`Toggle ${side} panel`}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<div
|
||||
className="extra-buttons"
|
||||
style={{
|
||||
display: zonesData[selectedZone].activeSides.includes(side)
|
||||
? "flex"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`icon ${
|
||||
zonesData[selectedZone].lockedPanels.includes(side)
|
||||
? "active"
|
||||
: ""
|
||||
className={`zone ${
|
||||
selectedZone.zoneName === zoneName ? "active" : ""
|
||||
}`}
|
||||
title="Disable Sorting"
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<DisableSorting />
|
||||
</div>
|
||||
<div
|
||||
className={`icon ${
|
||||
zonesData[selectedZone].lockedPanels.includes(side)
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
title="Hide Panel"
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<EyeIcon />
|
||||
</div>
|
||||
<div
|
||||
className={`icon ${
|
||||
zonesData[selectedZone].lockedPanels.includes(side)
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
title="Clean Panel"
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<CleanPannel />
|
||||
</div>
|
||||
<div
|
||||
className={`icon ${
|
||||
zonesData[selectedZone].lockedPanels.includes(side)
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
title={
|
||||
zonesData[selectedZone].lockedPanels.includes(side)
|
||||
? "Unlock Panel"
|
||||
: "Lock Panel"
|
||||
onClick={() =>
|
||||
setSelectedZone({
|
||||
zoneName,
|
||||
...zonesData[zoneName],
|
||||
})
|
||||
}
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<LockIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{zonesData[selectedZone].activeSides.map((side) => (
|
||||
<div
|
||||
key={side}
|
||||
className={`panel ${side}-panel absolute`}
|
||||
style={getPanelStyle(side)} // Pass `side` to `getPanelStyle`
|
||||
onDrop={(e) => handleDrop(e, side)}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
ref={(el) => {
|
||||
if (el) {
|
||||
panelRefs.current[side] = el;
|
||||
} else {
|
||||
delete panelRefs.current[side];
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="panel-content"
|
||||
style={{
|
||||
pointerEvents: zonesData[selectedZone].lockedPanels.includes(
|
||||
side
|
||||
)
|
||||
? "none"
|
||||
: "auto",
|
||||
opacity: zonesData[selectedZone].lockedPanels.includes(side)
|
||||
? "0.8"
|
||||
: "1",
|
||||
}}
|
||||
>
|
||||
<SortableContext
|
||||
items={zonesData[selectedZone].widgets
|
||||
.filter((w) => w.panel === side)
|
||||
.map((w) => w.id)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{zonesData[selectedZone].widgets
|
||||
.filter((w) => w.panel === side)
|
||||
.map((widget) => (
|
||||
<DraggableWidget widget={widget} key={widget.id} />
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
{zoneName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Add buttons component */}
|
||||
{!isPlaying && (
|
||||
<AddButtons
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
)}
|
||||
{/* Panel component */}
|
||||
<Panel
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
</div>
|
||||
{/* Sidebar for data/design options */}
|
||||
{!isPlaying && (
|
||||
<SideBar header={["Data", "Design"]} defaultActive={"Data"} />
|
||||
)}
|
||||
</div>
|
||||
</DndContext>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RealTimeVisualization;
|
||||
|
|
|
@ -286,7 +286,7 @@ export const useThemeStore = create<ThemeState>((set) => ({
|
|||
}));
|
||||
// Define the WidgetStore interface
|
||||
// Define the WidgetStore interface
|
||||
interface Widget {
|
||||
export interface Widget {
|
||||
id: string;
|
||||
type: string; // Can be chart type or "progress"
|
||||
panel: "top" | "bottom" | "left" | "right";
|
||||
|
@ -315,7 +315,7 @@ interface Widget {
|
|||
interface WidgetStore {
|
||||
draggedAsset: Widget | null; // The currently dragged widget asset
|
||||
widgets: Widget[]; // List of all widgets
|
||||
selectedChartId: Widget | null; // The currently selected chart/widget
|
||||
selectedChartId: any;
|
||||
setDraggedAsset: (asset: Widget | null) => void; // Setter for draggedAsset
|
||||
addWidget: (widget: Widget) => void; // Add a new widget
|
||||
setWidgets: (widgets: Widget[]) => void; // Replace the entire widgets array
|
||||
|
@ -333,3 +333,13 @@ export const useWidgetStore = create<WidgetStore>((set) => ({
|
|||
setWidgets: (widgets) => set({ widgets }),
|
||||
setSelectedChartId: (widget) => set({ selectedChartId: widget }),
|
||||
}));
|
||||
|
||||
type PlayButtonStore = {
|
||||
isPlaying: boolean; // Updated state name to reflect the play/pause status more clearly
|
||||
setIsPlaying: (value: boolean) => void; // Updated setter function name for clarity
|
||||
};
|
||||
|
||||
export const usePlayButtonStore = create<PlayButtonStore>((set) => ({
|
||||
isPlaying: false, // Default state for play/pause
|
||||
setIsPlaying: (value) => set({ isPlaying: value }), // Update isPlaying state
|
||||
}));
|
||||
|
|
Loading…
Reference in New Issue