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

This commit is contained in:
Jerald-Golden-B 2025-05-03 10:11:20 +05:30
commit 8b0daa3305
35 changed files with 2208 additions and 1423 deletions

View File

@ -3,23 +3,20 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Dashboard from "./pages/Dashboard"; import Dashboard from "./pages/Dashboard";
import Project from "./pages/Project"; import Project from "./pages/Project";
import UserAuth from "./pages/UserAuth"; import UserAuth from "./pages/UserAuth";
import ToastProvider from "./components/templates/ToastProvider"; import "./styles/main.scss";
import "./styles/main.scss" import { LoggerProvider } from "./components/ui/log/LoggerContext";
const App: React.FC = () => { const App: React.FC = () => {
return ( return (
<ToastProvider> <LoggerProvider>
<Router> <Router>
<Routes> <Routes>
<Route <Route path="/" element={<UserAuth />} />
path="/"
element={<UserAuth />}
/>
<Route path="/dashboard" element={<Dashboard />} /> <Route path="/dashboard" element={<Dashboard />} />
<Route path="/project" element={<Project />} /> <Route path="/project" element={<Project />} />
</Routes> </Routes>
</Router> </Router>
</ToastProvider> </LoggerProvider>
); );
}; };

View File

@ -0,0 +1,67 @@
import React from "react";
import { HelpIcon } from "../icons/DashboardIcon";
import {
LogInfoIcon,
ErrorIcon,
WarningIcon,
} from "../icons/ExportCommonIcons"; // Adjust path as needed
import { useLogger } from "../ui/log/LoggerContext";
const getLogIcon = (type: string) => {
switch (type) {
case "info":
return <LogInfoIcon />;
case "error":
return <ErrorIcon />;
case "warning":
return <WarningIcon />;
case "log":
default:
return <LogInfoIcon />;
}
};
const Footer: React.FC = () => {
const { logs, setIsLogListVisible } = useLogger();
const lastLog = logs.length > 0 ? logs[logs.length - 1] : null;
return (
<div className="footer-wrapper">
<div className="selection-wrapper">
<div className="selector-wrapper">
<div className="icon"></div>
<div className="selector">Selection</div>
</div>
<div className="selector-wrapper">
<div className="icon"></div>
<div className="selector">Rotate/Zoom</div>
</div>
<div className="selector-wrapper">
<div className="icon"></div>
<div className="selector">Pan/Context Menu</div>
</div>
</div>
<div className="logs-wrapper">
<div className="logs-detail" onClick={() => setIsLogListVisible(true)}>
{lastLog ? (
<>
<span className="log-icon">{getLogIcon(lastLog.type)}</span>
<span className="log-message">{lastLog.message}</span>
</>
) : (
"No logs yet."
)}
</div>
<div className="version">
V 0.01
<div className="icon">
<HelpIcon />
</div>
</div>
</div>
</div>
);
};
export default Footer;

View File

@ -458,7 +458,7 @@ export function InfoIcon() {
> >
<path <path
d="M5.46289 7.75441C5.46289 7.68342 5.47691 7.6131 5.50422 7.54758C5.53158 7.48201 5.57156 7.42254 5.62201 7.37257C5.67246 7.3226 5.73231 7.2831 5.79807 7.25636C5.86388 7.22963 5.93425 7.21619 6.00529 7.21681C6.11003 7.21873 6.21188 7.25142 6.29814 7.31089C6.38435 7.37036 6.45121 7.45398 6.49019 7.55118C6.52921 7.64843 6.53862 7.75499 6.5174 7.85757C6.49614 7.96014 6.44511 8.05417 6.37071 8.12795C6.29631 8.20168 6.20185 8.25184 6.09908 8.27219C5.99631 8.29254 5.8898 8.28212 5.79294 8.24224C5.69603 8.2024 5.61308 8.13486 5.55438 8.04808C5.49567 7.96134 5.46385 7.8592 5.46289 7.75441ZM5.63564 6.44401L5.56844 3.93842C5.56206 3.87819 5.56844 3.81729 5.58716 3.75968C5.60583 3.70207 5.63641 3.64902 5.67692 3.604C5.71743 3.55897 5.76697 3.52297 5.82227 3.49832C5.87761 3.47368 5.93751 3.46094 5.99804 3.46094C6.05862 3.46094 6.11852 3.47368 6.17387 3.49832C6.22916 3.52297 6.2787 3.55897 6.31921 3.604C6.35972 3.64902 6.3903 3.70207 6.40897 3.75968C6.42769 3.81729 6.43407 3.87819 6.42769 3.93842L6.36529 6.44401C6.36529 6.54073 6.32689 6.63356 6.25844 6.70196C6.19004 6.77036 6.09721 6.80881 6.00049 6.80881C5.90372 6.80881 5.81094 6.77036 5.74254 6.70196C5.67414 6.63356 5.63564 6.54073 5.63564 6.44401Z" d="M5.46289 7.75441C5.46289 7.68342 5.47691 7.6131 5.50422 7.54758C5.53158 7.48201 5.57156 7.42254 5.62201 7.37257C5.67246 7.3226 5.73231 7.2831 5.79807 7.25636C5.86388 7.22963 5.93425 7.21619 6.00529 7.21681C6.11003 7.21873 6.21188 7.25142 6.29814 7.31089C6.38435 7.37036 6.45121 7.45398 6.49019 7.55118C6.52921 7.64843 6.53862 7.75499 6.5174 7.85757C6.49614 7.96014 6.44511 8.05417 6.37071 8.12795C6.29631 8.20168 6.20185 8.25184 6.09908 8.27219C5.99631 8.29254 5.8898 8.28212 5.79294 8.24224C5.69603 8.2024 5.61308 8.13486 5.55438 8.04808C5.49567 7.96134 5.46385 7.8592 5.46289 7.75441ZM5.63564 6.44401L5.56844 3.93842C5.56206 3.87819 5.56844 3.81729 5.58716 3.75968C5.60583 3.70207 5.63641 3.64902 5.67692 3.604C5.71743 3.55897 5.76697 3.52297 5.82227 3.49832C5.87761 3.47368 5.93751 3.46094 5.99804 3.46094C6.05862 3.46094 6.11852 3.47368 6.17387 3.49832C6.22916 3.52297 6.2787 3.55897 6.31921 3.604C6.35972 3.64902 6.3903 3.70207 6.40897 3.75968C6.42769 3.81729 6.43407 3.87819 6.42769 3.93842L6.36529 6.44401C6.36529 6.54073 6.32689 6.63356 6.25844 6.70196C6.19004 6.77036 6.09721 6.80881 6.00049 6.80881C5.90372 6.80881 5.81094 6.77036 5.74254 6.70196C5.67414 6.63356 5.63564 6.54073 5.63564 6.44401Z"
fill="var(--icon-default-color)" fill="var(--text-color)"
/> />
<path <path
d="M6.00006 10.3175C8.45219 10.3175 10.4401 8.32963 10.4401 5.8775C10.4401 3.42536 8.45219 1.4375 6.00006 1.4375C3.54792 1.4375 1.56006 3.42536 1.56006 5.8775C1.56006 8.32963 3.54792 10.3175 6.00006 10.3175Z" d="M6.00006 10.3175C8.45219 10.3175 10.4401 8.32963 10.4401 5.8775C10.4401 3.42536 8.45219 1.4375 6.00006 1.4375C3.54792 1.4375 1.56006 3.42536 1.56006 5.8775C1.56006 8.32963 3.54792 10.3175 6.00006 10.3175Z"
@ -471,7 +471,7 @@ export function InfoIcon() {
); );
} }
export function AI_Icon() { export function AIIcon() {
return ( return (
<svg <svg
width="20" width="20"
@ -814,3 +814,136 @@ export const SpeedIcon = () => {
</svg> </svg>
); );
}; };
export const LogListIcon = () => {
return (
<svg
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 18.25H5.5M18.5 21.7272V19.3568H17.3829C16.8245 19.3568 16.375 18.9072 16.375 18.3489V18.08C16.375 17.5216 16.8246 17.0721 17.3829 17.0721H18.5V13.7318H17.2579C16.6995 13.7318 16.25 13.2822 16.25 12.7239V12.4551C16.25 11.8967 16.6996 11.4472 17.2579 11.4472H18.5V7.98185H17.2579C16.6995 7.98185 16.25 7.5323 16.25 6.97395V6.70515C16.25 6.14675 16.6996 5.69725 17.2579 5.69725H18.5V3.27065M14.5 12.75H5.5M12 7.24995H5.5M20.25 3.25H3.75C3.1977 3.25 2.75 3.6977 2.75 4.25V20.75C2.75 21.3023 3.1977 21.75 3.75 21.75H20.25C20.8023 21.75 21.25 21.3023 21.25 20.75V4.25C21.25 3.6977 20.8023 3.25 20.25 3.25Z"
stroke="#2B3344"
strokeWidth="1.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export const LogTickIcon = () => {
return (
<svg
width="10"
height="10"
viewBox="0 0 10 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_3962_4223)">
<path
d="M5.00065 0.835938C2.70482 0.835938 0.833984 2.70677 0.833984 5.0026C0.833984 7.29844 2.70482 9.16927 5.00065 9.16927C7.29648 9.16927 9.16732 7.29844 9.16732 5.0026C9.16732 2.70677 7.29648 0.835938 5.00065 0.835938ZM6.99232 4.04427L4.62982 6.40677C4.57148 6.4651 4.49232 6.49844 4.40898 6.49844C4.32565 6.49844 4.24648 6.4651 4.18815 6.40677L3.00898 5.2276C2.88815 5.10677 2.88815 4.90677 3.00898 4.78594C3.12982 4.6651 3.32982 4.6651 3.45065 4.78594L4.40898 5.74427L6.55065 3.6026C6.67148 3.48177 6.87148 3.48177 6.99232 3.6026C7.11315 3.72344 7.11315 3.91927 6.99232 4.04427Z"
fill="#49B841"
/>
</g>
<defs>
<clipPath id="clip0_3962_4223">
<rect width="10" height="10" fill="white" />
</clipPath>
</defs>
</svg>
);
};
export const LogInfoIcon = () => {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.75 2.64438V7.41438C0.75 7.92094 0.951232 8.40675 1.30943 8.76495C1.66762 9.12314 2.15344 9.32438 2.66 9.32438H3.615V10.7544L6.5 9.32438H9.365C9.87156 9.32438 10.3574 9.12314 10.7156 8.76495C11.0738 8.40675 11.275 7.92094 11.275 7.41438V2.64438C11.275 2.13781 11.0738 1.652 10.7156 1.2938C10.3574 0.935607 9.87156 0.734375 9.365 0.734375H2.66C2.15344 0.734375 1.66762 0.935607 1.30943 1.2938C0.951232 1.652 0.75 2.13781 0.75 2.64438Z"
fill="#8E8E93"
/>
<path
d="M5.04492 6.94531H6.95492"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
<path
d="M5.04492 4.07812H5.99992V6.94313"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
<path
d="M5.52539 2.65625H6.47539"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
</svg>
);
};
export const WarningIcon = () => {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.75 2.64438V7.41438C0.75 7.92094 0.951232 8.40675 1.30943 8.76495C1.66762 9.12314 2.15344 9.32438 2.66 9.32438H3.615V10.7544L6.5 9.32438H9.365C9.87156 9.32438 10.3574 9.12314 10.7156 8.76495C11.0738 8.40675 11.275 7.92094 11.275 7.41438V2.64438C11.275 2.13781 11.0738 1.652 10.7156 1.2938C10.3574 0.935607 9.87156 0.734375 9.365 0.734375H2.66C2.15344 0.734375 1.66762 0.935607 1.30943 1.2938C0.951232 1.652 0.75 2.13781 0.75 2.64438Z"
fill="#8E8E93"
/>
<path
d="M5.04492 6.94531H6.95492"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
<path
d="M5.04492 4.07812H5.99992V6.94313"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
<path
d="M5.52539 2.65625H6.47539"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
</svg>
);
};
export const ErrorIcon = () => {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11 6C11 8.7614 8.7614 11 6 11C3.23857 11 1 8.7614 1 6C1 3.23857 3.23857 1 6 1C8.7614 1 11 3.23857 11 6ZM4.48482 4.48483C4.63126 4.33838 4.8687 4.33838 5.01515 4.48483L6 5.46965L6.9848 4.48484C7.13125 4.33839 7.3687 4.33839 7.51515 4.48484C7.6616 4.63128 7.6616 4.86872 7.51515 5.01515L6.5303 6L7.51515 6.9848C7.6616 7.13125 7.6616 7.3687 7.51515 7.51515C7.3687 7.6616 7.13125 7.6616 6.9848 7.51515L6 6.53035L5.01515 7.51515C4.86871 7.6616 4.63127 7.6616 4.48483 7.51515C4.33838 7.3687 4.33838 7.13125 4.48483 6.98485L5.46965 6L4.48482 5.01515C4.33837 4.86871 4.33837 4.63127 4.48482 4.48483Z"
fill="#ED5342"
/>
</svg>
);
};

View File

@ -125,21 +125,21 @@ export function ResetIcon() {
> >
<path <path
d="M2.54182 4.09637C3.33333 2.73422 4.80832 1.81836 6.49721 1.81836C9.02194 1.81836 11.0686 3.86506 11.0686 6.38979C11.0686 8.91452 9.02194 10.9612 6.49721 10.9612C3.97248 10.9612 1.92578 8.91452 1.92578 6.38979" d="M2.54182 4.09637C3.33333 2.73422 4.80832 1.81836 6.49721 1.81836C9.02194 1.81836 11.0686 3.86506 11.0686 6.38979C11.0686 8.91452 9.02194 10.9612 6.49721 10.9612C3.97248 10.9612 1.92578 8.91452 1.92578 6.38979"
stroke="var(--icon-default-color-active)" stroke="var(--text-color)"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
<path <path
d="M7.64118 6.38895C7.64118 7.02013 7.12951 7.53181 6.49833 7.53181C5.86714 7.53181 5.35547 7.02013 5.35547 6.38895C5.35547 5.75777 5.86714 5.24609 6.49833 5.24609C7.12951 5.24609 7.64118 5.75777 7.64118 6.38895Z" d="M7.64118 6.38895C7.64118 7.02013 7.12951 7.53181 6.49833 7.53181C5.86714 7.53181 5.35547 7.02013 5.35547 6.38895C5.35547 5.75777 5.86714 5.24609 6.49833 5.24609C7.12951 5.24609 7.64118 5.75777 7.64118 6.38895Z"
fill="var(--icon-default-color-active)" fill="var(--text-color)"
stroke="var(--icon-default-color-active)" stroke="var(--text-color)"
strokeWidth="0.571429" strokeWidth="0.571429"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
<path <path
d="M4.78125 4.10407H2.49554V1.81836" d="M4.78125 4.10407H2.49554V1.81836"
stroke="var(--icon-default-color-active)" stroke="var(--text-color)"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
@ -158,7 +158,7 @@ export function PlayStopIcon() {
> >
<path <path
d="M8 2.88867V9.88867M11 2.88867V9.88867M2 3.99171V8.78562C2 9.28847 2 9.53987 2.09943 9.65627C2.1857 9.75732 2.31512 9.81092 2.44756 9.80047C2.60019 9.78847 2.77796 9.61072 3.13352 9.25517L5.5305 6.85817C5.69485 6.69382 5.777 6.61167 5.8078 6.51692C5.83485 6.43357 5.83485 6.34377 5.8078 6.26042C5.777 6.16567 5.69485 6.08352 5.5305 5.91917L3.13352 3.52219C2.77796 3.16664 2.60019 2.98886 2.44756 2.97685C2.31512 2.96643 2.1857 3.02004 2.09943 3.12105C2 3.23747 2 3.48888 2 3.99171Z" d="M8 2.88867V9.88867M11 2.88867V9.88867M2 3.99171V8.78562C2 9.28847 2 9.53987 2.09943 9.65627C2.1857 9.75732 2.31512 9.81092 2.44756 9.80047C2.60019 9.78847 2.77796 9.61072 3.13352 9.25517L5.5305 6.85817C5.69485 6.69382 5.777 6.61167 5.8078 6.51692C5.83485 6.43357 5.83485 6.34377 5.8078 6.26042C5.777 6.16567 5.69485 6.08352 5.5305 5.91917L3.13352 3.52219C2.77796 3.16664 2.60019 2.98886 2.44756 2.97685C2.31512 2.96643 2.1857 3.02004 2.09943 3.12105C2 3.23747 2 3.48888 2 3.99171Z"
stroke="var(--icon-default-color-active)" stroke="var(--text-color)"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
@ -177,7 +177,7 @@ export function ExitIcon() {
> >
<path <path
d="M2.08203 6.34311L5.03647 3.38867M2.08203 6.34311L5.03647 9.29755M2.08203 6.34311H5.9228C7.22276 6.34334 9.34995 7.05241 10.7681 9.88867" d="M2.08203 6.34311L5.03647 3.38867M2.08203 6.34311L5.03647 9.29755M2.08203 6.34311H5.9228C7.22276 6.34334 9.34995 7.05241 10.7681 9.88867"
stroke="var(--icon-default-color-active)" stroke="var(--text-color)"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />

View File

@ -19,7 +19,7 @@ const Header: React.FC = () => {
<FileMenu /> <FileMenu />
</div> </div>
</div> </div>
<div <button
className={`toggle-sidebar-ui-button ${!toggleUI ? "active" : ""}`} className={`toggle-sidebar-ui-button ${!toggleUI ? "active" : ""}`}
onClick={() => { onClick={() => {
if (activeModule !== "market") { if (activeModule !== "market") {
@ -29,7 +29,7 @@ const Header: React.FC = () => {
}} }}
> >
<ToggleSidebarIcon /> <ToggleSidebarIcon />
</div> </button>
</div> </div>
); );
}; };

View File

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { AI_Icon } from "../../../icons/ExportCommonIcons"; import { AIIcon } from "../../../icons/ExportCommonIcons";
import RegularDropDown from "../../../ui/inputs/RegularDropDown"; import RegularDropDown from "../../../ui/inputs/RegularDropDown";
import { AnalysisPresetsType } from "../../../../types/analysis"; import { AnalysisPresetsType } from "../../../../types/analysis";
import RenderAnalysisInputs from "./RenderAnalysisInputs"; import RenderAnalysisInputs from "./RenderAnalysisInputs";
@ -13,53 +13,24 @@ const Analysis: React.FC = () => {
const AnalysisPresets: AnalysisPresetsType = { const AnalysisPresets: AnalysisPresetsType = {
"Throughput time": [ "Throughput time": [
{ type: "default", inputs: { label: "Height", activeOption: "mm" } }, { type: "default", inputs: { label: "Cycle time", activeOption: "s" } },
{ type: "default", inputs: { label: "Width", activeOption: "mm" } }, { type: "default", inputs: { label: "machines / lines", activeOption: "item" } },
{ type: "default", inputs: { label: "Length", activeOption: "mtr" } }, { type: "default", inputs: { label: "Machine uptime", activeOption: "%" } },
{ type: "default", inputs: { label: "Thickness", activeOption: "mm" } },
{
type: "default",
inputs: { label: "Raw Material", activeOption: "mm" },
},
{
type: "range",
inputs: { label: "Material flow", activeOption: "m/min" },
},
], ],
"Production capacity": [ "Production capacity": [
{ type: "default", inputs: { label: "Height", activeOption: "mm" } }, { type: "default", inputs: { label: "Shift length", activeOption: "hr" } },
{ type: "default", inputs: { label: "Width", activeOption: "mm" } }, { type: "default", inputs: { label: "Shifts / day", activeOption: "unit" } },
{ type: "default", inputs: { label: "Length", activeOption: "mtr" } }, { type: "default", inputs: { label: "Working days / year", activeOption: "days" } },
{ type: "default", inputs: { label: "Thickness", activeOption: "mm" } }, { type: "default", inputs: { label: "Yield rate", activeOption: "%" } },
{
type: "default",
inputs: { label: "Raw Material", activeOption: "mm" },
},
{
type: "range",
inputs: { label: "Material flow", activeOption: "m/min" },
},
{
type: "range",
inputs: { label: "Shift 1", activeOption: "hr(s)" },
},
{
type: "range",
inputs: { label: "Shift 2", activeOption: "hr(s)" },
},
{
type: "range",
inputs: { label: "Over time", activeOption: "hr(s)" },
},
], ],
ROI: [ ROI: [
{ {
type: "default", type: "default",
inputs: { label: "Equipment Cost", activeOption: "INR" }, inputs: { label: "Selling price", activeOption: "INR" },
}, },
{ {
type: "default", type: "default",
inputs: { label: "Employee Salary", activeOption: "INR" }, inputs: { label: "Material cost", activeOption: "INR" },
}, },
{ {
type: "default", type: "default",
@ -67,7 +38,7 @@ const Analysis: React.FC = () => {
}, },
{ {
type: "default", type: "default",
inputs: { label: "Cost per unit", activeOption: "INR" }, inputs: { label: "Maintenance cost", activeOption: "INR" },
}, },
{ {
type: "default", type: "default",
@ -75,20 +46,19 @@ const Analysis: React.FC = () => {
}, },
{ {
type: "default", type: "default",
inputs: { label: "Upkeep Cost", activeOption: "INR" }, inputs: { label: "Fixed costs", activeOption: "INR" },
}, },
{ {
type: "default", type: "default",
inputs: { label: "Working Hours", activeOption: "Hrs" }, inputs: { label: "Salvage value", activeOption: "Hrs" },
}, },
{ {
type: "default", type: "default",
inputs: { label: "Power Usage", activeOption: "KWH" }, inputs: { label: "Production period", activeOption: "yrs" },
}, },
{ type: "default", inputs: { label: "KWH", activeOption: "Mos" } },
{ {
type: "default", type: "default",
inputs: { label: "Man Power", activeOption: "Person" }, inputs: { label: "Tax rate", activeOption: "%" },
}, },
], ],
}; };
@ -98,7 +68,7 @@ const Analysis: React.FC = () => {
<div className="analysis-main-container"> <div className="analysis-main-container">
<div className="header">Object</div> <div className="header">Object</div>
<div className="generate-report-button"> <div className="generate-report-button">
<AI_Icon /> Generate Report <AIIcon /> Generate Report
</div> </div>
<div className="analysis-content-container section"> <div className="analysis-content-container section">
<div className="dropdown-header-container"> <div className="dropdown-header-container">
@ -121,7 +91,7 @@ const Analysis: React.FC = () => {
/> />
<div className="buttons-container"> <div className="buttons-container">
<input type="button" value={"Clear"} className="cancel" /> <input type="button" value={"Clear"} className="cancel" />
<input type="button" value={"Calculate"} className="submit" /> <input type="button" value={"Update"} className="submit" />
</div> </div>
<div className="create-custom-analysis-container"> <div className="create-custom-analysis-container">
<div className="custom-analysis-header">Create Custom Analysis</div> <div className="custom-analysis-header">Create Custom Analysis</div>

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import InputRange from "../../../ui/inputs/InputRange"; import InputRange from "../../../ui/inputs/InputRange";
import InputToggle from "../../../ui/inputs/InputToggle"; import InputToggle from "../../../ui/inputs/InputToggle";
import { AI_Icon } from "../../../icons/ExportCommonIcons"; import { AIIcon } from "../../../icons/ExportCommonIcons";
import LabeledButton from "../../../ui/inputs/LabledButton"; import LabeledButton from "../../../ui/inputs/LabledButton";
import { import {
useAzimuth, useAzimuth,
@ -225,7 +225,7 @@ const GlobalProperties: React.FC = () => {
<section> <section>
<div className="header">Environment</div> <div className="header">Environment</div>
<div className="optimize-button" onClick={optimizeScene}> <div className="optimize-button" onClick={optimizeScene}>
<AI_Icon /> <AIIcon />
Optimize Optimize
</div> </div>

View File

@ -18,7 +18,9 @@ const EventProperties: React.FC = () => {
const { selectedEventData } = useSelectedEventData(); const { selectedEventData } = useSelectedEventData();
const { getEventByModelUuid } = useProductStore(); const { getEventByModelUuid } = useProductStore();
const { selectedProduct } = useSelectedProduct(); const { selectedProduct } = useSelectedProduct();
const [currentEventData, setCurrentEventData] = useState<EventsSchema | null>(null); const [currentEventData, setCurrentEventData] = useState<EventsSchema | null>(
null
);
const [assetType, setAssetType] = useState<string | null>(null); const [assetType, setAssetType] = useState<string | null>(null);
const { products, addEvent } = useProductStore(); const { products, addEvent } = useProductStore();
const { selectedEventSphere } = useSelectedEventSphere(); const { selectedEventSphere } = useSelectedEventSphere();
@ -89,30 +91,32 @@ const EventProperties: React.FC = () => {
<p> <p>
<strong>Here are some products you can add it to:</strong> <strong>Here are some products you can add it to:</strong>
</p> </p>
<ul> <div className="product-item">
{products.map((product) => ( {products.map((product) => (
<li key={product.productId}>
<button <button
key={product.productId}
onClick={() => { onClick={() => {
if (selectedEventData) { if (selectedEventData) {
handleAddEventToProduct({ handleAddEventToProduct({
event: useEventsStore.getState().getEventByModelUuid(selectedEventData?.data.modelUuid), event: useEventsStore
.getState()
.getEventByModelUuid(
selectedEventData?.data.modelUuid
),
addEvent, addEvent,
selectedProduct, selectedProduct,
}) });
} }
}} }}
> >
<AddIcon /> <AddIcon />
{product.productName} {product.productName}
</button> </button>
</li>
))} ))}
</ul>
</div> </div>
</div> </div>
) </div>
} )}
{!selectedEventSphere && ( {!selectedEventSphere && (
<div className="no-event-selected"> <div className="no-event-selected">
<p> <p>

View File

@ -36,7 +36,6 @@ import {
import useToggleStore from "../../store/useUIToggleStore"; import useToggleStore from "../../store/useUIToggleStore";
import { import {
use3DWidget, use3DWidget,
useDroppedObjectsStore,
useFloatingWidget, useFloatingWidget,
} from "../../store/visualization/useDroppedObjectsStore"; } from "../../store/visualization/useDroppedObjectsStore";
@ -57,20 +56,18 @@ const Tools: React.FC = () => {
const { widgets3D } = use3DWidget(); const { widgets3D } = use3DWidget();
const zones = useDroppedObjectsStore((state) => state.zones);
// wall options // wall options
const { toggleView, setToggleView } = useToggleView(); const { toggleView, setToggleView } = useToggleView();
const { setDeleteTool } = useDeleteTool(); const { setDeleteTool } = useDeleteTool();
const { setAddAction } = useAddAction(); const { setAddAction } = useAddAction();
const { setSelectedWallItem } = useSelectedWallItem(); const { setSelectedWallItem } = useSelectedWallItem();
const { transformMode, setTransformMode } = useTransformMode(); const { setTransformMode } = useTransformMode();
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine(); const { setDeletePointOrLine } = useDeletePointOrLine();
const { movePoint, setMovePoint } = useMovePoint(); const { setMovePoint } = useMovePoint();
const { toolMode, setToolMode } = useToolMode(); const { setToolMode } = useToolMode();
const { activeTool, setActiveTool } = useActiveTool(); const { activeTool, setActiveTool } = useActiveTool();
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate(); const { setRefTextUpdate } = useRefTextUpdate();
// Reset activeTool whenever activeModule changes // Reset activeTool whenever activeModule changes
useEffect(() => { useEffect(() => {
@ -209,7 +206,6 @@ const Tools: React.FC = () => {
return ( return (
<> <>
{!isPlaying ? ( {!isPlaying ? (
<>
<div className="tools-container"> <div className="tools-container">
<div className="drop-down-icons"> <div className="drop-down-icons">
<div className="activeDropicon"> <div className="activeDropicon">
@ -464,13 +460,12 @@ const Tools: React.FC = () => {
</> </>
)} )}
</div> </div>
</>
) : ( ) : (
<> <>
{activeModule !== "simulation" && ( {activeModule !== "simulation" && (
<div className="exitPlay" onClick={() => setIsPlaying(false)}> <button className="exitPlay" onClick={() => setIsPlaying(false)}>
X X
</div> </button>
)} )}
</> </>
)} )}

View File

@ -1,6 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { ROISummaryIcon } from "../../icons/analysis"; import { ROISummaryIcon } from "../../icons/analysis";
import SemiCircleProgress from "./SemiCircleProgress"; import SemiCircleProgress from "./SemiCircleProgress";
import { ArrowIcon } from "../../icons/ExportCommonIcons";
const ROISummary = ({ const ROISummary = ({
roiSummaryData = { roiSummaryData = {
@ -121,7 +122,7 @@ const ROISummary = ({
</div> </div>
<span className={`expand-icon ${isTableOpen ? "open" : ""}`}> <span className={`expand-icon ${isTableOpen ? "open" : ""}`}>
{isTableOpen ? "⌵" : "⌵"} <ArrowIcon />
</span> </span>
</div> </div>
<div <div

View File

@ -0,0 +1,95 @@
// LogList.tsx
import React, { useState } from "react";
import {
LogListIcon,
LogInfoIcon,
WarningIcon,
ErrorIcon,
CloseIcon,
} from "../../icons/ExportCommonIcons"; // Adjust path as needed
import { useLogger } from "./LoggerContext";
const LogList: React.FC = () => {
const { logs, clear, setIsLogListVisible } = useLogger();
const [selectedTab, setSelectedTab] = useState<
"all" | "info" | "warning" | "error" | "log"
>("all");
const getLogIcon = (type: string) => {
switch (type) {
case "info":
return <LogInfoIcon />;
case "error":
return <ErrorIcon />;
case "warning":
return <WarningIcon />;
case "log":
default:
return <LogInfoIcon />;
}
};
const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
const filteredLogs =
selectedTab === "all"
? [...logs].reverse()
: [...logs].filter((log) => log.type === selectedTab).reverse();
return (
// eslint-disable-next-line
<div
className="log-list-container"
onClick={() => setIsLogListVisible(false)}
>
<div
className="log-list-wrapper"
onClick={(e) => {
e.stopPropagation();
}}
>
<div className="log-header">
<div className="log-header-wrapper">
<div className="icon">
<LogListIcon />
</div>
<div className="head">Log List</div>
</div>
<button className="close" onClick={() => setIsLogListVisible(false)}>
<CloseIcon />
</button>
</div>
{/* Tabs */}
<div className="log-nav-wrapper">
{["all", "info", "warning", "error"].map((type) => (
<div
key={type}
className={`log-nav ${selectedTab === type ? "active" : ""}`}
onClick={() => setSelectedTab(type as any)}
>
{`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
</div>
))}
</div>
{/* Log Entries */}
<div className="log-entry-wrapper">
{filteredLogs.map((log) => (
<div key={log.id} className={`log-entry ${log.type}`}>
<div className="log-icon">{getLogIcon(log.type)}</div>
<div className="log-entry-message-container">
<div className="log-entry-message">{log.message}</div>
<div className="message-time">
{formatTimestamp(log.timestamp)}
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
};
export default LogList;

View File

@ -0,0 +1,77 @@
import React, { createContext, useContext, useState, useCallback } from "react";
export type LogType = "log" | "info" | "warning" | "error";
export interface LogEntry {
id: string;
type: LogType;
message: string;
timestamp: Date;
}
interface LoggerContextValue {
logs: LogEntry[];
setLogs: React.Dispatch<React.SetStateAction<LogEntry[]>>;
isLogListVisible: boolean;
setIsLogListVisible: React.Dispatch<React.SetStateAction<boolean>>;
log: (message: string) => void;
info: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
clear: () => void;
}
const LoggerContext = createContext<LoggerContextValue | undefined>(undefined);
export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [logs, setLogs] = useState<LogEntry[]>([]);
const [isLogListVisible, setIsLogListVisible] = useState<boolean>(false);
const generateId = useCallback(
() => Math.random().toString(36).substring(2, 9),
[]
);
const addLog = useCallback(
(type: LogType, message: string) => {
const newLog: LogEntry = {
id: generateId(),
type,
message,
timestamp: new Date(),
};
setLogs((prevLogs) => [...prevLogs, newLog]);
},
[generateId]
);
const loggerMethods: LoggerContextValue = {
logs,
setLogs,
isLogListVisible,
setIsLogListVisible,
log: (message: string) => addLog("log", message),
info: (message: string) => addLog("info", message),
warn: (message: string) => addLog("warning", message),
error: (message: string) => addLog("error", message),
clear: () => {
setLogs([]);
},
};
return (
<LoggerContext.Provider value={loggerMethods}>
{children}
</LoggerContext.Provider>
);
};
export const useLogger = () => {
const context = useContext(LoggerContext);
if (!context) {
throw new Error("useLogger must be used within a LoggerProvider");
}
return context;
};

View File

@ -0,0 +1,71 @@
type LogType = 'log' | 'info' | 'warning' | 'error';
interface LogEntry {
type: LogType;
message: string;
timestamp: Date;
context?: string;
}
class Logger {
private static instance: Logger;
private logs: LogEntry[] = [];
private subscribers: Array<(log: LogEntry) => void> = [];
private constructor() {}
public static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
private notifySubscribers(log: LogEntry) {
this.subscribers.forEach(callback => callback(log));
}
private addLog(type: LogType, message: string, context?: string) {
const log: LogEntry = { type, message, timestamp: new Date(), context };
this.logs.push(log);
this.notifySubscribers(log);
if (process.env.NODE_ENV === 'development') {
const logMessage = context ? `[${context}] ${message}` : message;
console[type === 'warning' ? 'warn' : type](logMessage);
}
}
public log(message: string, context?: string) {
this.addLog('log', message, context);
}
public info(message: string, context?: string) {
this.addLog('info', message, context);
}
public warning(message: string, context?: string) {
this.addLog('warning', message, context);
}
public error(message: string, context?: string) {
this.addLog('error', message, context);
}
public getLogs(): LogEntry[] {
return [...this.logs];
}
public clear() {
this.logs = [];
}
public subscribe(callback: (log: LogEntry) => void) {
this.subscribers.push(callback);
return () => {
this.subscribers = this.subscribers.filter(sub => sub !== callback);
};
}
}
export const logger = Logger.getInstance();

View File

@ -12,25 +12,31 @@ import {
EndIcon, EndIcon,
ExpandIcon, ExpandIcon,
HourlySimulationIcon, HourlySimulationIcon,
InfoIcon,
MonthlyROI, MonthlyROI,
SpeedIcon, SpeedIcon,
StartIcon, StartIcon,
} from "../../icons/ExportCommonIcons"; } from "../../icons/ExportCommonIcons";
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor"; import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
import { useSubModuleStore } from "../../../store/useModuleStore";
import ProductionCapacity from "../analysis/ProductionCapacity";
import ThroughputSummary from "../analysis/ThroughputSummary";
import ROISummary from "../analysis/ROISummary";
const SimulationPlayer: React.FC = () => { const SimulationPlayer: React.FC = () => {
const MAX_SPEED = 8; // Maximum speed const MAX_SPEED = 8; // Maximum speed
const isDragging = useRef(false);
const sliderRef = useRef<HTMLDivElement>(null);
const [expand, setExpand] = useState(true); const [expand, setExpand] = useState(true);
const [playSimulation, setPlaySimulation] = useState(false);
const { speed, setSpeed } = useAnimationPlaySpeed(); const { speed, setSpeed } = useAnimationPlaySpeed();
const [playSimulation, setPlaySimulation] = useState(false);
const { setIsPlaying } = usePlayButtonStore(); const { setIsPlaying } = usePlayButtonStore();
const sliderRef = useRef<HTMLDivElement>(null);
const isDragging = useRef(false);
const { setActiveTool } = useActiveTool(); const { setActiveTool } = useActiveTool();
const { isPaused, setIsPaused } = usePauseButtonStore(); const { isPaused, setIsPaused } = usePauseButtonStore();
const { isReset, setReset } = useResetButtonStore(); const { isReset, setReset } = useResetButtonStore();
const { subModule } = useSubModuleStore();
// Button functions // Button functions
const handleReset = () => { const handleReset = () => {
@ -114,7 +120,7 @@ const SimulationPlayer: React.FC = () => {
}; };
// Store colors for each process item // Store colors for each process item
const [processColors, setProcessColors] = useState<string[]>([]); const [_, setProcessColors] = useState<string[]>([]);
// Generate colors on mount or when process changes // Generate colors on mount or when process changes
useEffect(() => { useEffect(() => {
@ -159,9 +165,11 @@ const SimulationPlayer: React.FC = () => {
}; };
return ( return (
<>
<div className="simulation-player-wrapper"> <div className="simulation-player-wrapper">
<div className={`simulation-player-container ${expand ? "open" : ""}`}> <div className={`simulation-player-container ${expand ? "open" : ""}`}>
<div className="controls-container"> <div className="controls-container">
{subModule === "analysis" && (
<div className="production-details"> <div className="production-details">
{/* hourlySimulation */} {/* hourlySimulation */}
<div className="hourly-wrapper production-wrapper"> <div className="hourly-wrapper production-wrapper">
@ -202,10 +210,22 @@ const SimulationPlayer: React.FC = () => {
<div className="label">Monthly ROI</div> <div className="label">Monthly ROI</div>
</div> </div>
<div className="progress-wrapper"> <div className="progress-wrapper">
<div className="progress" style={{ width: monthlyROI }}></div> <div
className="progress"
style={{ width: monthlyROI }}
></div>
</div>{" "} </div>{" "}
</div> </div>
</div> </div>
)}
{subModule === "simulations" && (
<div className="header">
<InfoIcon />
{playSimulation
? "Paused - system idle."
: "Running simulation..."}
</div>
)}
<div className="controls-wrapper"> <div className="controls-wrapper">
<button <button
className="simulation-button-container" className="simulation-button-container"
@ -234,12 +254,14 @@ const SimulationPlayer: React.FC = () => {
<ExitIcon /> <ExitIcon />
Exit Exit
</button> </button>
{subModule === "analysis" && (
<button <button
className="expand-icon-container" className="expand-icon-container"
onClick={() => setExpand(!expand)} onClick={() => setExpand(!expand)}
> >
<ExpandIcon isActive={!expand} /> <ExpandIcon isActive={!expand} />
</button> </button>
)}
</div> </div>
</div> </div>
<div className="progresser-wrapper"> <div className="progresser-wrapper">
@ -330,6 +352,7 @@ const SimulationPlayer: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
{subModule === "analysis" && (
<div className="processDisplayer"> <div className="processDisplayer">
<div className="start-displayer timmer">00:00</div> <div className="start-displayer timmer">00:00</div>
<div className="end-displayer timmer">24:00</div> <div className="end-displayer timmer">24:00</div>
@ -361,8 +384,17 @@ const SimulationPlayer: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
)}
</div> </div>
</div> </div>
<div className="analysis">
<div className="analysis-wrapper">
<ProductionCapacity />
<ThroughputSummary />
</div>
<ROISummary />
</div>
</>
); );
}; };

View File

@ -142,109 +142,109 @@ const RealTimeVisulization: React.FC = () => {
}); });
}, [selectedZone]); }, [selectedZone]);
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => { // const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault(); // event.preventDefault();
try { // try {
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 data = event.dataTransfer.getData("text/plain"); // const data = event.dataTransfer.getData("text/plain");
if (widgetSubOption === "3D") return; // if (widgetSubOption === "3D") return;
if (!data || selectedZone.zoneName === "") return; // if (!data || selectedZone.zoneName === "") return;
const droppedData = JSON.parse(data); // const droppedData = JSON.parse(data);
const canvasElement = document.getElementById("real-time-vis-canvas"); // const canvasElement = document.getElementById("real-time-vis-canvas");
if (!canvasElement) throw new Error("Canvas element not found"); // if (!canvasElement) throw new Error("Canvas element not found");
const rect = canvasElement.getBoundingClientRect(); // const rect = canvasElement.getBoundingClientRect();
const relativeX = event.clientX - rect.left; // const relativeX = event.clientX - rect.left;
const relativeY = event.clientY - rect.top; // const relativeY = event.clientY - rect.top;
// Widget dimensions // // Widget dimensions
const widgetWidth = droppedData.width ?? 125; // const widgetWidth = droppedData.width ?? 125;
const widgetHeight = droppedData.height ?? 100; // const widgetHeight = droppedData.height ?? 100;
// Center the widget at cursor // // Center the widget at cursor
const centerOffsetX = widgetWidth / 2; // const centerOffsetX = widgetWidth / 2;
const centerOffsetY = widgetHeight / 2; // const centerOffsetY = widgetHeight / 2;
const adjustedX = relativeX - centerOffsetX; // const adjustedX = relativeX - centerOffsetX;
const adjustedY = relativeY - centerOffsetY; // const adjustedY = relativeY - centerOffsetY;
const finalPosition = determinePosition(rect, adjustedX, adjustedY); // const finalPosition = determinePosition(rect, adjustedX, adjustedY);
const [activeProp1, activeProp2] = getActiveProperties(finalPosition); // const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
let finalY = 0; // let finalY = 0;
let finalX = 0; // let finalX = 0;
if (activeProp1 === "top") { // if (activeProp1 === "top") {
finalY = adjustedY; // finalY = adjustedY;
} else { // } else {
finalY = rect.height - (adjustedY + widgetHeight); // finalY = rect.height - (adjustedY + widgetHeight);
} // }
if (activeProp2 === "left") { // if (activeProp2 === "left") {
finalX = adjustedX; // finalX = adjustedX;
} else { // } else {
finalX = rect.width - (adjustedX + widgetWidth); // finalX = rect.width - (adjustedX + widgetWidth);
} // }
// Clamp to boundaries // // Clamp to boundaries
finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX)); // finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY)); // finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
const boundedPosition = { // const boundedPosition = {
...finalPosition, // ...finalPosition,
[activeProp1]: finalY, // [activeProp1]: finalY,
[activeProp2]: finalX, // [activeProp2]: finalX,
[activeProp1 === "top" ? "bottom" : "top"]: "auto", // [activeProp1 === "top" ? "bottom" : "top"]: "auto",
[activeProp2 === "left" ? "right" : "left"]: "auto", // [activeProp2 === "left" ? "right" : "left"]: "auto",
}; // };
const newObject = { // const newObject = {
...droppedData, // ...droppedData,
id: generateUniqueId(), // id: generateUniqueId(),
position: boundedPosition, // position: boundedPosition,
}; // };
const existingZone = // const existingZone =
useDroppedObjectsStore.getState().zones[selectedZone.zoneName]; // useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
if (!existingZone) { // if (!existingZone) {
useDroppedObjectsStore // useDroppedObjectsStore
.getState() // .getState()
.setZone(selectedZone.zoneName, selectedZone.zoneId); // .setZone(selectedZone.zoneName, selectedZone.zoneId);
} // }
const addFloatingWidget = { // const addFloatingWidget = {
organization, // organization,
widget: newObject, // widget: newObject,
zoneId: selectedZone.zoneId, // zoneId: selectedZone.zoneId,
}; // };
if (visualizationSocket) { // if (visualizationSocket) {
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget); // visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
} // }
useDroppedObjectsStore // useDroppedObjectsStore
.getState() // .getState()
.addObject(selectedZone.zoneName, newObject); // .addObject(selectedZone.zoneName, newObject);
const droppedObjectsStore = useDroppedObjectsStore.getState(); // const droppedObjectsStore = useDroppedObjectsStore.getState();
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName]; // const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
if (currentZone && currentZone.zoneId === selectedZone.zoneId) { // if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
console.log( // console.log(
`Objects for Zone ${selectedZone.zoneId}:`, // `Objects for Zone ${selectedZone.zoneId}:`,
currentZone.objects // currentZone.objects
); // );
setFloatingWidget(currentZone.objects); // setFloatingWidget(currentZone.objects);
} else { // } else {
console.warn("Zone not found or zoneId mismatch"); // console.warn("Zone not found or zoneId mismatch");
} // }
} catch (error) { // } catch (error) {
console.error("Error in handleDrop:", error); // console.error("Error in handleDrop:", error);
} // }
}; // };
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
@ -301,16 +301,7 @@ const RealTimeVisulization: React.FC = () => {
} }
`} `}
</style> </style>
<div <div ref={containerRef} className="realTime-viz">
ref={containerRef}
id="real-time-vis-canvas"
className={`realTime-viz canvas ${isPlaying ? "playingFlase" : ""}`}
style={{
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
}}
>
<div className="realTime-viz-wrapper"> <div className="realTime-viz-wrapper">
{openConfirmationPopup && ( {openConfirmationPopup && (
<RenderOverlay> <RenderOverlay>
@ -321,20 +312,6 @@ const RealTimeVisulization: React.FC = () => {
/> />
</RenderOverlay> </RenderOverlay>
)} )}
<div
className="scene-container"
style={{
height: "100%",
width: "100%",
borderRadius:
isPlaying || activeModule !== "visualization" ? "" : "6px",
}}
role="application"
onDrop={(event) => handleDrop(event)}
onDragOver={(event) => event.preventDefault()}
>
<Scene />
</div>
{activeModule === "visualization" && selectedZone.zoneName !== "" && ( {activeModule === "visualization" && selectedZone.zoneName !== "" && (
<DroppedObjects /> <DroppedObjects />
)} )}

View File

@ -0,0 +1,122 @@
import { generateUniqueId } from "../../../functions/generateUniqueId";
import { useDroppedObjectsStore } from "../../../store/visualization/useDroppedObjectsStore";
import { determinePosition } from "./determinePosition";
import { getActiveProperties } from "./getActiveProperties";
interface HandleDropProps {
widgetSubOption: any;
visualizationSocket: any;
selectedZone: any;
setFloatingWidget: (value: any) => void;
event: React.DragEvent<HTMLDivElement>
}
export const createHandleDrop = ({
widgetSubOption,
visualizationSocket,
selectedZone,
setFloatingWidget,
event,
}: HandleDropProps) => {
event.preventDefault();
try {
const email = localStorage.getItem("email") ?? "";
const organization = email?.split("@")[1]?.split(".")[0];
const data = event.dataTransfer.getData("text/plain");
if (widgetSubOption === "3D") return;
if (!data || selectedZone.zoneName === "") return;
const droppedData = JSON.parse(data);
const canvasElement = document.getElementById("real-time-vis-canvas");
if (!canvasElement) throw new Error("Canvas element not found");
const rect = canvasElement.getBoundingClientRect();
const relativeX = event.clientX - rect.left;
const relativeY = event.clientY - rect.top;
// Widget dimensions
const widgetWidth = droppedData.width ?? 125;
const widgetHeight = droppedData.height ?? 100;
// Center the widget at cursor
const centerOffsetX = widgetWidth / 2;
const centerOffsetY = widgetHeight / 2;
const adjustedX = relativeX - centerOffsetX;
const adjustedY = relativeY - centerOffsetY;
const finalPosition = determinePosition(rect, adjustedX, adjustedY);
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
let finalY = 0;
let finalX = 0;
if (activeProp1 === "top") {
finalY = adjustedY;
} else {
finalY = rect.height - (adjustedY + widgetHeight);
}
if (activeProp2 === "left") {
finalX = adjustedX;
} else {
finalX = rect.width - (adjustedX + widgetWidth);
}
// Clamp to boundaries
finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
const boundedPosition = {
...finalPosition,
[activeProp1]: finalY,
[activeProp2]: finalX,
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
[activeProp2 === "left" ? "right" : "left"]: "auto",
};
const newObject = {
...droppedData,
id: generateUniqueId(),
position: boundedPosition,
};
const existingZone =
useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
if (!existingZone) {
useDroppedObjectsStore
.getState()
.setZone(selectedZone.zoneName, selectedZone.zoneId);
}
const addFloatingWidget = {
organization,
widget: newObject,
zoneId: selectedZone.zoneId,
};
if (visualizationSocket) {
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
}
useDroppedObjectsStore
.getState()
.addObject(selectedZone.zoneName, newObject);
const droppedObjectsStore = useDroppedObjectsStore.getState();
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
console.log(
`Objects for Zone ${selectedZone.zoneId}:`,
currentZone.objects
);
setFloatingWidget(currentZone.objects);
} else {
console.warn("Zone not found or zoneId mismatch");
}
} catch (error) {
console.error("Error in handleDrop:", error);
}
};

View File

@ -13,6 +13,7 @@ import {
useWallItems, useWallItems,
useZones, useZones,
useLoadingProgress, useLoadingProgress,
useWidgetSubOption,
} from "../store/store"; } from "../store/store";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { usePlayButtonStore } from "../store/usePlayButtonStore"; import { usePlayButtonStore } from "../store/usePlayButtonStore";
@ -22,12 +23,19 @@ import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys"; import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
import { useSelectedUserStore } from "../store/useCollabStore"; import { useSelectedUserStore } from "../store/useCollabStore";
import FollowPerson from "../components/templates/FollowPerson"; import FollowPerson from "../components/templates/FollowPerson";
import ProductionCapacity from "../components/ui/analysis/ProductionCapacity"; import Scene from "../modules/scene/scene";
import ThroughputSummary from "../components/ui/analysis/ThroughputSummary"; import { createHandleDrop } from "../modules/visualization/functions/handleUiDrop";
import ROISummary from "../components/ui/analysis/ROISummary"; import { useSelectedZoneStore } from "../store/visualization/useZoneStore";
import { useFloatingWidget } from "../store/visualization/useDroppedObjectsStore";
import { useLogger } from "../components/ui/log/LoggerContext";
import RenderOverlay from "../components/templates/Overlay";
import LogList from "../components/ui/log/LogList";
import Footer from "../components/footer/Footer";
const Project: React.FC = () => { const Project: React.FC = () => {
let navigate = useNavigate(); let navigate = useNavigate();
const echo = useLogger();
const { activeModule, setActiveModule } = useModuleStore(); const { activeModule, setActiveModule } = useModuleStore();
const { loadingProgress } = useLoadingProgress(); const { loadingProgress } = useLoadingProgress();
const { setUserName } = useUserName(); const { setUserName } = useUserName();
@ -50,24 +58,33 @@ const Project: React.FC = () => {
setOrganization(Organization); setOrganization(Organization);
setUserName(name); setUserName(name);
} }
echo.info("Log in success full");
} else { } else {
navigate("/"); navigate("/");
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const { isPlaying } = usePlayButtonStore(); // global store
const { toggleThreeD } = useThreeDStore(); const { toggleThreeD } = useThreeDStore();
// simulation store
const { isPlaying } = usePlayButtonStore();
// collaboration store
const { selectedUser } = useSelectedUserStore(); const { selectedUser } = useSelectedUserStore();
const { isLogListVisible } = useLogger();
// real-time visualization store
const { widgetSubOption } = useWidgetSubOption();
const { visualizationSocket } = useSocketStore();
const { selectedZone } = useSelectedZoneStore();
const { setFloatingWidget } = useFloatingWidget();
return ( return (
<div className="project-main"> <div className="project-main">
{/* <div className="analysis"> {!selectedUser && (
<div className="analysis-wrapper"> <>
<ProductionCapacity />
<ThroughputSummary />
</div>
<ROISummary />
</div> */}
<KeyPressListener /> <KeyPressListener />
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />} {loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
{!isPlaying && ( {!isPlaying && (
@ -77,15 +94,43 @@ const Project: React.FC = () => {
<SideBarRight /> <SideBarRight />
</> </>
)} )}
{/* <RenderOverlay>
<MenuBar setOpenMenu={setOpenMenu} />
</RenderOverlay> */}
{activeModule === "market" && <MarketPlace />}
<RealTimeVisulization /> <RealTimeVisulization />
{activeModule === "market" && <MarketPlace />}
{activeModule !== "market" && <Tools />} {activeModule !== "market" && <Tools />}
{isPlaying && activeModule === "simulation" && <SimulationPlayer />} {isPlaying && activeModule === "simulation" && <SimulationPlayer />}
{/* {<SimulationPlayer />} */} </>
)}
<div
className="scene-container"
id="real-time-vis-canvas"
style={{
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
borderRadius:
isPlaying || activeModule !== "visualization" ? "" : "6px",
}}
role="application"
onDrop={(event) =>
createHandleDrop({
widgetSubOption,
visualizationSocket,
selectedZone,
setFloatingWidget,
event,
})
}
onDragOver={(event) => event.preventDefault()}
>
<Scene />
</div>
{selectedUser && <FollowPerson />} {selectedUser && <FollowPerson />}
{isLogListVisible && (
<RenderOverlay>
<LogList />
</RenderOverlay>
)}
<Footer />
</div> </div>
); );
}; };

View File

@ -1,5 +1,4 @@
import { create } from "zustand"; import { create } from "zustand";
import { addingFloatingWidgets } from "../../services/visulization/zone/addFloatingWidgets";
import { useSocketStore } from "../store"; import { useSocketStore } from "../store";
import useChartStore from "./useChartStore"; import useChartStore from "./useChartStore";

View File

@ -22,7 +22,11 @@ $text-button-color-dark: #f3f3fd;
// background colors // background colors
// ---------- light mode ---------- // ---------- light mode ----------
$background-color: linear-gradient(-45deg, #fcfdfd71 0%, #fcfdfd79 100%); $background-color: linear-gradient(-45deg, #fcfdfd71 0%, #fcfdfd79 100%);
$background-color-solid-gradient: linear-gradient(-45deg, #fcfdfd 0%, #fcfdfd 100%); $background-color-solid-gradient: linear-gradient(
-45deg,
#fcfdfd 0%,
#fcfdfd 100%
);
$background-color-solid: #fcfdfd; $background-color-solid: #fcfdfd;
$background-color-secondary: #fcfdfd4d; $background-color-secondary: #fcfdfd4d;
$background-color-accent: #6f42c1; $background-color-accent: #6f42c1;
@ -45,7 +49,11 @@ $background-radial-gray-gradient: radial-gradient(
// ---------- dark mode ---------- // ---------- dark mode ----------
$background-color-dark: linear-gradient(-45deg, #333333b3 0%, #2d2437b3 100%); $background-color-dark: linear-gradient(-45deg, #333333b3 0%, #2d2437b3 100%);
$background-color-solid-gradient-dark: linear-gradient(-45deg, #333333 0%, #2d2437 100%); $background-color-solid-gradient-dark: linear-gradient(
-45deg,
#333333 0%,
#2d2437 100%
);
$background-color-solid-dark: #19191d; $background-color-solid-dark: #19191d;
$background-color-secondary-dark: #19191d99; $background-color-secondary-dark: #19191d99;
$background-color-accent-dark: #6f42c1; $background-color-accent-dark: #6f42c1;
@ -104,6 +112,21 @@ $color3: #b186ff;
$color4: #8752e8; $color4: #8752e8;
$color5: #c7a8ff; $color5: #c7a8ff;
// log indication colors
// ------------ text -------------
$log-default-text-color: #6f42c1;
$log-info-text-color: #488ef6;
$log-warn-text-color: #f3a50c;
$log-error-text-color: #f65648;
$log-success-text-color: #43c06d;
// ------------ background -------------
$log-default-backgroung-color: #6e42c133;
$log-info-background-color: #488ef633;
$log-warn-background-color: #f3a50c33;
$log-error-background-color: #f6564833;
$log-success-background-color: #43c06d33;
// old variables // old variables
$accent-color: #6f42c1; $accent-color: #6f42c1;
$accent-color-dark: #c4abf1; $accent-color-dark: #c4abf1;

View File

@ -37,11 +37,9 @@
// old colors // old colors
--accent-color: #{$accent-color}; --accent-color: #{$accent-color};
--highlight-accent-color: #{$highlight-accent-color};
--accent-gradient-color: #{$acent-gradient}; --accent-gradient-color: #{$acent-gradient};
--faint-gradient-color: #{$faint-gradient}; --faint-gradient-color: #{$faint-gradient};
--background-color-gray: #{$background-color-gray}; --background-color-gray: #{$background-color-gray};
--border-color: #{$border-color};
--shadow-main-light: #{$shadow-color}; --shadow-main-light: #{$shadow-color};
--box-shadow-light: 0px 2px 4px var(--shadow-main-light); --box-shadow-light: 0px 2px 4px var(--shadow-main-light);
--box-shadow-medium: 0px 4px 8px var(--shadow-main-light); --box-shadow-medium: 0px 4px 8px var(--shadow-main-light);
@ -75,7 +73,7 @@
--background-radial-gray-gradient: #{$background-radial-gray-gradient-dark}; --background-radial-gray-gradient: #{$background-radial-gray-gradient-dark};
// border colors // border colors
--border-color: #{$border-color}; --border-color: #{$border-color-dark};
--input-border-color: #{$input-border-color-dark}; --input-border-color: #{$input-border-color-dark};
--border-color-accent: #{$border-color-accent-dark}; --border-color-accent: #{$border-color-accent-dark};
@ -89,11 +87,9 @@
// old colors // old colors
--accent-color: #{$accent-color-dark}; --accent-color: #{$accent-color-dark};
--highlight-accent-color: #{$highlight-accent-color-dark};
--accent-gradient-color: #{$acent-gradient-dark}; --accent-gradient-color: #{$acent-gradient-dark};
--faint-gradient-color: #{$faint-gradient-dark}; --faint-gradient-color: #{$faint-gradient-dark};
--background-color-gray: #{$background-color-gray-dark}; --background-color-gray: #{$background-color-gray-dark};
--border-color: #{$border-color-dark};
--shadow-main-dark: #{$shadow-color}; --shadow-main-dark: #{$shadow-color};
--box-shadow-light: 0px 2px 4px var(--shadow-main-dark); --box-shadow-light: 0px 2px 4px var(--shadow-main-dark);
--box-shadow-medium: 0px 4px 8px var(--shadow-main-dark); --box-shadow-medium: 0px 4px 8px var(--shadow-main-dark);

View File

@ -1,7 +1,8 @@
@use "../abstracts/variables" as *; @use "../abstracts/variables" as *;
@use "../abstracts/mixins" as *; @use "../abstracts/mixins" as *;
section, .section{ section,
.section {
padding: 4px; padding: 4px;
outline: 1px solid var(--border-color); outline: 1px solid var(--border-color);
outline-offset: -1px; outline-offset: -1px;
@ -9,3 +10,21 @@ section, .section{
background: var(--background-color); background: var(--background-color);
margin: 4px 0; margin: 4px 0;
} }
.scene-container {
width: calc(100% - (320px + 270px + 90px));
height: calc(100% - (250px));
position: absolute;
top: 50%;
left: calc(270px + 45px);
overflow: hidden;
z-index: 1;
transform: translate(0, -50%);
transition: all 0.2s;
box-shadow: $box-shadow-medium;
background: var(--background-color-solid);
canvas {
outline: none;
border: none;
}
}

View File

@ -12,10 +12,3 @@ input[type="password"]::-webkit-clear-button, /* For Chrome/Safari clear button
input[type="password"]::-webkit-inner-spin-button { /* Just in case */ input[type="password"]::-webkit-inner-spin-button { /* Just in case */
display: none; display: none;
} }
button{
border: none;
outline: none;
background: none;
cursor: pointer;
}

View File

@ -1,311 +0,0 @@
.roiSummary-container {
.roiSummary-wrapper {
background-color: var(--background-color);
.product-info {
display: flex;
}
.playBack {
display: flex;
background-color: var(--background-color);
border-radius: 12px;
padding: 6px;
.info {
span {
font-size: var(--font-size-xlarge);
&:first-child {
color: #31C756;
}
&:last-child {
color: var(--text-color);
}
}
}
}
.roi-details {
display: flex;
align-items: center;
gap: 12px;
.progress-wrapper {
width: 250px;
display: flex;
flex-direction: column;
gap: 6px;
.content {
display: flex;
flex-direction: column;
gap: 3px;
align-items: center;
.key {
font-size: var(--font-size-xlarge);
color: var(--accent-color);
}
}
}
.roi-progress {
width: 100%;
}
.metrics {
display: flex;
flex-direction: column;
gap: 6px;
.metric-item {
width: 100%;
border-radius: 6px;
border: 1px solid #00FF56;
background: #436D51;
display: flex;
flex-direction: column;
padding: 4px 6px;
&:last-child {
align-items: center;
}
.metric-label {
font-size: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.metric-value {
text-align: center;
line-height: 20px;
}
}
.metric-wrapper {
display: flex;
gap: 6px;
.metric-item {
background-color: var(--background-color);
border: 1px solid var(--Grays-Gray-6, #F2F2F7);
}
}
}
}
.cost-breakdown {
background-color: var(--background-color);
border: 1px solid var(--text-disabled);
border-radius: 8px;
padding: 16px;
.breakdown-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 16px;
.section-wrapper {
display: flex;
gap: 4px;
align-items: center;
}
.section-number {
font-size: 20px;
color: #00aaff;
}
.section-title {
font-size: var(--font-size-regular);
color: var(--text-color);
}
.expand-icon {
font-size: 16px;
color: var(--text-color);
cursor: pointer;
transform: rotate(90deg);
transition: transform 0.2s linear;
}
.expand-icon.open {
transform: rotate(0deg);
}
}
.breakdown-table {
width: 100%;
border-collapse: collapse;
border-radius: 8px;
th,
td {
padding: 8px;
text-align: left;
border-top: 1px solid var(--text-disabled);
border-bottom: 1px solid var(--text-disabled);
}
th:first-child,
td:first-child {
border-left: 1px solid var(--text-disabled);
}
th:last-child,
td:last-child {
border-right: 1px solid var(--text-disabled);
}
th {
background-color: var(--background-color);
color: #333;
}
.total-row,
.net-profit-row {
font-weight: bold;
color: #333;
}
}
}
.tips-section {
background-color: var(--background-color);
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px;
.tip-header {
display: flex;
align-items: center;
.tip-title {
color: var(--text-color);
font-weight: 600;
}
}
.tip-description {
span {
font-size: var(--font-size-xlarge);
color: #34C759;
&:first-child {
color: #34C759;
}
&:nth-child(2) {
color: #488EF6;
}
}
}
}
.get-tips-button {
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
margin-top: 8px;
display: inline-block;
display: flex;
justify-content: flex-end;
background: none;
.btn {
background-color: var(--accent-color);
color: var(--background-color);
padding: 4px 6px;
border-radius: 5px;
display: inline-block;
font-size: 14px;
text-align: center;
}
}
}
.semi-circle-wrapper {
width: 100%;
height: 125px;
overflow-y: hidden;
position: relative;
.semi-circle {
width: 100%;
height: 250px;
border-radius: 50%;
position: relative;
transition: background 0.5s ease;
}
.progress-cover {
position: absolute;
width: 75%;
height: 75%;
top: 12.5%;
left: 12.5%;
background: #000000cc;
border-radius: 50%;
}
}
.label-wrapper {
.label {
font-size: var(--font-size-xxxlarge);
}
position: absolute;
bottom: 0%;
left: 50%;
transform: translate(-50%, 0%);
font-weight: bold;
font-size: 1.2rem;
color: #333;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
}
// Breakdown Table Open/Close Logic
.breakdown-table-wrapper {
&.closed {
max-height: 0;
padding: 0;
}
&.open {
max-height: 500px;
}
.breakdown-table {
width: 100%;
border-collapse: collapse;
th,
td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
}
}

View File

@ -1,279 +0,0 @@
.analysis {
position: fixed;
top: 0;
left: 0;
display: flex;
justify-content: space-between;
align-items: start;
width: 100%;
height: 100vh;
// pointer-events: none;
z-index: 10000;
.analysis-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
}
.analysis-card {
min-width: 333px;
background: var(--background-color);
border-radius: 20px;
padding: 8px;
.analysis-card-wrapper {
width: 100%;
background: var(--background-color);
border-radius: 14px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 14px;
.card-header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.main-header {
line-height: 20px;
font-size: var(--font-size-regular);
}
}
.process-container {
display: flex;
flex-direction: column;
.throughput-value {
font-size: 1rem;
.value {
font-weight: bold;
font-size: 1.5rem;
}
}
.progress-bar-wrapper {
display: flex;
gap: 8px;
margin-top: 6px;
}
.progress-bar {
position: relative;
// width: 36px;
width: 100%;
height: 4px;
border-radius: 13px;
overflow: hidden;
background: #FBEBD7;
.bar-fill {
position: absolute;
height: 100%;
top: 0;
left: 0;
background: #FC9D2F;
border-radius: 13px;
}
.bar-fill.full {
width: 100%;
}
.bar-fill.partial {
width: 0; // inline style will override this
}
}
}
.metrics-section {
padding-top: 16px;
border-top: 1px solid var(--background-color-gray);
.metric {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
margin-bottom: 8px;
.label {
color: var(--text-color);
}
.value {
font-weight: bold;
}
}
}
}
}
.throughoutSummary {
.throughoutSummary-wrapper {
.process-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 16px;
width: 100%;
.throughput-value {
font-size: var(--font-size-small);
flex: 1;
display: flex;
flex-direction: column;
.value {
color: var(--accent-color);
}
/* Let the text take available space */
}
.lineChart {
max-width: 200px;
height: 100px;
position: relative;
.assetUsage {
text-align: right;
position: absolute;
right: 0;
top: 0;
}
canvas {
background: transparent;
}
}
}
.footer {
display: flex;
gap: 16px; // Space between cards
margin-top: 24px;
.footer-card {
width: 100%;
background: var(--background-color-gray);
border-radius: 6px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
&:first-child {
width: 85%;
}
.header {
font-size: var(--font-size-regular);
}
.value-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: end;
gap: 6px;
}
}
.shiftUtilization {
.value-container {
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
.value {
font-size: var(--font-size-xlarge);
}
.progress-wrapper {
width: 100%;
display: flex;
gap: 6px;
.progress {
border-radius: 6px;
height: 5px;
&:nth-child(1) {
background: #F3C64D;
}
&:nth-child(2) {
background: #67B3F4;
}
&:nth-child(3) {
background: #7981F5;
}
}
}
.progress-indicator {
display: flex;
justify-content: space-between;
width: 100%;
gap: 6px;
.shift-wrapper {
display: flex;
align-items: center;
gap: 5px;
/* Align items vertically */
&:nth-child(1) {
.indicator {
background: #F3C64D;
}
}
&:nth-child(2) {
.indicator {
background: #67B3F4;
}
}
&:nth-child(3) {
.indicator {
background: #7981F5;
}
}
label {
font-size: var(--font-size-small);
position: relative;
}
.indicator {
display: inline-block;
width: 5px;
height: 5px;
border-radius: 50%;
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,24 @@
@use "../abstracts/variables" as *;
@use "../abstracts/mixins" as *;
.labeled-button-container {
@include flex-space-between;
padding: 6px 12px;
button {
padding: 2px 32px;
border: none;
border-radius: #{$border-radius-large};
color: var(--text-button-color);
background: var(--background-color-button);
transition: all 0.2s;
cursor: pointer;
}
}
button {
border: none;
outline: none;
background: none;
cursor: pointer;
}

View File

@ -0,0 +1,71 @@
@use "../../abstracts/variables" as *;
@use "../../abstracts/mixins" as *;
.footer-wrapper {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
z-index: 1;
display: flex;
justify-content: space-between;
padding: 2px 12px;
.selection-wrapper {
display: flex;
gap: 6px;
.selector-wrapper {
display: flex;
gap: 6px;
align-items: center;
background: var(--background-color);
padding: 3px 6px;
border-radius: 12px;
color: var(--text-color);
.selector {
color: var(--text-color);
}
}
}
.logs-wrapper {
display: flex;
gap: 6px;
.logs-detail,
.version {
border-radius: 12px;
background: var(--background-color);
padding: 3px 6px;
color: var(--text-color);
display: flex;
align-items: center;
gap: 6px;
}
.logs-detail {
padding: 2px 12px;
cursor: pointer;
.log-icon {
@include flex-center;
}
.log-message {
max-width: 40vw;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.version {
font-size: var(--font-size-tiny);
display: flex;
gap: 6px;
.icon{
@include flex-center;
}
}
}
}

View File

@ -639,21 +639,6 @@ input[type="number"] {
} }
} }
.labeled-button-container {
@include flex-space-between;
padding: 6px 12px;
button {
padding: 2px 32px;
border: none;
border-radius: #{$border-radius-large};
color: var(--text-button-color);
background: var(--background-color-button);
transition: all 0.2s;
cursor: pointer;
}
}
.value-field-container { .value-field-container {
margin-bottom: 6px; margin-bottom: 6px;
padding: 6px 12px; padding: 6px 12px;

View File

@ -0,0 +1,105 @@
@use "../../abstracts/variables" as *;
@use "../../abstracts/mixins" as *;
.log-list-container {
width: 100vw;
height: 100vh;
background: var(--background-color-secondary);
.log-list-wrapper {
height: 50%;
min-width: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 5;
background: var(--background-color);
padding: 14px 12px;
border-radius: 15px;
display: flex;
flex-direction: column;
gap: 12px;
backdrop-filter: blur(50px);
outline: 1px solid var(--border-color);
.log-header {
display: flex;
justify-content: space-between;
.log-header-wrapper {
display: flex;
align-items: center;
gap: 6px;
}
.close {
@include flex-center;
height: 28px;
width: 28px;
cursor: pointer;
svg {
scale: 1.6;
}
}
}
.log-nav-wrapper {
display: flex;
gap: 6px;
.log-nav {
padding: 8px 16px;
border-radius: 19px;
}
.log-nav.active {
background-color: var(--background-color-accent);
color: var(--text-button-color);
}
}
.log-entry-wrapper {
height: 100%;
display: flex;
flex-direction: column;
gap: 4px;
background: var(--background-color);
padding: 18px 10px;
border-radius: 16px;
outline: 1px solid var(--border-color);
outline-offset: -1px;
.log-entry {
padding: 4px;
border-radius: 4px;
font-size: var(--font-size-small);
display: flex;
align-items: center;
gap: 6px;
.log-icon {
@include flex-center;
}
.log-entry-message-container {
@include flex-space-between;
gap: 12px;
width: 100%;
.message-time {
font-size: var(--font-size-tiny);
font-weight: 300;
opacity: 0.8;
text-wrap: nowrap;
}
.log-entry-message{
width: 100%;
}
}
&:nth-child(odd) {
background: var(--background-color);
}
}
}
}
}

View File

@ -0,0 +1,557 @@
@use "../../abstracts/variables" as *;
@use "../../abstracts/mixins" as *;
.analysis {
position: fixed;
top: 0;
left: 0;
display: flex;
justify-content: space-between;
align-items: start;
width: 100%;
height: 100vh;
pointer-events: none;
padding: 10px;
z-index: 2;
.analysis-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
.analysis-card {
min-width: 333px;
border-radius: 20px;
padding: 8px;
pointer-events: all;
.analysis-card-wrapper {
width: 100%;
background: var(--background-color);
border-radius: 14px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 14px;
backdrop-filter: blur(10px);
outline: 1px solid var(--border-color);
outline-offset: -1px;
.card-header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.main-header {
line-height: 20px;
font-size: var(--font-size-regular);
}
}
.process-container {
display: flex;
flex-direction: column;
.throughput-value {
font-size: 1rem;
.value {
font-weight: bold;
font-size: 1.5rem;
}
}
.progress-bar-wrapper {
display: flex;
gap: 8px;
margin-top: 6px;
}
.progress-bar {
position: relative;
width: 100%;
height: 4px;
border-radius: 13px;
overflow: hidden;
background: #fbebd7;
.bar-fill {
position: absolute;
height: 100%;
top: 0;
left: 0;
background: #fc9d2f;
border-radius: 13px;
}
.bar-fill.full {
width: 100%;
}
.bar-fill.partial {
width: 0; // inline style will override this
}
}
}
.metrics-section {
padding-top: 16px;
border-top: 1px solid var(--background-color-gray);
.metric {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
margin-bottom: 8px;
.label {
color: var(--text-color);
}
.value {
font-weight: bold;
}
}
}
}
.throughoutSummary-wrapper {
.process-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 16px;
width: 100%;
.throughput-value {
font-size: var(--font-size-small);
flex: 1;
display: flex;
flex-direction: column;
.value {
color: var(--accent-color);
}
/* Let the text take available space */
}
.lineChart {
max-width: 200px;
height: 100px;
position: relative;
.assetUsage {
text-align: right;
position: absolute;
right: 0;
top: 0;
}
canvas {
background: transparent;
}
}
}
.footer {
display: flex;
gap: 16px; // Space between cards
margin-top: 24px;
.footer-card {
width: 100%;
background: var(--background-color-gray);
border-radius: 6px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
&:first-child {
width: 85%;
}
.header {
font-size: var(--font-size-regular);
}
.value-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: end;
gap: 6px;
}
}
.shiftUtilization {
.value-container {
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
.value {
font-size: var(--font-size-xlarge);
}
.progress-wrapper {
width: 100%;
display: flex;
gap: 6px;
.progress {
border-radius: 6px;
height: 5px;
&:nth-child(1) {
background: #f3c64d;
}
&:nth-child(2) {
background: #67b3f4;
}
&:nth-child(3) {
background: #7981f5;
}
}
}
.progress-indicator {
display: flex;
justify-content: space-between;
width: 100%;
gap: 6px;
.shift-wrapper {
display: flex;
align-items: center;
gap: 5px;
/* Align items vertically */
&:nth-child(1) {
.indicator {
background: #f3c64d;
}
}
&:nth-child(2) {
.indicator {
background: #67b3f4;
}
}
&:nth-child(3) {
.indicator {
background: #7981f5;
}
}
label {
font-size: var(--font-size-small);
position: relative;
}
.indicator {
display: inline-block;
width: 5px;
height: 5px;
border-radius: 50%;
}
}
}
}
}
}
}
}
.roiSummary-wrapper {
max-width: 470px;
background-color: var(--background-color);
.product-info {
display: flex;
}
.playBack {
display: flex;
background-color: var(--background-color);
border-radius: 12px;
padding: 6px;
.info {
span {
font-size: var(--font-size-xlarge);
&:first-child {
color: #31c756;
}
&:last-child {
color: var(--text-color);
}
}
}
}
.roi-details {
display: flex;
align-items: center;
gap: 12px;
.progress-wrapper {
width: 250px;
display: flex;
flex-direction: column;
gap: 6px;
.content {
display: flex;
flex-direction: column;
gap: 3px;
align-items: center;
.key {
font-size: var(--font-size-xlarge);
color: var(--accent-color);
}
}
}
.roi-progress {
width: 100%;
}
.metrics {
display: flex;
flex-direction: column;
gap: 6px;
.metric-item {
width: 100%;
border-radius: #{$border-radius-xxx};
border: 1px solid #00ff56;
background: #17eb5e42;
display: flex;
flex-direction: column;
padding: 4px 8px;
&:last-child {
align-items: center;
}
.metric-label {
opacity: 0.8;
font-size: 10px;
font-weight: 300;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.metric-value {
text-align: center;
line-height: 20px;
}
}
.metric-wrapper {
display: flex;
gap: 6px;
.metric-item {
border-radius: #{$border-radius-large};
background-color: var(--background-color);
border: 1px solid var(--border-color);
}
}
}
}
.cost-breakdown {
background-color: var(--background-color);
border: 1px solid var(--border-color);
border-radius: #{$border-radius-extra-large};
padding: 16px;
.breakdown-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
.section-wrapper {
display: flex;
gap: 4px;
align-items: center;
}
.section-number {
color: #00aaff;
}
.section-title {
font-size: var(--font-size-regular);
color: var(--text-color);
}
.expand-icon {
font-size: 16px;
color: var(--text-color);
cursor: pointer;
transform: rotate(90deg);
transition: transform 0.2s linear;
}
.expand-icon.open {
transform: rotate(0deg);
}
}
.breakdown-table {
width: 100%;
border-collapse: collapse;
border-radius: 8px;
overflow: hidden;
outline: 1px solid var(--border-color);
outline-offset: -1px;
margin-top: 12px;
th,
td {
color: var(--text-color);
padding: 8px;
text-align: left;
border: 1px solid var(--border-color);
}
th {
background-color: var(--background-color);
}
}
}
.tips-section {
background-color: var(--background-color);
border-radius: #{$border-radius-large};
outline: 1px solid var(--border-color);
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px;
.tip-header {
display: flex;
align-items: center;
.tip-title {
color: var(--text-color);
font-weight: 600;
}
}
.tip-description {
span {
font-size: var(--font-size-xlarge);
color: #34c759;
&:first-child {
color: #34c759;
}
&:nth-child(2) {
color: #488ef6;
}
}
}
}
.get-tips-button {
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
margin-top: 8px;
display: inline-block;
display: flex;
justify-content: flex-end;
background: none;
.btn {
color: var(--text-button-color);
background: var(--background-color-button);
padding: 4px 12px;
border-radius: #{$border-radius-large};
display: inline-block;
text-align: center;
}
}
}
.semi-circle-wrapper {
width: 100%;
height: 125px;
overflow-y: hidden;
position: relative;
.semi-circle {
width: 100%;
height: 250px;
border-radius: 50%;
position: relative;
}
.progress-cover {
position: absolute;
width: 75%;
height: 75%;
top: 12.5%;
left: 12.5%;
border-radius: 50%;
}
}
.label-wrapper {
.label {
font-size: var(--font-size-xxxlarge);
}
position: absolute;
bottom: 0%;
left: 50%;
transform: translate(-50%, 0%);
font-weight: bold;
font-size: 1.2rem;
color: #333;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
}
.breakdown-table-wrapper {
&.closed {
max-height: 0;
padding: 0;
}
&.open {
max-height: 500px;
}
.breakdown-table {
width: 100%;
border-collapse: collapse;
th,
td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
}
}
// Breakdown Table Open/Close Logic

View File

@ -32,10 +32,17 @@
} }
.controls-container { .controls-container {
@include flex-center; @include flex-space-between;
gap: 12px; gap: 12px;
justify-content: space-between; justify-content: space-between;
.header{
@include flex-center;
gap: 6px;
padding: 0 8px;
svg{
scale: 1.3;
}
}
.production-details, .production-details,
.controls-wrapper { .controls-wrapper {
@include flex-center; @include flex-center;
@ -72,7 +79,7 @@
.expand-icon-container { .expand-icon-container {
@include flex-center; @include flex-center;
padding: 6px 8px; padding: 0 8px;
cursor: pointer; cursor: pointer;
} }
@ -307,6 +314,22 @@
font-size: var(--font-size-tiny); font-size: var(--font-size-tiny);
} }
.timmer {
width: auto;
position: absolute;
bottom: 0;
font-size: var(--font-size-tiny);
}
.start-displayer {
left: 8px;
}
.end-displayer {
width: auto;
right: 8px;
}
.start-displayer { .start-displayer {
bottom: 4px; bottom: 4px;
left: 16px; left: 16px;
@ -347,6 +370,12 @@
} }
.simulation-player-container.open { .simulation-player-container.open {
.start-displayer,
.end-displayer {
display: none;
}
.timmer { .timmer {
display: none; display: none;
} }

View File

@ -15,7 +15,7 @@
transition: width 0.2s; transition: width 0.2s;
background: var(--background-color); background: var(--background-color);
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
z-index: #{$z-index-default}; z-index: 2;
outline: 1px solid var(--border-color); outline: 1px solid var(--border-color);
outline-offset: -1px; outline-offset: -1px;
@ -124,6 +124,8 @@
padding: 4px; padding: 4px;
border-radius: #{$border-radius-medium}; border-radius: #{$border-radius-medium};
background: var(--background-color); background: var(--background-color);
outline: 1px solid var(--border-color);
outline-offset: -1px;
gap: 5px; gap: 5px;
position: relative; position: relative;

View File

@ -58,12 +58,8 @@
fill: var(--icon-default-color-active); fill: var(--icon-default-color-active);
} }
&:hover { &:hover {
rect { filter: saturate(0.8);
stroke: var(--icon-default-color); background: var(--background-color-accent);
}
circle {
fill: var(--icon-default-color);
}
} }
} }
} }
@ -420,7 +416,7 @@
outline: none; outline: none;
path { path {
stroke: var(--text-button-color); stroke: var(--text-button-color);
stroke-width: 1.3; strokeWidth: 1.3;
} }
} }
} }
@ -686,7 +682,7 @@
path { path {
stroke: var(--accent-color); stroke: var(--accent-color);
stroke-width: 1.5px; strokeWidth: 1.5px;
} }
&:hover { &:hover {
@ -714,10 +710,10 @@
.add-button { .add-button {
@include flex-center; @include flex-center;
padding: 2px 4px; padding: 4px 8px;
background: var(--background-color-button); background: var(--background-color-button);
color: var(--text-button-color); color: var(--text-button-color);
border-radius: #{$border-radius-small}; border-radius: #{$border-radius-large};
cursor: pointer; cursor: pointer;
outline: none; outline: none;
border: none; border: none;
@ -832,10 +828,10 @@
transform: translateX(4px); transform: translateX(4px);
&:hover { &:hover {
background: var(--accent-color); background: var(--background-color-accent);
path { path {
stroke: var(--primary-color); stroke: var(--text-button-color);
} }
} }
} }
@ -1003,10 +999,10 @@
border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px;
&:hover { &:hover {
background: var(--accent-color); background: var(--background-color-accent);
path { path {
stroke: var(--primary-color); stroke: var(--text-button-color);
} }
} }
} }
@ -1067,7 +1063,13 @@
.dropdown-content-container { .dropdown-content-container {
padding: 6px 12px; padding: 6px 12px;
} }
.value-field-container {
padding: 6px;
.dropdown {
min-width: 44px;
text-align: center;
}
}
.input-range-container { .input-range-container {
.input-container { .input-container {
width: 75%; width: 75%;

View File

@ -13,7 +13,6 @@
@use 'components/button'; @use 'components/button';
@use 'components/form'; @use 'components/form';
@use 'components/input'; @use 'components/input';
@use 'components/layouts';
@use 'components/lists'; @use 'components/lists';
@use 'components/moduleToggle'; @use 'components/moduleToggle';
@use 'components/templates'; @use 'components/templates';
@ -22,11 +21,12 @@
@use 'components/visualization/ui/styledWidgets'; @use 'components/visualization/ui/styledWidgets';
@use 'components/visualization/floating/common'; @use 'components/visualization/floating/common';
@use 'components/marketPlace/marketPlace'; @use 'components/marketPlace/marketPlace';
@use 'components/simulation/simulation';
@use 'components/menu/menu'; @use 'components/menu/menu';
@use 'components/confirmationPopUp'; @use 'components/confirmationPopUp';
@use 'components/analysis/analysis'; @use 'components/simulation/simulation';
@use 'components/analysis/ROISummary.scss'; @use 'components/simulation/analysis';
@use 'components/logs/logs';
@use 'components/footer/footer.scss';
// layout // layout
@use 'layout/loading'; @use 'layout/loading';

View File

@ -3,8 +3,6 @@
// Main Container // Main Container
.realTime-viz { .realTime-viz {
background: #131313;
box-shadow: $box-shadow-medium;
width: calc(100% - (320px + 270px + 90px)); width: calc(100% - (320px + 270px + 90px));
height: calc(100% - (250px)); height: calc(100% - (250px));
position: absolute; position: absolute;
@ -12,8 +10,8 @@
left: calc(270px + 45px); left: calc(270px + 45px);
transform: translate(0, -50%); transform: translate(0, -50%);
border-radius: #{$border-radius-medium}; border-radius: #{$border-radius-medium};
transition: all 0.2s; z-index: 2;
z-index: #{$z-index-default}; pointer-events: none;
.realTime-viz-wrapper { .realTime-viz-wrapper {
width: 100%; width: 100%;
@ -39,10 +37,6 @@
z-index: 1; z-index: 1;
} }
.scene-container {
overflow: hidden;
}
.icon { .icon {
display: flex; display: flex;
align-items: center; align-items: center;
@ -74,6 +68,8 @@
z-index: 3; z-index: 3;
transform: translate(-50%, -10%); transform: translate(-50%, -10%);
transition: transform 0.5s linear; transition: transform 0.5s linear;
pointer-events: all;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
@ -367,6 +363,7 @@
border-radius: 2px; border-radius: 2px;
transition: transform 0.3s ease; transition: transform 0.3s ease;
box-shadow: #{$box-shadow-medium}; box-shadow: #{$box-shadow-medium};
pointer-events: all;
svg { svg {
stroke: var(--icon-default-color) !important; stroke: var(--icon-default-color) !important;
@ -426,10 +423,8 @@
path { path {
stroke: #f65648; stroke: #f65648;
stroke-width: 1.3; strokeWidth: 1.3;
} }
} }
} }
@ -778,17 +773,10 @@
} }
} }
.panel-content { .panel-content {
background: var(--background-color); background: var(--background-color);
} }
/* RIGHT */ /* RIGHT */
.panel-content.right-opening { .panel-content.right-opening {
animation: rightExpand 0.5s ease-in-out forwards; animation: rightExpand 0.5s ease-in-out forwards;
@ -913,9 +901,6 @@
} }
} }
// Add button // Add button
.extra-Bs-addopening { .extra-Bs-addopening {
@ -926,7 +911,6 @@
animation: slideUp 0.3s ease forwards; animation: slideUp 0.3s ease forwards;
} }
@keyframes slideDown { @keyframes slideDown {
from { from {
opacity: 0; opacity: 0;