This commit is contained in:
Vishnu 2025-05-23 12:09:30 +05:30
commit 1a0edcd43a
6 changed files with 270 additions and 334 deletions

View File

@ -1,175 +1,21 @@
import { useState, useEffect, useRef } from "react";
import React, { useEffect, useState } from "react";
import { ArrowIcon } from "../../../../icons/ExportCommonIcons";
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
import InputRange from "../../../../ui/inputs/InputRange";
import { useWidgetStore } from "../../../../../store/useWidgetStore";
import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent";
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
import { WalletIcon } from "../../../../icons/3dChartIcons";
import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard";
interface Widget {
id: string;
type?: string;
panel: "top" | "bottom" | "left" | "right";
title?: string;
header?: string;
fontFamily?: string;
fontSize?: string;
fontWeight?: string;
className?: string;
data?: {
labels: string[];
datasets: {
data: number[];
backgroundColor: string;
borderColor: string;
borderWidth: number;
}[];
};
value?: string;
per?: string;
}
const defaultStyle = {
theme: "Glass",
elementColor: "#ffffff",
blurEffect: 10,
opacity: 10,
selectedElement: "Glass",
};
interface ChartElement {
tagName: string;
className: string;
textContent: string;
selector: string;
}
const Design = () => {
const [selectedFont, setSelectedFont] = useState("drop down");
const [selectedSize, setSelectedSize] = useState("drop down");
const [selectedWeight, setSelectedWeight] = useState("drop down");
const [elementColor, setElementColor] = useState("#6f42c1");
const [showColorPicker, setShowColorPicker] = useState(false);
const [chartElements, setChartElements] = useState<ChartElement[]>([]);
const [selectedElementToStyle, setSelectedElementToStyle] = useState<
string | null
>(null);
const [nameInput, setNameInput] = useState("");
const chartRef = useRef<HTMLDivElement>(null);
const { selectedChartId, setSelectedChartId, widgets, setWidgets } =
useWidgetStore();
// Initialize name input and extract elements when selectedChartId changes
useEffect(() => {
setNameInput(selectedChartId?.header ? selectedChartId?.title : "");
if (!chartRef.current) return;
const timer = setTimeout(() => {
const chartContainer = chartRef.current;
if (!chartContainer) return;
const elements = Array.from(chartContainer.querySelectorAll("*"))
.filter((el) => {
const tagName = el.tagName.toLowerCase();
return !["script", "style", "meta", "link", "head"].includes(tagName);
})
.map((el, index) => {
const tagName = el.tagName.toLowerCase();
const className =
typeof el.className === "string" ? el.className : "";
const textContent = el.textContent?.trim() ?? "";
let selector = tagName;
if (className && typeof className === "string") {
const classList = className
.split(/\s+/)
.filter((c) => c.length > 0);
if (classList.length > 0) {
selector += "." + classList.join(".");
}
}
if (!className || className.trim() === "") {
const parent = el.parentElement;
if (parent) {
const siblings = Array.from(parent.children).filter(
(child) => child.tagName.toLowerCase() === tagName
);
const position = siblings.indexOf(el) + 1;
selector += `:nth-of-type(${position})`;
}
}
return {
tagName,
className,
textContent,
selector,
};
});
setChartElements(elements);
}, 300);
return () => clearTimeout(timer);
}, [selectedChartId]);
const applyStyles = () => {
if (!selectedElementToStyle || !chartRef.current) return;
const element = chartRef.current.querySelector(selectedElementToStyle);
if (!element) return;
const elementToStyle = element as HTMLElement;
if (selectedFont !== "drop down") {
elementToStyle.style.fontFamily = selectedFont;
}
if (selectedSize !== "drop down") {
elementToStyle.style.fontSize = selectedSize;
}
if (selectedWeight !== "drop down") {
elementToStyle.style.fontWeight = selectedWeight.toLowerCase();
}
if (elementColor) {
elementToStyle.style.color = elementColor;
}
};
useEffect(() => {
applyStyles();
}, [
selectedFont,
selectedSize,
selectedWeight,
elementColor,
selectedElementToStyle,
]);
const handleUpdateWidget = (updatedProperties: Partial<Widget>) => {
if (!selectedChartId) return;
const updatedChartId = {
...selectedChartId,
...updatedProperties,
};
setSelectedChartId(updatedChartId);
const updatedWidgets = widgets.map((widget) =>
widget.id === selectedChartId.id
? { ...widget, ...updatedProperties }
: widget
);
setWidgets(updatedWidgets);
};
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value;
setNameInput(newName);
if (selectedChartId?.title) {
handleUpdateWidget({ title: newName });
} else if (selectedChartId?.header) {
handleUpdateWidget({ header: newName });
}
};
const defaultChartData = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
const defaultChartData = {
duration: "1h",
measurements: {},
datasets: [
{
data: [65, 59, 80, 81, 56, 55, 40],
@ -178,147 +24,123 @@ const Design = () => {
borderWidth: 1,
},
],
labels: ["January", "February", "March", "April", "May", "June", "July"],
};
const Design = () => {
const { selectedChartId } = useWidgetStore();
const [styles, setStyles] = useState<Record<string, typeof defaultStyle>>({});
const currentStyle = selectedChartId
? styles[selectedChartId.id] || defaultStyle
: defaultStyle;
const updateStyle = (updates: Partial<typeof defaultStyle>) => {
if (!selectedChartId) return;
setStyles((prev) => ({
...prev,
[selectedChartId.id]: { ...currentStyle, ...updates },
}));
};
const elementOptions = chartElements.map((el) => {
let displayName = el.tagName;
if (el.className) displayName += `.${el.className}`;
if (el.textContent)
displayName += ` (${el.textContent.substring(0, 20)}${
el.textContent.length > 20 ? "..." : ""
})`;
return {
display: displayName,
value: el.selector,
};
});
useEffect(() => {
console.log("Styles", styles);
}, [styles]);
return (
<div className="design">
<div className="selectedWidget">
{selectedChartId?.title ? selectedChartId?.header : "Widget 1"}
<div className="appearance-container">
<div className="header-container">
<div className="head">Appearance</div>
<div className="icon">
<ArrowIcon />
</div>
</div>
<div className="reviewChart" ref={chartRef}>
{selectedChartId?.title ? (
<ChartComponent
type={selectedChartId.type ?? "bar"}
title={selectedChartId.title}
data={selectedChartId.data ?? defaultChartData}
/>
) : (
<SimpleCard
header={selectedChartId?.header ?? ""}
icon={WalletIcon}
value={selectedChartId?.value ?? ""}
per={selectedChartId?.per ?? ""}
/>
)}
</div>
<div className="optionsContainer">
<div className="option">
<span>Element to Style</span>
<div className="appearance-style">
<div className="theme-wrapper">
<div className="key">Theme</div>
<div className="value">
<RegularDropDown
header={selectedElementToStyle ?? "Select Element"}
options={
elementOptions.length > 0
? elementOptions.map((opt) => opt.display)
: ["No elements found"]
}
onSelect={(value) => {
const selected = elementOptions.find(
(opt) => opt.display === value
);
setSelectedElementToStyle(selected?.value ?? null);
}}
header={currentStyle.theme}
options={["Glass", "Fill", "Transparent"]}
onSelect={(theme) => updateStyle({ theme })}
/>
</div>
<div className="option">
<span>Name</span>
<input
type="text"
value={nameInput}
onChange={handleNameChange}
placeholder="Enter name"
/>
</div>
{selectedChartId?.title && (
<div className="option">
<span>Chart Type</span>
<RegularDropDown
header={selectedChartId?.type ?? "Select Type"}
options={["bar", "line", "pie", "doughnut", "radar", "polarArea"]}
onSelect={(value) => {
handleUpdateWidget({ type: value });
}}
{currentStyle.theme === "Glass" && (
<div className="blurEffect-wrapper">
<InputRange
label="Blur Effects"
disabled={false}
value={currentStyle.blurEffect}
min={0}
max={50}
onChange={(blurEffect) => updateStyle({ blurEffect })}
onPointerUp={() => {}}
/>
</div>
)}
<div className="option">
<span>Font Family</span>
<RegularDropDown
header={selectedChartId?.fontFamily ?? "Select Font"}
options={["Arial", "Roboto", "Sans-serif"]}
onSelect={(value) => setSelectedFont(value)}
{currentStyle.theme !== "Fill" && (
<div className="opacity-wrapper">
<InputRange
label="Opacity"
disabled={false}
value={currentStyle.opacity}
min={0}
max={50}
onChange={(opacity) => updateStyle({ opacity })}
onPointerUp={() => {}}
/>
</div>
)}
<div className="option">
<span>Size</span>
<RegularDropDown
header={selectedChartId?.fontSize ?? "Select Size"}
options={["12px", "14px", "16px", "18px"]}
onSelect={(value) => setSelectedSize(value)}
/>
</div>
<div className="option">
<span>Weight</span>
<RegularDropDown
header={selectedChartId?.fontWeight ?? "Select Weight"}
options={["Light", "Regular", "Bold"]}
onSelect={(value) => setSelectedWeight(value)}
/>
</div>
<div className="option">
<button
className="header"
onClick={() => setShowColorPicker((prev) => !prev)}
>
<span>Element Color</span>
<div className="icon"></div>
</button>
{showColorPicker && (
<div className="colorDisplayer">
<div className="color-wrapper">
<div className="key">Color</div>
<div className="value">
<input
type="color"
value={elementColor}
onChange={(e) => {
setElementColor(e.target.value);
if (selectedChartId?.data) {
handleUpdateWidget({
data: {
...selectedChartId.data,
datasets: [
{
...selectedChartId.data.datasets[0],
backgroundColor: e.target.value,
},
],
},
});
}
value={currentStyle.elementColor}
onChange={(e) => updateStyle({ elementColor: e.target.value })}
/>
<span style={{ marginLeft: "10px" }}>
{currentStyle.elementColor}
</span>
</div>
</div>
</div>
</div>
<div className="element-container">
<div className="display-element">
<ChartComponent
type={selectedChartId?.type ?? "bar"}
title={selectedChartId?.title ?? "Chart"}
data={{
labels: selectedChartId?.data?.labels ?? defaultChartData.labels,
datasets: selectedChartId?.data?.datasets?.length
? selectedChartId.data.datasets
: defaultChartData.datasets,
}}
/>
<span style={{ marginLeft: "10px" }}>{elementColor}</span>
</div>
)}
<div className="name-wrapper">
<div className="key">Name</div>
<input className="value" type="text" />
</div>
<div className="element-wrapper">
<div className="key">Element</div>
<div className="value">
<RegularDropDown
header={currentStyle.selectedElement}
options={["Glass", "Fill", "Transparent"]}
onSelect={(selectedElement) => updateStyle({ selectedElement })}
/>
</div>
</div>
</div>
</div>

View File

@ -5,10 +5,14 @@ import { useLogger } from "./LoggerContext";
import { GetLogIcon } from "../../footer/getLogIcons";
const LogList: React.FC = () => {
const { logs, clear, setIsLogListVisible } = useLogger();
const [selectedTab, setSelectedTab] = useState<
"all" | "info" | "warning" | "error" | "log" | "success"
>("all");
const {
logs,
clear,
setIsLogListVisible,
isLogListVisible,
selectedTab,
setSelectedTab,
} = useLogger();
const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
@ -18,18 +22,17 @@ const LogList: React.FC = () => {
: [...logs].filter((log) => log.type === selectedTab).reverse();
useEffect(() => {
if (logs.length > 0) {
if (isLogListVisible && logs.length > 0) {
const lastLog = logs[logs.length - 1];
const validTypes = ["all", "info", "warning", "error"];
if (validTypes.includes(lastLog.type)) {
console.log("lastLog.type: ", lastLog.type);
setSelectedTab(lastLog.type);
} else {
setSelectedTab("all");
}
}
}, [logs]);
}, [isLogListVisible]);
return (
// eslint-disable-next-line

View File

@ -1,5 +1,12 @@
// LoggerProvider.tsx
import React, { createContext, useContext, useState, useCallback, useMemo, useEffect } from "react";
import React, {
createContext,
useContext,
useState,
useCallback,
useMemo,
useEffect,
} from "react";
import { MathUtils } from "three";
export type LogType = "log" | "info" | "warning" | "error" | "success";
@ -16,6 +23,8 @@ interface LoggerContextValue {
setLogs: React.Dispatch<React.SetStateAction<LogEntry[]>>;
isLogListVisible: boolean;
setIsLogListVisible: React.Dispatch<React.SetStateAction<boolean>>;
selectedTab: LogType | "all";
setSelectedTab: React.Dispatch<React.SetStateAction<LogType | "all">>;
log: (message: string) => void;
info: (message: string) => void;
warn: (message: string) => void;
@ -31,9 +40,9 @@ export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
}) => {
const [logs, setLogs] = useState<LogEntry[]>([]);
const [isLogListVisible, setIsLogListVisible] = useState<boolean>(false);
const [selectedTab, setSelectedTab] = useState<LogType | "all">("all");
const addLog = useCallback(
(type: LogType, message: string) => {
const addLog = useCallback((type: LogType, message: string) => {
const newLog: LogEntry = {
id: MathUtils.generateUUID(),
type,
@ -41,9 +50,7 @@ export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
timestamp: new Date(),
};
setLogs((prevLogs) => [...prevLogs, newLog]);
},
[]
);
}, []);
const loggerMethods: LoggerContextValue = useMemo(
() => ({
@ -51,17 +58,33 @@ export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
setLogs,
isLogListVisible,
setIsLogListVisible,
selectedTab,
setSelectedTab,
log: (message: string) => addLog("log", message),
info: (message: string) => addLog("info", message),
warn: (message: string) => addLog("warning", message),
error: (message: string) => addLog("error", message),
success: (message: string) => addLog("success", message),
clear: () => setLogs([]),
}),
[logs, setLogs, isLogListVisible, setIsLogListVisible, addLog]
clear: () => {
if (selectedTab !== "all") {
setLogs((prevLogs) =>
prevLogs.filter((log) => log.type !== selectedTab)
);
} else {
setLogs([]);
}
},
}),
[
logs,
setLogs,
isLogListVisible,
setIsLogListVisible,
selectedTab,
setSelectedTab,
addLog,
]
);
// Attach logger globally to window object
useEffect(() => {
(window as any).echo = {
log: loggerMethods.log,

View File

@ -98,8 +98,6 @@ export const DraggableWidget = ({
const deleteSelectedChart = async () => {
try {
console.log("delete");
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
let deleteWidget = {
@ -111,7 +109,6 @@ export const DraggableWidget = ({
if (visualizationSocket) {
setSelectedChartId(null);
visualizationSocket.emit("v2:viz-widget:delete", deleteWidget);
console.log("delete widget", selectedChartId);
}
const updatedWidgets = selectedZone.widgets.filter(
(w: Widget) => w.id !== widget.id
@ -313,7 +310,6 @@ export const DraggableWidget = ({
ref={chartWidget}
onClick={() => {
setSelectedChartId(widget);
console.log("click");
}}
>
{/* Kebab Icon */}

View File

@ -12,12 +12,11 @@ import {
} from "../../../../../components/icons/3dChartIcons";
const TotalCardComponent = ({ object }: any) => {
console.log('object: ', object);
const [progress, setProgress] = useState<any>(0);
const [measurements, setmeasurements] = useState<any>({});
const [duration, setDuration] = useState("1h");
const [name, setName] = useState(object.header ? object.header : "");
console.log('name: ', name);
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
const { header, flotingDuration, flotingMeasurements } = useChartStore();

View File

@ -829,22 +829,115 @@
display: flex;
flex-direction: column;
gap: 15px;
padding: 0;
font-size: var(--font-weight-regular);
color: var(--text-color);
padding: 12px;
.reviewChart {
width: 100%;
.appearance-container,
.element-container {
.floating {
width: 100%;
background: var(--background-color);
backdrop-filter: blur(20px);
border-radius: 15px;
outline: 1px solid var(--border-color);
padding: 10px;
display: flex;
flex-direction: column;
gap: 12px;
.header-container {
padding: 0;
height: auto;
}
.appearance-style {
display: flex;
align-items: center;
justify-content: space-between;
flex-direction: column;
gap: 12px;
.regularDropdown-container {
.dropdown-options {
width: 130%;
left: -15%;
}
.dropdown-header {
gap: 12px;
}
}
.selectedWidget {
padding: 6px 12px;
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
.color-wrapper,
.opacity-wrapper,
.blurEffect-wrapper,
.theme-wrapper {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
.input-range-container {
width: 100%;
padding: 0;
.input-container {}
}
}
.theme-wrapper {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.color-wrapper {
flex-direction: row;
.value {
display: flex;
align-items: center;
input {
width: 34px;
height: 24px;
border-radius: 12px;
padding: 0;
}
}
}
}
}
.element-container {
padding: 8px;
.display-element {
width: 100%;
height: 150px;
background: var(--background-color);
backdrop-filter: blur(20px);
border-radius: 5px;
outline: 1px solid var(--border-color);
}
.name-wrapper,
.element-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
.value {
width: 60%;
}
}
}
}