updated real time viz ui

This commit is contained in:
Nalvazhuthi 2025-03-18 09:51:33 +05:30
parent 2da7011462
commit 92676cd12c
10 changed files with 783 additions and 525 deletions

View File

@ -58,8 +58,11 @@
}
}
.child {
padding-left: 13%;
// padding-left: 13%;
width: 100%;
gap: 6px;
}

View File

@ -27,6 +27,50 @@
overflow-y: auto;
padding-bottom: 40px;
.progressBar {
height: auto !important;
padding: 12px 10px 41px 10px;
display: flex;
flex-direction: column;
gap: 6px;
.header {
display: flex;
justify-content: center;
align-items: center;
border-bottom: none;
}
.stock {
padding: 13px 5px;
background-color: #E0DFFF80;
border-radius: 6.33px;
display: flex;
justify-content: space-between;
.stock-item {
.stockValues {
display: flex;
flex-direction: row-reverse;
align-items: flex-end;
gap: 3px;
.value {
color: #4a90e2;
font-size: 16px;
}
}
.stock-description {
font-size: 12px;
}
}
}
}
&::-webkit-scrollbar {
display: none;
}
@ -63,6 +107,15 @@
padding: 4px 8px;
white-space: nowrap;
}
.active {
background-color: #4a90e2;
color: #FCFDFD !important;
}
}
.zoon-wrapper.bottom {
bottom: 210px;
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useMemo, useCallback } from "react";
import asset1 from "../../assets/images/tempimages/asset1.png";
import asset2 from "../../assets/images/tempimages/asset2.png";
import asset3 from "../../assets/images/tempimages/asset3.png";
@ -39,33 +39,41 @@ const SideBar: React.FC<SideBarProps> = ({ header, defaultActive }) => {
const [filteredData, setFilteredData] = useState<FilteredAssets[]>([]);
const [inputData, setInputData] = useState<string>("");
const [assets, setAssets] = useState<FilteredAssets[]>([]);
const assets: FilteredAssets[] = useMemo(
() => [
{ filename: "Asset 1", thumbnail: asset1, modelfileID: "1" },
{ filename: "Asset 2", thumbnail: asset2, modelfileID: "2" },
{ filename: "Asset 3", thumbnail: asset3, modelfileID: "3" },
{ filename: "Asset 4", thumbnail: asset4, modelfileID: "4" },
{ filename: "Asset 5", thumbnail: asset5, modelfileID: "5" },
{ filename: "Asset 6", thumbnail: asset6, modelfileID: "6" },
{ filename: "Asset 7", thumbnail: asset7, modelfileID: "7" },
{ filename: "Asset 8", thumbnail: asset8, modelfileID: "8" },
{ filename: "Asset 9", thumbnail: asset9, modelfileID: "9" },
],
[]
);
// const assets: FilteredAssets[] = [
// { name: "Asset 1", img: asset1 },
// { name: "Asset 2", img: asset2 },
// { name: "Asset 3", img: asset3 },
// { name: "Asset 4", img: asset4 },
// { name: "Asset 5", img: asset5 },
// { name: "Asset 6", img: asset6 },
// { name: "Asset 7", img: asset7 },
// { name: "Asset 8", img: asset8 },
// { name: "Asset 9", img: asset9 },
// ];
// Memoized Input Change Handler
const handleInputChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setInputData(value);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setInputData(value);
const filtered = assets.filter((asset) =>
asset.filename.toLowerCase().includes(value.toLowerCase())
);
setFilteredData(filtered);
};
const filtered = assets.filter((asset) =>
asset.filename.toLowerCase().includes(value.toLowerCase())
);
setFilteredData(filtered);
},
[assets]
);
// Update Active State Only When Necessary
useEffect(() => {
setActive(defaultActive || header[0]);
}, [header, defaultActive]);
if (defaultActive && defaultActive !== active) {
setActive(defaultActive);
}
}, [defaultActive]);
return (
<div
@ -86,6 +94,7 @@ const SideBar: React.FC<SideBarProps> = ({ header, defaultActive }) => {
))}
</div>
{/* Render Components Based on Active Header */}
{active === "Outline" && <Outline />}
{active === "Asset library" && <AssetLibrary />}
{active === "Overview" && <Overview />}
@ -96,4 +105,4 @@ const SideBar: React.FC<SideBarProps> = ({ header, defaultActive }) => {
);
};
export default SideBar;
export default React.memo(SideBar);

View File

@ -1,13 +1,23 @@
import React, { useEffect, useRef, useMemo } from "react";
import { Chart, ChartType } from "chart.js/auto";
import { useThemeStore, useWidgetStore } from "../../../../store/store";
import { useThemeStore } from "../../../../store/store";
// Define Props Interface
interface ChartComponentProps {
type: ChartType;
title: string;
fontFamily?: string;
fontSize?: string;
fontWeight?: "Light" | "Regular" | "Bold"; // Explicitly typing fontWeight
type: any; // Type of chart (e.g., "bar", "line", etc.)
title: string; // Title of the chart
fontFamily?: string; // Optional font family for the chart title
fontSize?: string; // Optional font size for the chart title
fontWeight?: "Light" | "Regular" | "Bold"; // Optional font weight for the chart title
data: {
labels: string[]; // Labels for the x-axis
datasets: {
data: number[]; // Data points for the chart
backgroundColor: string; // Background color for the chart
borderColor: string; // Border color for the chart
borderWidth: number; // Border width for the chart
}[];
}; // Data for the chart
}
const ChartComponent = ({
@ -16,11 +26,12 @@ const ChartComponent = ({
fontFamily,
fontSize,
fontWeight = "Regular", // Default to "Regular"
data: propsData,
}: ChartComponentProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const { themeColor } = useThemeStore();
// Memoize theme colors to prevent unnecessary recalculations
// Memoize Theme Colors to Prevent Unnecessary Recalculations
const buttonActionColor = useMemo(
() => themeColor[0] || "#5c87df",
[themeColor]
@ -30,7 +41,7 @@ const ChartComponent = ({
[themeColor]
);
// Memoize font weight mapping
// Memoize Font Weight Mapping
const chartFontWeightMap = useMemo(
() => ({
Light: "lighter" as const,
@ -40,19 +51,19 @@ const ChartComponent = ({
[]
);
// Parse and memoize fontSize
// Parse and Memoize Font Size
const fontSizeValue = useMemo(
() => (fontSize ? parseInt(fontSize) : 12),
[fontSize]
);
// Determine and memoize font weight
// Determine and Memoize Font Weight
const fontWeightValue = useMemo(
() => chartFontWeightMap[fontWeight], // No need for '|| "normal"' since fontWeight is guaranteed to be valid
() => chartFontWeightMap[fontWeight],
[fontWeight, chartFontWeightMap]
);
// Memoize chart font style
// Memoize Chart Font Style
const chartFontStyle = useMemo(
() => ({
family: fontFamily || "Arial",
@ -62,23 +73,10 @@ const ChartComponent = ({
[fontFamily, fontSizeValue, fontWeightValue]
);
// Memoize chart data
const data = useMemo(
() => ({
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"],
datasets: [
{
data: [65, 59, 80, 81, 56, 55, 40],
backgroundColor: buttonActionColor,
borderColor: buttonAbortColor,
borderWidth: 1,
},
],
}),
[buttonActionColor, buttonAbortColor]
);
// Memoize Chart Data
const data = useMemo(() => propsData, [propsData]);
// Memoize chart options
// Memoize Chart Options
const options = useMemo(
() => ({
responsive: true,
@ -97,6 +95,7 @@ const ChartComponent = ({
[title, chartFontStyle]
);
// Initialize Chart on Component Mount
useEffect(() => {
if (!canvasRef.current) return;
@ -105,10 +104,21 @@ const ChartComponent = ({
const chart = new Chart(ctx, { type, data, options });
// Cleanup: Destroy the chart instance when the component unmounts
return () => chart.destroy();
}, [type, data, options]); // Only recreate chart when these essentials change
}, [type, data, options]); // Only recreate the chart when these dependencies change
return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
};
export default React.memo(ChartComponent);
export default React.memo(ChartComponent, (prevProps, nextProps) => {
// Custom comparison function to prevent unnecessary re-renders
return (
prevProps.type === nextProps.type &&
prevProps.title === nextProps.title &&
prevProps.fontFamily === nextProps.fontFamily &&
prevProps.fontSize === nextProps.fontSize &&
prevProps.fontWeight === nextProps.fontWeight &&
JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data)
);
});

View File

@ -1,6 +1,7 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import RegularDropDown from "../../inputs/regularDropDown";
import { LinkIcon, RemoveIcon } from "../../../../assets/images/svgExports";
import { useWidgetStore } from "../../../../store/store";
interface Child {
id: number;
@ -14,132 +15,126 @@ interface Group {
}
const Data = () => {
const { selectedChartId } = useWidgetStore();
const [selectedWidget] = useState("Widget 1");
const [groups, setGroups] = useState<Group[]>([
{
id: 1,
easing: "Connecter 1",
children: [
{ id: 1, easing: "Linear" },
{ id: 2, easing: "Ease Out" },
{ id: 3, easing: "Linear" },
],
},
]);
const handleLinkClick = (childId: number) => {
setGroups((currentGroups) => {
let sourceGroup: Group | undefined;
let sourceChild: Child | undefined;
// State to store groups for all widgets (using Widget.id as keys)
const [chartDataGroups, setChartDataGroups] = useState<
Record<string, Group[]>
>({});
// Find the source group and child
for (const group of currentGroups) {
sourceChild = group.children.find((c) => c.id === childId);
if (sourceChild) {
sourceGroup = group;
break;
}
}
useEffect(() => {
console.log("Selected Chart ID:", selectedChartId);
if (!sourceGroup || !sourceChild) return currentGroups;
// Initialize data groups for the newly selected widget if it doesn't exist
if (selectedChartId && !chartDataGroups[selectedChartId.id]) {
setChartDataGroups((prev) => ({
...prev,
[selectedChartId.id]: [
{
id: Date.now(),
easing: "Connecter 1",
children: [
{ id: Date.now(), easing: "Linear" },
{ id: Date.now() + 1, easing: "Ease Out" },
{ id: Date.now() + 2, easing: "Linear" },
],
},
],
}));
}
}, [selectedChartId]);
// Remove child from source group
const updatedGroups = currentGroups
.map((group) => ({
...group,
children: group.children.filter((c) => c.id !== childId),
}))
.filter((group) => group.children.length > 0);
// Handle linking children between groups
const handleLinkClick = (childId: number) => {};
// Find or create target group
const targetGroup = updatedGroups.find(
(g) => g.id !== sourceGroup!.id && g.easing === sourceGroup!.easing
);
// Remove a child from a group
const removeChild = (groupId: number, childId: number) => {
setChartDataGroups((currentGroups) => {
if (!selectedChartId) return currentGroups;
if (targetGroup) {
targetGroup.children.push(sourceChild);
} else {
updatedGroups.push({
id: Date.now(),
easing: sourceGroup.easing,
children: [sourceChild],
});
}
const currentChartData = currentGroups[selectedChartId.id] || [];
return updatedGroups;
return {
...currentGroups,
[selectedChartId.id]: currentChartData.map((group) =>
group.id === groupId
? {
...group,
children: group.children.map((child) =>
child.id === childId ? { ...child, easing: "Linear" } : child
),
}
: group
),
};
});
};
const removeChild = (groupId: number, childId: number) => {
setGroups((currentGroups) =>
currentGroups.map((group) => {
if (group.id === groupId) {
return {
...group,
children: group.children.map((child) =>
child.id === childId ? { ...child, easing: "Linear" } : child
),
};
}
return group;
})
);
};
console.log("selectedChartId: ", selectedChartId?.title);
return (
<div className="dataSideBar">
<div className="sideBarHeader">{selectedWidget}</div>
{groups.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) => {
setGroups(
groups.map((g) => (g.id === group.id ? { ...g, easing } : g))
);
}}
/>
</div>
{group.children.map((child) => (
<div key={child.id} className="selectedMain child">
<main>Input {child.id}</main>
{/* Pass the current easing as the header */}
{selectedChartId?.title && (
<div className="sideBarHeader">{selectedChartId?.title}</div>
)}
{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={child.easing}
options={["Linear", "Ease In", "Ease Out", "Ease In-Out"]}
header={group.easing}
options={[
"Connecter 1",
"Connecter 2",
"Connecter 3",
"Connecter 4",
]}
onSelect={(easing) => {
setGroups(
groups.map((g) => ({
...g,
children: g.children.map((c) =>
c.id === child.id ? { ...c, easing } : c
),
}))
);
setChartDataGroups((prev) => ({
...prev,
[selectedChartId.id]: prev[selectedChartId.id].map((g) =>
g.id === group.id ? { ...g, easing } : g
),
}));
}}
/>
<div className="icon" onClick={() => handleLinkClick(child.id)}>
<LinkIcon />
</div> */}
{/* Render children only if there is a selected chart */}
{group.children.map((child) => (
<div key={child.id} className="selectedMain child">
<main>Input</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) => ({
...g,
children: g.children.map((c) =>
c.id === child.id ? { ...c, easing } : c
),
})
),
}));
}}
/>
<div className="icon" onClick={() => handleLinkClick(child.id)}>
<LinkIcon />
</div>
<div
className="icon"
onClick={() => removeChild(group.id, child.id)}
>
<RemoveIcon />
</div>
</div>
<div
className="icon"
onClick={() => removeChild(group.id, child.id)}
>
<RemoveIcon />
</div>
</div>
))}
</div>
))}
))}
</div>
))}
<div className="infoBox">
<span className="infoIcon">i</span>
<p>

View File

@ -1,22 +1,28 @@
import React, { useEffect, useState } from "react";
import RegularDropDown from "../../inputs/regularDropDown";
import { useWidgetStore } from "../../../../store/store";
import styles from "./Design.module.scss"; // Import SCSS file
import ChartComponent from "./chartComponent";
type Side = "top" | "bottom" | "left" | "right";
// Define the Widget interface
// Define Props Interface
interface DesignProps {}
interface Widget {
id: string;
type: string; // Change this if the type is more specific
panel: Side; // You should define or import 'Side' type if not already defined
type: string; // Chart type (e.g., "bar", "line")
panel: "top" | "bottom" | "left" | "right"; // Panel location
title: string;
fontFamily?: string;
fontSize?: string;
fontWeight?: string;
data: {
labels: string[];
datasets: {
data: number[];
backgroundColor: string;
borderColor: string;
borderWidth: number;
}[];
}; // Data for the chart
}
const Design = () => {
const [selectedName, setSelectedName] = useState("drop down");
const [selectedElement, setSelectedElement] = useState("drop down");
@ -24,66 +30,70 @@ const Design = () => {
const [selectedSize, setSelectedSize] = useState("drop down");
const [selectedWeight, setSelectedWeight] = useState("drop down");
// Zustand Store Hooks
const { selectedChartId, setSelectedChartId, widgets, setWidgets } =
useWidgetStore(); // Get selected chart ID and list of widgets
useWidgetStore();
// Find the selected widget based on `selectedChartId`
const selectedWidget = selectedChartId
? widgets.find((widget) => widget.id === selectedChartId.id)
: null;
// Log Selected Chart ID for Debugging
// Function to update the selected widget
// Handle Widget Updates
const handleUpdateWidget = (updatedProperties: Partial<Widget>) => {
if (!selectedWidget) {
return;
}
if (!selectedChartId) return;
// Update the selectedChartId
const updatedChartId = {
...selectedChartId,
...updatedProperties,
};
setSelectedChartId(updatedChartId);
// Update the widgets array
const updatedWidgets = widgets.map((widget) =>
widget.id === selectedWidget.id
? { ...widget, ...updatedProperties } // Merge existing widget with updated properties
widget.id === selectedChartId.id
? { ...widget, ...updatedProperties }
: widget
);
// Update the global state with the new widgets array
setWidgets(updatedWidgets);
// Update `selectedChartId` to reflect the changes
if (selectedChartId) {
const updatedChartId = {
...selectedChartId,
...updatedProperties, // Merge updated properties into `selectedChartId`
};
setSelectedChartId(updatedChartId);
}
};
useEffect(() => {
// Log the current state of widgets for debugging
}, [widgets]);
// Default Chart Data
const defaultChartData = {
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"],
datasets: [
{
data: [65, 59, 80, 81, 56, 55, 40],
backgroundColor: "#5c87df",
borderColor: "#ffffff",
borderWidth: 1,
},
],
};
return (
<div className="design">
{/* Display the selected widget's title */}
{/* Title of the Selected Widget */}
<div className="selectedWidget">
{selectedWidget ? selectedWidget.title : "Widget 1"}
</div>
<div className="reviewChart">
{/* Pass selectedWidget properties to ChartComponent */}
{selectedWidget && (
<ChartComponent
type={selectedWidget.type}
title={selectedWidget.title}
/>
)}
{selectedChartId?.title || "Widget 1"}
</div>
{/* Design Options */}
{/* Chart Component */}
<div className="reviewChart">
{/* {selectedChartId && (
<ChartComponent
type={selectedChartId.type}
title={selectedChartId.title}
data={selectedChartId.data || defaultChartData} // Use widget data or default
/>
)} */}
</div>
{/* Options Container */}
<div className="optionsContainer">
{/* Name Dropdown */}
<div className="option">
<span>Name</span>
<RegularDropDown
header={selectedWidget ? selectedWidget.title : "Select Name"}
header={selectedChartId?.title || "Select Name"}
options={["Option 1", "Option 2", "Option 3"]}
onSelect={(value) => {
setSelectedName(value);
@ -92,22 +102,24 @@ const Design = () => {
/>
</div>
{/* Element Dropdown */}
<div className="option">
<span>Element</span>
<RegularDropDown
header={selectedElement}
options={["Option 1", "Option 2", "Option 3"]}
header={selectedChartId?.type || "Select Element"}
options={["bar", "line", "pie", "doughnut", "radar", "polarArea"]} // Valid chart types
onSelect={(value) => {
setSelectedElement(value);
handleUpdateWidget({ type: value }); // Update element type
handleUpdateWidget({ type: value });
}}
/>
</div>
{/* Font Family Dropdown */}
<div className="option">
<span>Font Family</span>
<RegularDropDown
header={selectedFont}
header={selectedChartId?.fontFamily || "Select Font"}
options={["Arial", "Roboto", "Sans-serif"]}
onSelect={(value) => {
setSelectedFont(value);
@ -116,10 +128,11 @@ const Design = () => {
/>
</div>
{/* Size Dropdown */}
<div className="option">
<span>Size</span>
<RegularDropDown
header={selectedSize}
header={selectedChartId?.fontSize || "Select Size"}
options={["12px", "14px", "16px", "18px"]}
onSelect={(value) => {
setSelectedSize(value);
@ -128,10 +141,11 @@ const Design = () => {
/>
</div>
{/* Weight Dropdown */}
<div className="option">
<span>Weight</span>
<RegularDropDown
header={selectedWeight}
header={selectedChartId?.fontWeight || "Select Weight"}
options={["Light", "Regular", "Bold"]}
onSelect={(value) => {
setSelectedWeight(value);

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react";
import React, { useState } from "react";
import { useThemeStore, useWidgetStore } from "../../../../store/store";
import { ChartType } from "chart.js/auto";
import ChartComponent from "./chartComponent";
@ -7,7 +7,6 @@ import {
SearchIcon,
ThemeIcon,
} from "../../../../assets/images/svgExports";
import RegularDropDown from "../../inputs/regularDropDown";
const Widgets = () => {
const chartTypes: ChartType[] = [
@ -18,63 +17,14 @@ const Widgets = () => {
"radar",
"polarArea",
];
const { setDraggedAsset } = useWidgetStore((state) => state);
const [selectedValue, setSelectedValue] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<"2D" | "3D" | "Floating">("2D");
const switchView = (mode: "2D" | "3D" | "Floating") => {
setViewMode(mode);
};
const { themeColor, setThemeColor } = useThemeStore();
const [theme, setTheme] = useState<boolean>(false); // Default is false
const [activePresetIndex, setActivePresetIndex] = useState<number | null>(
null
);
const [customColor, setCustomColor] = useState<string>("#000000");
const themeContainerRef = useRef<HTMLDivElement | null>(null);
const preset = [
["#6F42C1", "#EEEEFE", "#B392F0"],
["#F8CB47", "#F79002", "#F73F65"],
["#FDA4B8", "#FF6A90", "#B91348"],
["#D1BCF6", "#987BEB", "#6443C9"],
["#FDC64B", "#EF9407", "#B54300"],
["#69E9AB", "#0BB96E", "#087348"],
["#85ADFC", "#246FFE", "#0050EB"],
["#F570C7", "#27CEF7", "#FFAD1A"],
["#6572F2", "#EE42B7", "#12B56E"],
["#10BA68", "#FDB022", "#EA48B5"],
["#10BA68", "#FDB022", "#EA48B5"],
["#6F42C1", "#CEB2F6", "#EA48B5"],
];
// Close theme container on outside click
useEffect(() => {
const themBtn = document.querySelector(".theme-switch");
const handleClickOutside = (event: MouseEvent) => {
// Check if the click is outside the theme container or theme button
if (
themeContainerRef.current &&
!themeContainerRef.current.contains(event.target as Node) &&
!themBtn?.contains(event.target as Node) // Ensure that the click is not inside the theme button
) {
setTheme(false); // Close the theme container when clicking outside
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside); // Cleanup event listener on unmount
};
}, []);
useEffect(() => {}, [theme]);
return (
<div className="widgets-layout">
{/* Search Container */}
@ -87,7 +37,6 @@ const Widgets = () => {
</div>
</div>
</div>
{/* Options and Theme Management */}
<div className="ui-wrapper">
<div className="options">
@ -110,68 +59,24 @@ const Widgets = () => {
Floating
</span>
</div>
{/* Theme Switcher */}
<div
className="theme-switch"
onClick={() => setTheme(!theme)} // Toggle the theme container
>
<button className="theme-button">
<ThemeIcon />
<DropDownIcon />
</button>
</div>
{/* Theme Presets */}
{theme && (
<div className="theme-container" ref={themeContainerRef}>
<h2>Presets</h2>
<div className="theme-preset-wrapper">
{preset.map((colors, index) => (
<div
key={index}
className={`theme-preset ${
activePresetIndex === index ? "active" : ""
}`}
onClick={() => {
setThemeColor(colors);
setActivePresetIndex(index);
}}
>
{colors.map((color, i) => (
<div
key={i}
className="color"
style={{ background: color }}
/>
))}
</div>
))}
</div>
<div className="custom-color">
<h3>Custom Color</h3>
<div className="color-displayer">
<input
type="color"
value={customColor}
onChange={(e) => {
const newColor = e.target.value;
setCustomColor(newColor);
setThemeColor([newColor, newColor, newColor]);
}}
/>
{customColor}
</div>
</div>
</div>
)}
</div>
</div>
{/* Chart Widgets */}
<div className="chart-container">
{chartTypes.map((type, index) => {
const widgetTitle = `Widget ${index + 1}`;
const sampleData = {
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"],
datasets: [
{
data: [65, 59, 80, 81, 56, 55, 40],
backgroundColor: "#5c87df",
borderColor: "#ffffff",
borderWidth: 1,
},
],
};
return (
<div
key={index}
@ -182,17 +87,110 @@ const Widgets = () => {
type,
id: `widget-${index + 1}`,
title: widgetTitle,
panel: "top", // Default panel assignment
data: sampleData, // Include data in the dragged asset
});
}}
onDragEnd={() => setDraggedAsset(null)}
>
<ChartComponent type={type} title={widgetTitle} />
<ChartComponent
type={type}
title={widgetTitle}
data={sampleData}
/>
</div>
);
})}
<div
className="chart progressBar"
draggable
onDragStart={(e) => {
setDraggedAsset({
type: "progress", // New widget type
id: `widget-7`,
title: "Widget 7",
panel: "top",
data: {
stocks: [
{
key: "units",
value: 1000,
description: "Initial stock",
},
],
},
});
}}
onDragEnd={() => setDraggedAsset(null)}
>
{" "}
<div className="header">Widget 7</div>
<div className="stock">
<span className="stock-item">
<span className="stockValues">
<div className="key">units</div>
<div className="value">1000</div>
</span>
<div className="stock-description">Initial stock</div>
</span>
<div className="icon">Icon</div>
</div>
</div>
<div
className="chart progressBar"
draggable
onDragStart={(e) => {
setDraggedAsset({
type: "progress",
id: `widget-8`,
title: "Widget 8",
panel: "top",
data: {
stocks: [
{
key: "units",
value: 1000,
description: "Initial stock",
},
{
key: "units",
value: 500,
description: "Additional stock",
},
],
},
});
}}
onDragEnd={() => setDraggedAsset(null)}
>
{" "}
<div className="header">Widget 8</div>
<div className="stock">
<span className="stock-item">
<span className="stockValues">
<div className="key">units</div>
<div className="value">1000</div>
</span>
<div className="stock-description">Initial stock</div>
</span>
<div className="icon">Icon</div>
</div>
<div className="stock">
<span className="stock-item">
<span className="stockValues">
<div className="key">units</div>
<div className="value">1000</div>
</span>
<div className="stock-description">Initial stock</div>
</span>
<div className="icon">Icon</div>
</div>
</div>
</div>
</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

View File

@ -0,0 +1,22 @@
import React from "react";
export const ProgressCard = ({ title, data }: {
title: string;
data: { stocks: Array<{ key: string; value: number; description: string }> };
}) => (
<div className="chart progressBar">
<div className="header">{title}</div>
{data.stocks.map((stock, index) => (
<div key={index} className="stock">
<span className="stock-item">
<span className="stockValues">
<div className="key">{stock.key}</div>
<div className="value">{stock.value}</div>
</span>
<div className="stock-description">{stock.description}</div>
</span>
<div className="icon">Icon</div>
</div>
))}
</div>
);

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useMemo, useState, useEffect, useRef } from "react";
import {
DndContext,
closestCenter,
@ -9,20 +9,20 @@ import {
} from "@dnd-kit/core";
import {
SortableContext,
useSortable,
verticalListSortingStrategy,
arrayMove,
useSortable,
} from "@dnd-kit/sortable";
import { useWidgetStore } from "../../store/store"; // Assuming you have this store
import ChartComponent from "../../components/ui/sideBar/realTimeViz/chartComponent"; // Assuming this exists
import SideBar from "../../components/layout/sideBar"; // Assuming this exists
import { useWidgetStore } from "../../store/store";
import ChartComponent from "../../components/ui/sideBar/realTimeViz/chartComponent";
import SideBar from "../../components/layout/sideBar";
import {
CleanPannel,
DisableSorting,
EyeIcon,
LockIcon,
} from "../../assets/images/svgExports"; // Assuming these are your icon components
} from "../../assets/images/svgExports";
import { ProgressCard } from "./progressCard ";
type Side = "top" | "bottom" | "left" | "right";
@ -38,15 +38,17 @@ const DraggableWidget = ({ widget }: { widget: any }) => {
transition,
isDragging,
} = useSortable({ id: widget.id });
const { setSelectedChartId } = useWidgetStore();
const { selectedChartId, setSelectedChartId } = useWidgetStore();
const style = {
transform: transform
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
: undefined,
transition: transition || "transform 200ms ease",
};
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) {
@ -64,137 +66,276 @@ const DraggableWidget = ({ widget }: { widget: any }) => {
style={style}
onPointerDown={handlePointerDown}
>
<ChartComponent
type={widget.type}
title={widget.title}
fontFamily={widget.fontFamily}
fontSize={widget.fontSize}
fontWeight={widget.fontWeight}
/>
{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 [zone, setZone] = useState([
"Manufacturing unit",
"Assembly unit",
"Packing unit",
"Warehouse",
"Inventory",
]);
const [activeSides, setActiveSides] = useState<Side[]>([]);
const [panelOrder, setPanelOrder] = useState<Side[]>([]);
const [selectedSide, setSelectedSide] = useState<Side | null>(null);
const [lockedPanels, setLockedPanels] = useState<Side[]>([]);
const [activeExtraButton, setActiveExtraButton] = useState<{
[key in Side]?: string;
const [selectedZone, setSelectedZone] = useState("Manufacturing unit");
const [panelDimensions, setPanelDimensions] = useState<{
[side in Side]?: { width: number; height: number };
}>({});
const { draggedAsset, addWidget, widgets, setWidgets } = useWidgetStore();
const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({});
const [zonesData, setZonesData] = useState<{
[key: string]: {
activeSides: Side[];
panelOrder: Side[];
lockedPanels: Side[];
widgets: {
id: string;
type: string;
title: string;
panel: Side;
data: any;
}[];
};
}>({
"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: [],
},
});
useEffect(() => {}, [zonesData]);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor)
);
const toggleSide = (side: Side) => {
setActiveSides((prev) => {
const newActive = prev.includes(side)
? prev.filter((s) => s !== side)
: [...prev, side];
setPanelOrder(newActive);
useEffect(() => {
const observers: ResizeObserver[] = [];
const currentPanelRefs = panelRefs.current;
if (prev.includes(side)) {
setSelectedSide(null);
} else {
setSelectedSide(side);
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);
}
});
return newActive;
return () => {
observers.forEach((observer) => observer.disconnect());
};
}, [zonesData[selectedZone].activeSides, 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 toggleLockPanel = (side: Side) => {
setLockedPanels((prev) =>
prev.includes(side)
? prev.filter((panel) => panel !== side)
: [...prev, 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 = (currentSide: Side) => {
const currentIndex = panelOrder.indexOf(currentSide);
const previousPanels = panelOrder.slice(0, currentIndex);
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");
const leftActive = previousPanels.includes("left");
const rightActive = previousPanels.includes("right");
const topActive = previousPanels.includes("top");
const bottomActive = previousPanels.includes("bottom");
switch (currentSide) {
case "top":
case "bottom":
return {
width: `calc(100% - ${
(leftActive ? 204 : 0) + (rightActive ? 204 : 0)
}px)`,
left: leftActive ? "204px" : "0",
right: rightActive ? "204px" : "0",
[currentSide]: "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",
[currentSide]: "0",
width: "200px",
};
default:
return {};
}
};
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) {
addWidget({ ...draggedAsset, id: generateUniqueId(), panel });
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) {
return;
}
setZonesData((prev) => ({
...prev,
[selectedZone]: {
...prev[selectedZone],
widgets: [
...prev[selectedZone].widgets,
{
...draggedAsset,
id: generateUniqueId(),
panel,
},
],
},
}));
}
};
const handleDragEnd = (event: any) => {
const { active, over } = event;
if (!over) return;
const oldIndex = widgets.findIndex((widget) => widget.id === active.id);
const newPanel =
widgets.find((widget) => widget.id === over.id)?.panel || active.panel;
if (active.panel === newPanel) {
const newIndex = widgets.findIndex((widget) => widget.id === over.id);
const reorderedWidgets = arrayMove(widgets, oldIndex, newIndex);
setWidgets(reorderedWidgets);
} else {
const updatedWidgets = widgets.map((widget) =>
widget.id === active.id ? { ...widget, panel: newPanel } : widget
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;
const widgetsInNewPanel = updatedWidgets.filter(
(w) => w.panel === newPanel
);
const newIndex = widgetsInNewPanel.findIndex((w) => w.id === over.id);
const reorderedWidgets = arrayMove(updatedWidgets, oldIndex, newIndex);
setWidgets(reorderedWidgets);
}
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,
},
};
}
});
};
return (
@ -208,11 +349,20 @@ const RealTimeVisualization = () => {
header={["Overview", "Widgets", "Templates"]}
defaultActive={"Widgets"}
/>
<div className="main-container relative">
<div className="zoon-wrapper">
{zone.map((zone, index) => (
<div className="zone">{zone}</div>
<div
className={`zoon-wrapper ${
zonesData[selectedZone].activeSides.includes("bottom") && "bottom"
}`}
>
{Object.keys(zonesData).map((zone, index) => (
<div
key={index}
className={`zone ${selectedZone === zone && "active"}`}
onClick={() => setSelectedZone(zone)}
>
{zone}
</div>
))}
</div>
{(["top", "right", "bottom", "left"] as Side[]).map((side) => (
@ -224,100 +374,100 @@ const RealTimeVisualization = () => {
>
+
</button>
<div
className="extra-buttons"
style={{
display: activeSides.includes(side) ? "flex" : "none",
display: zonesData[selectedZone].activeSides.includes(side)
? "flex"
: "none",
}}
>
<div
className={`icon ${
activeExtraButton[side] === "Disable Sorting"
zonesData[selectedZone].lockedPanels.includes(side)
? "active"
: ""
}`}
title="Disable Sorting"
onClick={() =>
setActiveExtraButton((prev) => ({
...prev,
[side]: "Disable Sorting",
}))
}
onClick={() => toggleLockPanel(side)}
>
<DisableSorting />
</div>
<div
className={`icon ${
activeExtraButton[side] === "Hide Panel" ? "active" : ""
zonesData[selectedZone].lockedPanels.includes(side)
? "active"
: ""
}`}
title="Hide Panel"
onClick={() =>
setActiveExtraButton((prev) => ({
...prev,
[side]: "Hide Panel",
}))
}
onClick={() => toggleLockPanel(side)}
>
<EyeIcon />
</div>
<div
className={`icon ${
activeExtraButton[side] === "Clean Panel" ? "active" : ""
zonesData[selectedZone].lockedPanels.includes(side)
? "active"
: ""
}`}
title="Clean Panel"
onClick={() =>
setActiveExtraButton((prev) => ({
...prev,
[side]: "Clean Panel",
}))
}
onClick={() => toggleLockPanel(side)}
>
<CleanPannel />
</div>
<div
className={`icon ${
activeExtraButton[side] === "Lock Panel" ? "active" : ""
zonesData[selectedZone].lockedPanels.includes(side)
? "active"
: ""
}`}
title={
lockedPanels.includes(side) ? "Unlock Panel" : "Lock Panel"
zonesData[selectedZone].lockedPanels.includes(side)
? "Unlock Panel"
: "Lock Panel"
}
onClick={() => {
toggleLockPanel(side);
setActiveExtraButton((prev) => ({
...prev,
[side]: "Lock Panel",
}));
}}
onClick={() => toggleLockPanel(side)}
>
<LockIcon />
</div>
</div>
</div>
))}
{activeSides.map((side) => (
{zonesData[selectedZone].activeSides.map((side) => (
<div
key={side}
className={`panel ${side}-panel absolute`}
style={getPanelStyle(side)}
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: lockedPanels.includes(side) ? "none" : "auto",
opacity: lockedPanels.includes(side) ? "0.8" : "1",
pointerEvents: zonesData[selectedZone].lockedPanels.includes(
side
)
? "none"
: "auto",
opacity: zonesData[selectedZone].lockedPanels.includes(side)
? "0.8"
: "1",
}}
>
<SortableContext
items={widgets
items={zonesData[selectedZone].widgets
.filter((w) => w.panel === side)
.map((w) => w.id)}
strategy={verticalListSortingStrategy}
>
{widgets
{zonesData[selectedZone].widgets
.filter((w) => w.panel === side)
.map((widget) => (
<DraggableWidget widget={widget} key={widget.id} />
@ -327,7 +477,6 @@ const RealTimeVisualization = () => {
</div>
))}
</div>
<SideBar header={["Data", "Design"]} defaultActive={"Data"} />
</div>
</DndContext>

View File

@ -275,50 +275,6 @@ export const useDrieUIValue = create<any>((set: any) => ({
type Side = "top" | "bottom" | "left" | "right";
// Define Widget interface
interface Widget {
id: string;
type: any;
panel: Side;
title: string; // Ensure title is a string
fontFamily?: string;
fontSize?: string;
fontWeight?: string;
}
// Store interface
interface WidgetStore {
draggedAsset: { type: any; id: string; title: string } | null;
widgets: Widget[];
setDraggedAsset: (
asset: { type: any; id: string; title: string } | null
) => void;
addWidget: (widget: Widget) => void;
setWidgets: (widgets: Widget[]) => void;
selectedChartId: Widget | null; // Change this to store an object of type Widget
setSelectedChartId: (widget: Widget | {
"type": "line",
"id": "1741781992712-ecvq5t61y",
"title": "Widget 2",
"panel": "left",
"fontWeight": "Light",
"fontSize": "12px",
"fontFamily": "Sans-serif"
}) => void; // Update to accept a Widget object
}
// Create the store with Zustand
export const useWidgetStore = create<WidgetStore>((set) => ({
draggedAsset: null,
widgets: [],
setDraggedAsset: (asset) => set({ draggedAsset: asset }),
addWidget: (widget) =>
set((state) => ({ widgets: [...state.widgets, widget] })),
setWidgets: (widgets) => set({ widgets }),
selectedChartId: null,
setSelectedChartId: (widget) => set({ selectedChartId: widget }),
}));
interface ThemeState {
themeColor: string[]; // This should be an array of strings
setThemeColor: (colors: string[]) => void; // This function will accept an array of strings
@ -328,3 +284,52 @@ export const useThemeStore = create<ThemeState>((set) => ({
themeColor: ["#5c87df", "#EEEEFE", "#969BA7"],
setThemeColor: (colors) => set({ themeColor: colors }),
}));
// Define the WidgetStore interface
// Define the WidgetStore interface
interface Widget {
id: string;
type: string; // Can be chart type or "progress"
panel: "top" | "bottom" | "left" | "right";
title: string;
fontFamily?: string;
fontSize?: string;
fontWeight?: string;
data: {
// Chart data
labels?: string[];
datasets?: Array<{
data: number[];
backgroundColor: string;
borderColor: string;
borderWidth: number;
}>;
// Progress card data
stocks?: Array<{
key: string;
value: number;
description: string;
}>;
};
}
interface WidgetStore {
draggedAsset: Widget | null; // The currently dragged widget asset
widgets: Widget[]; // List of all widgets
selectedChartId: Widget | null; // The currently selected chart/widget
setDraggedAsset: (asset: Widget | null) => void; // Setter for draggedAsset
addWidget: (widget: Widget) => void; // Add a new widget
setWidgets: (widgets: Widget[]) => void; // Replace the entire widgets array
setSelectedChartId: (widget: Widget | null) => void; // Set the selected chart/widget
}
// Create the store with Zustand
export const useWidgetStore = create<WidgetStore>((set) => ({
draggedAsset: null,
widgets: [],
selectedChartId: null, // Initialize as null, not as an array
setDraggedAsset: (asset) => set({ draggedAsset: asset }),
addWidget: (widget) =>
set((state) => ({ widgets: [...state.widgets, widget] })),
setWidgets: (widgets) => set({ widgets }),
setSelectedChartId: (widget) => set({ selectedChartId: widget }),
}));