Merge remote-tracking branch 'origin/ui' into simulation

This commit is contained in:
2025-04-09 18:41:30 +05:30
15 changed files with 827 additions and 647 deletions

View File

@@ -220,8 +220,8 @@ const ProductionCapacity: React.FC<ProductionCapacityProps> = ({
// e.stopPropagation(); // e.stopPropagation();
}} }}
wrapperClass="pointer-none" wrapperClass="pointer-none"
className={`${selectedChartId?.id === id ? "activeChart" : ""}`}
> >
<div <div
className={`productionCapacity-wrapper card ${selectedChartId?.id === id ? "activeChart" : ""}`} className={`productionCapacity-wrapper card ${selectedChartId?.id === id ? "activeChart" : ""}`}
onClick={() => setSelectedChartId({ id: id, type: type })} onClick={() => setSelectedChartId({ id: id, type: type })}

View File

@@ -46,20 +46,32 @@ interface ReturnOfInvestmentProps {
rotation: [number, number, number]; rotation: [number, number, number];
onContextMenu?: (event: React.MouseEvent) => void; onContextMenu?: (event: React.MouseEvent) => void;
} }
const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({ id, type, position, rotation, onContextMenu }) => { const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({
id,
type,
position,
rotation,
onContextMenu,
}) => {
const { selectedChartId, setSelectedChartId } = useWidgetStore(); const { selectedChartId, setSelectedChartId } = useWidgetStore();
const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); const {
measurements: chartMeasurements,
duration: chartDuration,
name: widgetName,
} = useChartStore();
const [measurements, setmeasurements] = useState<any>({}); const [measurements, setmeasurements] = useState<any>({});
const [duration, setDuration] = useState("1h") const [duration, setDuration] = useState("1h");
const [name, setName] = useState("Widget") const [name, setName] = useState("Widget");
const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ const [chartData, setChartData] = useState<{
labels: string[];
datasets: any[];
}>({
labels: [], labels: [],
datasets: [], datasets: [],
}); });
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0] const organization = email?.split("@")[1]?.split(".")[0];
// Improved sample data for the smooth curve graph (single day) // Improved sample data for the smooth curve graph (single day)
const graphData: ChartData<"line"> = { const graphData: ChartData<"line"> = {
labels: [ labels: [
@@ -129,7 +141,8 @@ const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({ id, type, posit
}; };
useEffect(() => { useEffect(() => {
if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return; if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
return;
const socket = io(`http://${iotApiUrl}`); const socket = io(`http://${iotApiUrl}`);
@@ -139,7 +152,6 @@ const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({ id, type, posit
interval: 1000, interval: 1000,
}; };
const startStream = () => { const startStream = () => {
socket.emit("lineInput", inputData); socket.emit("lineInput", inputData);
}; };
@@ -157,8 +169,10 @@ const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({ id, type, posit
return { return {
label: datasetKey, label: datasetKey,
data: responseData[datasetKey]?.values ?? [], data: responseData[datasetKey]?.values ?? [],
borderColor: index === 0 ? "rgba(75, 192, 192, 1)" : "rgba(255, 99, 132, 1)", // Light blue color borderColor:
backgroundColor: index === 0 ? "rgba(75, 192, 192, 0.2)" : "rgba(255, 99, 132, 0.2)", index === 0 ? "rgba(75, 192, 192, 1)" : "rgba(255, 99, 132, 1)", // Light blue color
backgroundColor:
index === 0 ? "rgba(75, 192, 192, 0.2)" : "rgba(255, 99, 132, 0.2)",
fill: true, fill: true,
tension: 0.4, // Smooth curve effect tension: 0.4, // Smooth curve effect
pointRadius: 0, // Hide dots pointRadius: 0, // Hide dots
@@ -177,14 +191,15 @@ const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({ id, type, posit
}, [measurements, duration, iotApiUrl]); }, [measurements, duration, iotApiUrl]);
const fetchSavedInputes = async () => { const fetchSavedInputes = async () => {
if (id !== "") { if (id !== "") {
try { try {
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`); const response = await axios.get(
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
);
if (response.status === 200) { if (response.status === 200) {
setmeasurements(response.data.Data.measurements) setmeasurements(response.data.Data.measurements);
setDuration(response.data.Data.duration) setDuration(response.data.Data.duration);
setName(response.data.widgetName) setName(response.data.widgetName);
} else { } else {
console.log("Unexpected response:", response); console.log("Unexpected response:", response);
} }
@@ -192,7 +207,7 @@ const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({ id, type, posit
console.error("There was an error!", error); console.error("There was an error!", error);
} }
} }
} };
useEffect(() => { useEffect(() => {
fetchSavedInputes(); fetchSavedInputes();
@@ -202,8 +217,7 @@ const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({ id, type, posit
if (selectedChartId?.id === id) { if (selectedChartId?.id === id) {
fetchSavedInputes(); fetchSavedInputes();
} }
} }, [chartMeasurements, chartDuration, widgetName]);
, [chartMeasurements, chartDuration, widgetName])
const rotationDegrees = { const rotationDegrees = {
x: (rotation[0] * 180) / Math.PI, x: (rotation[0] * 180) / Math.PI,
y: (rotation[1] * 180) / Math.PI, y: (rotation[1] * 180) / Math.PI,
@@ -215,27 +229,32 @@ const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({ id, type, posit
}; };
return ( return (
<Html position={[position[0], position[1], position[2]]} <Html
position={[position[0], position[1], position[2]]}
scale={[0.5, 0.5, 0.5]} scale={[0.5, 0.5, 0.5]}
transform transform
zIndexRange={[1, 0]} zIndexRange={[1, 0]}
sprite sprite
style={{ style={{
transform: transformStyle.transform, transform: transformStyle.transform,
transformStyle: 'preserve-3d', transformStyle: "preserve-3d",
transition: 'transform 0.1s ease-out' transition: "transform 0.1s ease-out",
}} }}
className={`${selectedChartId?.id === id ? "activeChart" : ""}`}
> >
<div className="returnOfInvestment card" <div
className={`returnOfInvestment card ${
selectedChartId?.id === id ? "activeChart" : ""
}`}
onClick={() => setSelectedChartId({ id: id, type: type })} onClick={() => setSelectedChartId({ id: id, type: type })}
onContextMenu={onContextMenu} onContextMenu={onContextMenu}
> >
<div className="header">Return of Investment</div> <div className="header">Return of Investment</div>
<div className="lineGraph charts"> <div className="lineGraph charts">
{/* Smooth curve graph with two datasets */} {/* Smooth curve graph with two datasets */}
<SmoothLineGraphComponent data={Object.keys(measurements).length > 0 ? chartData : graphData} options={graphOptions} /> <SmoothLineGraphComponent
data={Object.keys(measurements).length > 0 ? chartData : graphData}
options={graphOptions}
/>
</div> </div>
<div className="returns-wrapper"> <div className="returns-wrapper">
<div className="icon"> <div className="icon">

View File

@@ -13,16 +13,26 @@ interface StateWorkingProps {
rotation: [number, number, number]; rotation: [number, number, number];
onContextMenu?: (event: React.MouseEvent) => void; onContextMenu?: (event: React.MouseEvent) => void;
} }
const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotation, onContextMenu }) => { const StateWorking: React.FC<StateWorkingProps> = ({
id,
type,
position,
rotation,
onContextMenu,
}) => {
const { selectedChartId, setSelectedChartId } = useWidgetStore(); const { selectedChartId, setSelectedChartId } = useWidgetStore();
const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); const {
measurements: chartMeasurements,
duration: chartDuration,
name: widgetName,
} = useChartStore();
const [measurements, setmeasurements] = useState<any>({}); const [measurements, setmeasurements] = useState<any>({});
const [duration, setDuration] = useState("1h") const [duration, setDuration] = useState("1h");
const [name, setName] = useState("Widget") const [name, setName] = useState("Widget");
const [datas, setDatas] = useState<any>({}); const [datas, setDatas] = useState<any>({});
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0] const organization = email?.split("@")[1]?.split(".")[0];
// const datas = [ // const datas = [
// { key: "Oil Tank:", value: "24/341" }, // { key: "Oil Tank:", value: "24/341" },
// { key: "Oil Refin:", value: 36.023 }, // { key: "Oil Refin:", value: 36.023 },
@@ -33,7 +43,8 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
// ]; // ];
useEffect(() => { useEffect(() => {
if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return; if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
return;
const socket = io(`http://${iotApiUrl}`); const socket = io(`http://${iotApiUrl}`);
const inputData = { const inputData = {
measurements, measurements,
@@ -46,7 +57,6 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
socket.on("connect", startStream); socket.on("connect", startStream);
socket.on("lastOutput", (response) => { socket.on("lastOutput", (response) => {
const responseData = response; const responseData = response;
setDatas(responseData); setDatas(responseData);
}); });
@@ -59,14 +69,15 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
}, [measurements, duration, iotApiUrl]); }, [measurements, duration, iotApiUrl]);
const fetchSavedInputes = async () => { const fetchSavedInputes = async () => {
if (id !== "") { if (id !== "") {
try { try {
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`); const response = await axios.get(
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
);
if (response.status === 200) { if (response.status === 200) {
setmeasurements(response.data.Data.measurements) setmeasurements(response.data.Data.measurements);
setDuration(response.data.Data.duration) setDuration(response.data.Data.duration);
setName(response.data.widgetName) setName(response.data.widgetName);
} else { } else {
console.log("Unexpected response:", response); console.log("Unexpected response:", response);
} }
@@ -74,10 +85,7 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
console.error("There was an error!", error); console.error("There was an error!", error);
} }
} }
} };
useEffect(() => { useEffect(() => {
fetchSavedInputes(); fetchSavedInputes();
@@ -87,8 +95,7 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
if (selectedChartId?.id === id) { if (selectedChartId?.id === id) {
fetchSavedInputes(); fetchSavedInputes();
} }
} }, [chartMeasurements, chartDuration, widgetName]);
, [chartMeasurements, chartDuration, widgetName])
const rotationDegrees = { const rotationDegrees = {
x: (rotation[0] * 180) / Math.PI, x: (rotation[0] * 180) / Math.PI,
@@ -100,20 +107,22 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`, transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`,
}; };
return ( return (
<Html position={[position[0], position[1], position[2]]} <Html
position={[position[0], position[1], position[2]]}
scale={[0.5, 0.5, 0.5]} scale={[0.5, 0.5, 0.5]}
transform transform
zIndexRange={[1, 0]} zIndexRange={[1, 0]}
sprite sprite
style={{ style={{
transform: transformStyle.transform, transform: transformStyle.transform,
transformStyle: 'preserve-3d', transformStyle: "preserve-3d",
transition: 'transform 0.1s ease-out' transition: "transform 0.1s ease-out",
}} }}
className={`${selectedChartId?.id === id ? "activeChart" : ""}`}
> >
<div className="stateWorking-wrapper card" <div
className={`stateWorking-wrapper card ${
selectedChartId?.id === id ? "activeChart" : ""
}`}
onClick={() => setSelectedChartId({ id: id, type: type })} onClick={() => setSelectedChartId({ id: id, type: type })}
onContextMenu={onContextMenu} onContextMenu={onContextMenu}
> >
@@ -121,12 +130,10 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
<div className="header"> <div className="header">
<span>State</span> <span>State</span>
<span> <span>
{datas?.input1 ? datas.input1 : 'input1'} <span>.</span> {datas?.input1 ? datas.input1 : "input1"} <span>.</span>
</span> </span>
</div> </div>
<div className="img"> <div className="img">{/* <img src={image} alt="" /> */}</div>
{/* <img src={image} alt="" /> */}
</div>
</div> </div>
{/* Data */} {/* Data */}
<div className="data-wrapper"> <div className="data-wrapper">
@@ -137,28 +144,52 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
</div> </div>
))} */} ))} */}
<div className="data-table"> <div className="data-table">
<div className="data">{measurements?.input2?.fields ? measurements.input2.fields : 'input2'}</div> <div className="data">
<div className="key">{datas?.input2 ? datas.input2 : 'data'}</div> {measurements?.input2?.fields
? measurements.input2.fields
: "input2"}
</div>
<div className="key">{datas?.input2 ? datas.input2 : "data"}</div>
</div> </div>
<div className="data-table"> <div className="data-table">
<div className="data">{measurements?.input3?.fields ? measurements.input3.fields : 'input3'}</div> <div className="data">
<div className="key">{datas?.input3 ? datas.input3 : 'data'}</div> {measurements?.input3?.fields
? measurements.input3.fields
: "input3"}
</div>
<div className="key">{datas?.input3 ? datas.input3 : "data"}</div>
</div> </div>
<div className="data-table"> <div className="data-table">
<div className="data">{measurements?.input4?.fields ? measurements.input4.fields : 'input4'}</div> <div className="data">
<div className="key">{datas?.input4 ? datas.input4 : 'data'}</div> {measurements?.input4?.fields
? measurements.input4.fields
: "input4"}
</div>
<div className="key">{datas?.input4 ? datas.input4 : "data"}</div>
</div> </div>
<div className="data-table"> <div className="data-table">
<div className="data">{measurements?.input5?.fields ? measurements.input5.fields : 'input5'}</div> <div className="data">
<div className="key">{datas?.input5 ? datas.input5 : 'data'}</div> {measurements?.input5?.fields
? measurements.input5.fields
: "input5"}
</div>
<div className="key">{datas?.input5 ? datas.input5 : "data"}</div>
</div> </div>
<div className="data-table"> <div className="data-table">
<div className="data">{measurements?.input6?.fields ? measurements.input6.fields : 'input6'}</div> <div className="data">
<div className="key">{datas?.input6 ? datas.input6 : 'data'}</div> {measurements?.input6?.fields
? measurements.input6.fields
: "input6"}
</div>
<div className="key">{datas?.input6 ? datas.input6 : "data"}</div>
</div> </div>
<div className="data-table"> <div className="data-table">
<div className="data">{measurements?.input7?.fields ? measurements.input7.fields : 'input7'}</div> <div className="data">
<div className="key">{datas?.input7 ? datas.input7 : 'data'}</div> {measurements?.input7?.fields
? measurements.input7.fields
: "input7"}
</div>
<div className="key">{datas?.input7 ? datas.input7 : "data"}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -167,5 +198,3 @@ const StateWorking: React.FC<StateWorkingProps> = ({ id, type, position, rotatio
}; };
export default StateWorking; export default StateWorking;

View File

@@ -49,20 +49,32 @@ interface ThroughputProps {
onContextMenu?: (event: React.MouseEvent) => void; onContextMenu?: (event: React.MouseEvent) => void;
} }
const Throughput: React.FC<ThroughputProps> = ({ id, type, position, rotation, onContextMenu }) => { const Throughput: React.FC<ThroughputProps> = ({
id,
type,
position,
rotation,
onContextMenu,
}) => {
const { selectedChartId, setSelectedChartId } = useWidgetStore(); const { selectedChartId, setSelectedChartId } = useWidgetStore();
const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore(); const {
measurements: chartMeasurements,
duration: chartDuration,
name: widgetName,
} = useChartStore();
const [measurements, setmeasurements] = useState<any>({}); const [measurements, setmeasurements] = useState<any>({});
const [duration, setDuration] = useState("1h") const [duration, setDuration] = useState("1h");
const [name, setName] = useState("Widget") const [name, setName] = useState("Widget");
const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ const [chartData, setChartData] = useState<{
labels: string[];
datasets: any[];
}>({
labels: [], labels: [],
datasets: [], datasets: [],
}); });
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0] const organization = email?.split("@")[1]?.split(".")[0];
// Sample data for the line graph // Sample data for the line graph
const graphData: ChartData<"line"> = { const graphData: ChartData<"line"> = {
@@ -112,7 +124,8 @@ const Throughput: React.FC<ThroughputProps> = ({ id, type, position, rotation, o
}; };
useEffect(() => { useEffect(() => {
if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return; if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
return;
const socket = io(`http://${iotApiUrl}`); const socket = io(`http://${iotApiUrl}`);
@@ -122,7 +135,6 @@ const Throughput: React.FC<ThroughputProps> = ({ id, type, position, rotation, o
interval: 1000, interval: 1000,
}; };
const startStream = () => { const startStream = () => {
socket.emit("lineInput", inputData); socket.emit("lineInput", inputData);
}; };
@@ -157,14 +169,15 @@ const Throughput: React.FC<ThroughputProps> = ({ id, type, position, rotation, o
}, [measurements, duration, iotApiUrl]); }, [measurements, duration, iotApiUrl]);
const fetchSavedInputes = async () => { const fetchSavedInputes = async () => {
if (id !== "") { if (id !== "") {
try { try {
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`); const response = await axios.get(
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
);
if (response.status === 200) { if (response.status === 200) {
setmeasurements(response.data.Data.measurements) setmeasurements(response.data.Data.measurements);
setDuration(response.data.Data.duration) setDuration(response.data.Data.duration);
setName(response.data.widgetName) setName(response.data.widgetName);
} else { } else {
console.log("Unexpected response:", response); console.log("Unexpected response:", response);
} }
@@ -172,7 +185,7 @@ const Throughput: React.FC<ThroughputProps> = ({ id, type, position, rotation, o
console.error("There was an error!", error); console.error("There was an error!", error);
} }
} }
} };
useEffect(() => { useEffect(() => {
fetchSavedInputes(); fetchSavedInputes();
@@ -182,8 +195,7 @@ const Throughput: React.FC<ThroughputProps> = ({ id, type, position, rotation, o
if (selectedChartId?.id === id) { if (selectedChartId?.id === id) {
fetchSavedInputes(); fetchSavedInputes();
} }
} }, [chartMeasurements, chartDuration, widgetName]);
, [chartMeasurements, chartDuration, widgetName])
const rotationDegrees = { const rotationDegrees = {
x: (rotation[0] * 180) / Math.PI, x: (rotation[0] * 180) / Math.PI,
y: (rotation[1] * 180) / Math.PI, y: (rotation[1] * 180) / Math.PI,
@@ -195,20 +207,22 @@ const Throughput: React.FC<ThroughputProps> = ({ id, type, position, rotation, o
}; };
return ( return (
<Html position={[position[0], position[1], position[2]]} <Html
position={[position[0], position[1], position[2]]}
scale={[0.5, 0.5, 0.5]} scale={[0.5, 0.5, 0.5]}
transform transform
zIndexRange={[1, 0]} zIndexRange={[1, 0]}
sprite sprite
style={{ style={{
transform: transformStyle.transform, transform: transformStyle.transform,
transformStyle: 'preserve-3d', transformStyle: "preserve-3d",
transition: 'transform 0.1s ease-out' transition: "transform 0.1s ease-out",
}} }}
className={`${selectedChartId?.id === id ? "activeChart" : ""}`}
> >
<div className="throughput-wrapper" <div
className={`throughput-wrapper card ${
selectedChartId?.id === id ? "activeChart" : ""
}`}
onClick={() => setSelectedChartId({ id: id, type: type })} onClick={() => setSelectedChartId({ id: id, type: type })}
onContextMenu={onContextMenu} onContextMenu={onContextMenu}
> >
@@ -235,7 +249,10 @@ const Throughput: React.FC<ThroughputProps> = ({ id, type, position, rotation, o
</div> </div>
<div className="line-graph"> <div className="line-graph">
{/* Line graph using react-chartjs-2 */} {/* Line graph using react-chartjs-2 */}
<LineGraphComponent data={Object.keys(measurements).length > 0 ? chartData : graphData} options={graphOptions} /> <LineGraphComponent
data={Object.keys(measurements).length > 0 ? chartData : graphData}
options={graphOptions}
/>
</div> </div>
<div className="footer"> <div className="footer">
You made an extra <span className="value">$1256.13</span> this month You made an extra <span className="value">$1256.13</span> this month

View File

@@ -148,7 +148,7 @@ const Assets: React.FC = () => {
<div className="assets-container"> <div className="assets-container">
{categoryAssets && {categoryAssets &&
categoryAssets?.map((asset: any, index: number) => ( categoryAssets?.map((asset: any, index: number) => (
<div key={index} className="assets"> <div key={index} className="assets" id={asset.filename}>
<img <img
src={asset?.thumbnail} src={asset?.thumbnail}
alt={asset.filename} alt={asset.filename}
@@ -172,6 +172,7 @@ const Assets: React.FC = () => {
<div className="assets-wrapper"> <div className="assets-wrapper">
<div <div
className="back-button" className="back-button"
id="asset-backButtom"
onClick={() => { onClick={() => {
setSelectedCategory(null); setSelectedCategory(null);
setCategoryAssets([]); setCategoryAssets([]);
@@ -183,7 +184,7 @@ const Assets: React.FC = () => {
<div className="assets-container"> <div className="assets-container">
{categoryAssets && {categoryAssets &&
categoryAssets?.map((asset: any, index: number) => ( categoryAssets?.map((asset: any, index: number) => (
<div key={index} className="assets"> <div key={index} className="assets" id={asset.filename}>
<img <img
src={asset?.thumbnail} src={asset?.thumbnail}
alt={asset.filename} alt={asset.filename}
@@ -223,6 +224,7 @@ const Assets: React.FC = () => {
<div <div
key={index} key={index}
className="category" className="category"
id={category}
onClick={() => fetchCategoryAssets(category)} onClick={() => fetchCategoryAssets(category)}
> >
<img <img

View File

@@ -4,6 +4,7 @@ import useTemplateStore from "../../../../store/useTemplateStore";
import { useSelectedZoneStore } from "../../../../store/useZoneStore"; import { useSelectedZoneStore } from "../../../../store/useZoneStore";
import { getTemplateData } from "../../../../services/realTimeVisulization/zoneData/getTemplate"; import { getTemplateData } from "../../../../services/realTimeVisulization/zoneData/getTemplate";
import { useSocketStore } from "../../../../store/store"; import { useSocketStore } from "../../../../store/store";
import RenameInput from "../../../ui/inputs/RenameInput";
const Templates = () => { const Templates = () => {
const { templates, removeTemplate, setTemplates } = useTemplateStore(); const { templates, removeTemplate, setTemplates } = useTemplateStore();
@@ -34,7 +35,10 @@ const Templates = () => {
templateID: id, templateID: id,
}; };
if (visualizationSocket) { if (visualizationSocket) {
visualizationSocket.emit("v2:viz-template:deleteTemplate", deleteTemplate); visualizationSocket.emit(
"v2:viz-template:deleteTemplate",
deleteTemplate
);
} }
removeTemplate(id); removeTemplate(id);
} catch (error) { } catch (error) {
@@ -65,11 +69,15 @@ const Templates = () => {
widgets: template.widgets, widgets: template.widgets,
}); });
useDroppedObjectsStore.getState().setZone(selectedZone.zoneName, selectedZone.zoneId); useDroppedObjectsStore
.getState()
.setZone(selectedZone.zoneName, selectedZone.zoneId);
if (Array.isArray(template.floatingWidget)) { if (Array.isArray(template.floatingWidget)) {
template.floatingWidget.forEach((val: any) => { template.floatingWidget.forEach((val: any) => {
useDroppedObjectsStore.getState().addObject(selectedZone.zoneName, val); useDroppedObjectsStore
.getState()
.addObject(selectedZone.zoneName, val);
}); });
} }
} catch (error) { } catch (error) {
@@ -79,7 +87,7 @@ const Templates = () => {
return ( return (
<div className="template-list"> <div className="template-list">
{templates.map((template) => ( {templates.map((template, index) => (
<div key={template.id} className="template-item"> <div key={template.id} className="template-item">
{template?.snapshot && ( {template?.snapshot && (
<div className="template-image-container"> <div className="template-image-container">
@@ -96,7 +104,8 @@ const Templates = () => {
onClick={() => handleLoadTemplate(template)} onClick={() => handleLoadTemplate(template)}
className="template-name" className="template-name"
> >
{template.name} {/* {`Template ${index + 1}`} */}
<RenameInput value={`Template ${index + 1}`} />
</div> </div>
<button <button
onClick={() => handleDeleteTemplate(template.id)} onClick={() => handleDeleteTemplate(template.id)}

View File

@@ -1,12 +1,10 @@
import React, { useEffect } from "react"; import React from "react";
import { import {
CleanPannel, CleanPannel,
EyeIcon, EyeIcon,
LockIcon, LockIcon,
} from "../../icons/RealTimeVisulationIcons"; } from "../../icons/RealTimeVisulationIcons";
import { panelData } from "../../../services/realTimeVisulization/zoneData/panel";
import { AddIcon } from "../../icons/ExportCommonIcons"; import { AddIcon } from "../../icons/ExportCommonIcons";
import { deletePanelApi } from "../../../services/realTimeVisulization/zoneData/deletePanel";
import { useSocketStore } from "../../../store/store"; import { useSocketStore } from "../../../store/store";
import { clearPanel } from "../../../services/realTimeVisulization/zoneData/clearPanel"; import { clearPanel } from "../../../services/realTimeVisulization/zoneData/clearPanel";
import { lockPanel } from "../../../services/realTimeVisulization/zoneData/lockPanel"; import { lockPanel } from "../../../services/realTimeVisulization/zoneData/lockPanel";
@@ -14,6 +12,11 @@ import { lockPanel } from "../../../services/realTimeVisulization/zoneData/lockP
// Define the type for `Side` // Define the type for `Side`
type Side = "top" | "bottom" | "left" | "right"; type Side = "top" | "bottom" | "left" | "right";
// Define the type for HiddenPanels, where keys are zone IDs and values are arrays of hidden sides
interface HiddenPanels {
[zoneId: string]: Side[];
}
// Define the type for the props passed to the Buttons component // Define the type for the props passed to the Buttons component
interface ButtonsProps { interface ButtonsProps {
selectedZone: { selectedZone: {
@@ -37,7 +40,6 @@ interface ButtonsProps {
zoneName: string; zoneName: string;
activeSides: Side[]; activeSides: Side[];
panelOrder: Side[]; panelOrder: Side[];
lockedPanels: Side[]; lockedPanels: Side[];
zoneId: string; zoneId: string;
zoneViewPortTarget: number[]; zoneViewPortTarget: number[];
@@ -51,8 +53,8 @@ interface ButtonsProps {
}[]; }[];
}> }>
>; >;
hiddenPanels: Side[]; // Add this prop for hidden panels hiddenPanels: HiddenPanels; // Updated prop type
setHiddenPanels: React.Dispatch<React.SetStateAction<Side[]>>; // Add this prop for updating hidden panels setHiddenPanels: React.Dispatch<React.SetStateAction<HiddenPanels>>; // Updated prop type
} }
const AddButtons: React.FC<ButtonsProps> = ({ const AddButtons: React.FC<ButtonsProps> = ({
@@ -63,11 +65,33 @@ const AddButtons: React.FC<ButtonsProps> = ({
}) => { }) => {
const { visualizationSocket } = useSocketStore(); const { visualizationSocket } = useSocketStore();
// Local state to track hidden panels // Function to toggle visibility of a panel
const toggleVisibility = (side: Side) => {
const isHidden = hiddenPanels[selectedZone.zoneId]?.includes(side) ?? false;
if (isHidden) {
// If the panel is already hidden, remove it from the hiddenPanels array for this zone
setHiddenPanels((prevHiddenPanels) => ({
...prevHiddenPanels,
[selectedZone.zoneId]: prevHiddenPanels[selectedZone.zoneId].filter(
(panel) => panel !== side
),
}));
} else {
// If the panel is visible, add it to the hiddenPanels array for this zone
setHiddenPanels((prevHiddenPanels) => ({
...prevHiddenPanels,
[selectedZone.zoneId]: [
...(prevHiddenPanels[selectedZone.zoneId] || []),
side,
],
}));
}
};
// Function to toggle lock/unlock a panel // Function to toggle lock/unlock a panel
const toggleLockPanel = async (side: Side) => { const toggleLockPanel = async (side: Side) => {
console.log('side: ', side); // console.log('side: ', side);
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value
//add api //add api
@@ -99,23 +123,10 @@ const AddButtons: React.FC<ButtonsProps> = ({
}; };
// Function to toggle visibility of a panel
const toggleVisibility = (side: Side) => {
const isHidden = hiddenPanels.includes(side);
if (isHidden) {
// If the panel is already hidden, remove it from the hiddenPanels array
setHiddenPanels(hiddenPanels.filter((panel) => panel !== side));
} else {
// If the panel is visible, add it to the hiddenPanels array
setHiddenPanels([...hiddenPanels, side]);
}
};
// Function to clean all widgets from a panel // Function to clean all widgets from a panel
const cleanPanel = async (side: Side) => { const cleanPanel = async (side: Side) => {
//add api //add api
console.log('side: ', side); // console.log('side: ', side);
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value
@@ -135,7 +146,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
widgets: cleanedWidgets, widgets: cleanedWidgets,
}; };
// Update the selectedZone state // Update the selectedZone state
console.log('updatedZone: ', updatedZone); // console.log('updatedZone: ', updatedZone);
setSelectedZone(updatedZone); setSelectedZone(updatedZone);
// let response = await clearPanel(selectedZone.zoneId, organization, side) // let response = await clearPanel(selectedZone.zoneId, organization, side)
@@ -196,8 +207,6 @@ const AddButtons: React.FC<ButtonsProps> = ({
// //
// } // }
} else { } else {
setHiddenPanels(hiddenPanels.filter((panel) => panel !== side));
// Panel does not exist: Create panel // Panel does not exist: Create panel
try { try {
// Get email and organization safely with a default fallback // Get email and organization safely with a default fallback
@@ -234,7 +243,6 @@ const AddButtons: React.FC<ButtonsProps> = ({
} catch (error) { } } catch (error) { }
} }
}; };
return ( return (
<> <>
<div> <div>
@@ -261,16 +269,21 @@ const AddButtons: React.FC<ButtonsProps> = ({
<div className="extra-Bs"> <div className="extra-Bs">
{/* Hide Panel */} {/* Hide Panel */}
<div <div
className={`icon ${hiddenPanels.includes(side) ? "active" : "" className={`icon ${
}`} hiddenPanels[selectedZone.zoneId]?.includes(side)
? "active"
: ""
}`}
title={ title={
hiddenPanels.includes(side) ? "Show Panel" : "Hide Panel" hiddenPanels[selectedZone.zoneId]?.includes(side)
? "Show Panel"
: "Hide Panel"
} }
onClick={() => toggleVisibility(side)} onClick={() => toggleVisibility(side)}
> >
<EyeIcon <EyeIcon
fill={ fill={
hiddenPanels.includes(side) hiddenPanels[selectedZone.zoneId]?.includes(side)
? "var(--primary-color)" ? "var(--primary-color)"
: "var(--text-color)" : "var(--text-color)"
} }

View File

@@ -88,12 +88,15 @@ export const DraggableWidget = ({
const chartWidget = useRef<HTMLDivElement>(null); const chartWidget = useRef<HTMLDivElement>(null);
const isPanelHidden = hiddenPanels.includes(widget.panel); OuterClick({
contextClassName: [
// OuterClick({ "chart-container",
// contextClassName: ["chart-container", "floating", "sidebar-right-wrapper"], "floating",
// setMenuVisible: () => setSelectedChartId(null), "sidebar-right-wrapper",
// }); "card",
],
setMenuVisible: () => setSelectedChartId(null),
});
const deleteSelectedChart = async () => { const deleteSelectedChart = async () => {
try { try {
@@ -397,4 +400,4 @@ export const DraggableWidget = ({
); );
}; };
// by using canvasDimensions.height canvasDimensions.width dynamically div value insted of static 6 and 4 calculate according to canvasDimensions.width canvasDimensions.height

View File

@@ -39,7 +39,7 @@ interface PanelProps {
widgets: Widget[]; widgets: Widget[];
}> }>
>; >;
hiddenPanels: string[]; hiddenPanels: any;
setZonesData: React.Dispatch<React.SetStateAction<any>>; setZonesData: React.Dispatch<React.SetStateAction<any>>;
} }
@@ -139,7 +139,12 @@ const Panel: React.FC<PanelProps> = ({
const handleDrop = (e: React.DragEvent, panel: Side) => { const handleDrop = (e: React.DragEvent, panel: Side) => {
e.preventDefault(); e.preventDefault();
const { draggedAsset } = useWidgetStore.getState(); const { draggedAsset } = useWidgetStore.getState();
if (!draggedAsset || isPanelLocked(panel)) return; if (
!draggedAsset ||
isPanelLocked(panel) ||
hiddenPanels[selectedZone.zoneId]?.includes(panel)
)
return;
const currentWidgetsCount = getCurrentWidgetCount(panel); const currentWidgetsCount = getCurrentWidgetCount(panel);
const maxCapacity = calculatePanelCapacity(panel); const maxCapacity = calculatePanelCapacity(panel);
@@ -261,7 +266,7 @@ const Panel: React.FC<PanelProps> = ({
{` {`
:root { :root {
--topWidth: ${topWidth}; --topWidth: ${topWidth};
--bottomWidth: ${bottomWidth}; --bottomWidth: ${bottomWidth} ;
--leftHeight: ${leftHeight}; --leftHeight: ${leftHeight};
--rightHeight: ${rightHeight}; --rightHeight: ${rightHeight};
@@ -278,7 +283,7 @@ const Panel: React.FC<PanelProps> = ({
key={side} key={side}
id="panel-wrapper" id="panel-wrapper"
className={`panel ${side}-panel absolute ${ className={`panel ${side}-panel absolute ${
hiddenPanels.includes(side) ? "hidePanel" : "" hiddenPanels[selectedZone.zoneId]?.includes(side) ? "hidePanel" : ""
}`} }`}
style={getPanelStyle(side)} style={getPanelStyle(side)}
onDrop={(e) => handleDrop(e, side)} onDrop={(e) => handleDrop(e, side)}
@@ -294,9 +299,11 @@ const Panel: React.FC<PanelProps> = ({
<div <div
className={`panel-content ${isPlaying && "fullScreen"}`} className={`panel-content ${isPlaying && "fullScreen"}`}
style={{ style={{
pointerEvents: selectedZone.lockedPanels.includes(side) pointerEvents:
? "none" selectedZone.lockedPanels.includes(side) ||
: "auto", hiddenPanels[selectedZone.zoneId]?.includes(side)
? "none"
: "auto",
opacity: selectedZone.lockedPanels.includes(side) ? "0.8" : "1", opacity: selectedZone.lockedPanels.includes(side) ? "0.8" : "1",
}} }}
> >

View File

@@ -53,8 +53,13 @@ type Widget = {
data: any; data: any;
}; };
// Define the type for HiddenPanels, where keys are zone IDs and values are arrays of hidden sides
interface HiddenPanels {
[zoneId: string]: Side[];
}
const RealTimeVisulization: React.FC = () => { const RealTimeVisulization: React.FC = () => {
const [hiddenPanels, setHiddenPanels] = React.useState<Side[]>([]); const [hiddenPanels, setHiddenPanels] = React.useState<HiddenPanels>({});
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const { isPlaying } = usePlayButtonStore(); const { isPlaying } = usePlayButtonStore();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
@@ -74,7 +79,6 @@ const RealTimeVisulization: React.FC = () => {
const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption();
const { visualizationSocket } = useSocketStore(); const { visualizationSocket } = useSocketStore();
useEffect(() => { useEffect(() => {
async function GetZoneData() { async function GetZoneData() {
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
@@ -101,7 +105,7 @@ const RealTimeVisulization: React.FC = () => {
{} {}
); );
setZonesData(formattedData); setZonesData(formattedData);
} catch (error) { } } catch (error) {}
} }
GetZoneData(); GetZoneData();
@@ -229,8 +233,6 @@ const RealTimeVisulization: React.FC = () => {
}; };
}, [setRightClickSelected]); }, [setRightClickSelected]);
return ( return (
<> <>
<div <div
@@ -263,7 +265,6 @@ const RealTimeVisulization: React.FC = () => {
}} }}
onDrop={(event) => handleDrop(event)} onDrop={(event) => handleDrop(event)}
onDragOver={(event) => event.preventDefault()} onDragOver={(event) => event.preventDefault()}
> >
<Scene /> <Scene />
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +1,20 @@
import html2canvas from "html2canvas";
export const captureVisualization = async (): Promise<string | null> => { export const captureVisualization = async (): Promise<string | null> => {
const container = document.getElementById("real-time-vis-canvas"); const container = document.getElementById("real-time-vis-canvas");
if (!container) return null; if (!container) return null;
const canvas = document.createElement("canvas"); try {
const ctx = canvas.getContext("2d"); // Use html2canvas to capture the container
if (!ctx) return null; const canvas = await html2canvas(container, {
scale: 1, // Adjust scale for higher/lower resolution
const rect = container.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
// Draw background
ctx.fillStyle = getComputedStyle(container).backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Capture all canvas elements
const canvases = container.querySelectorAll("canvas");
canvases.forEach((childCanvas) => {
const childRect = childCanvas.getBoundingClientRect();
const x = childRect.left - rect.left;
const y = childRect.top - rect.top;
ctx.drawImage(childCanvas, x, y, childRect.width, childRect.height);
}); });
// Capture SVG elements // Convert the canvas to a data URL (PNG format)
const svgs = container.querySelectorAll("svg"); const dataUrl = canvas.toDataURL("image/png");
for (const svg of Array.from(svgs)) { return dataUrl;
const svgString = new XMLSerializer().serializeToString(svg); } catch (error) {
const svgBlob = new Blob([svgString], { type: "image/svg+xml" }); console.error("Error capturing visualization:", error);
const url = URL.createObjectURL(svgBlob); return null;
}
try {
const img = await new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = url;
});
const svgRect = svg.getBoundingClientRect();
ctx.drawImage(
img,
svgRect.left - rect.left,
svgRect.top - rect.top,
svgRect.width,
svgRect.height
);
} finally {
URL.revokeObjectURL(url);
}
}
return canvas.toDataURL("image/png");
}; };

View File

@@ -32,7 +32,6 @@ export const handleSaveTemplate = async ({
try { try {
// Check if the selected zone has any widgets // Check if the selected zone has any widgets
if (!selectedZone.panelOrder || selectedZone.panelOrder.length === 0) { if (!selectedZone.panelOrder || selectedZone.panelOrder.length === 0) {
console.warn("No widgets found in the selected zone.");
return; return;
} }
@@ -65,8 +64,7 @@ export const handleSaveTemplate = async ({
floatingWidget, floatingWidget,
widgets3D, widgets3D,
}; };
console.log('newTemplate: ', newTemplate);
// Extract organization from email // Extract organization from email
const email = localStorage.getItem("email") || ""; const email = localStorage.getItem("email") || "";
const organization = email.includes("@") const organization = email.includes("@")
@@ -74,7 +72,6 @@ export const handleSaveTemplate = async ({
: ""; : "";
if (!organization) { if (!organization) {
console.error("Organization could not be determined from email.");
return; return;
} }
let saveTemplate = { let saveTemplate = {
@@ -89,13 +86,13 @@ export const handleSaveTemplate = async ({
try { try {
addTemplate(newTemplate); addTemplate(newTemplate);
// const response = await saveTemplateApi(organization, newTemplate); // const response = await saveTemplateApi(organization, newTemplate);
// console.log("Save API Response:", response); //
// Add template only if API call succeeds // Add template only if API call succeeds
} catch (apiError) { } catch (apiError) {
// console.error("Error saving template to API:", apiError); //
} }
} catch (error) { } catch (error) {
// console.error("Error in handleSaveTemplate:", error); //
} }
}; };

View File

@@ -81,9 +81,12 @@
.returnOfInvestment { .returnOfInvestment {
gap: 10px; gap: 10px;
min-width: 150px;
.charts { .charts {
width: 100%;
height: 200px; height: 200px;
min-width: 150px;
} }
.returns-wrapper { .returns-wrapper {
@@ -126,6 +129,12 @@
gap: 6px; gap: 6px;
border-radius: 5.2px; border-radius: 5.2px;
width: 100%;
height: 150px;
display: flex;
justify-content: center;
align-items: center;
.header { .header {
font-size: $small; font-size: $small;
text-align: start; text-align: start;

View File

@@ -75,6 +75,7 @@
max-width: 80%; max-width: 80%;
overflow: auto; overflow: auto;
max-width: calc(100% - 500px); max-width: calc(100% - 500px);
min-width: 150px;
z-index: 3; z-index: 3;
transform: translate(-50%, -10%); transform: translate(-50%, -10%);
@@ -363,6 +364,7 @@
} }
.panel.hidePanel { .panel.hidePanel {
pointer-events: none;
opacity: 0.1; opacity: 0.1;
} }
} }