ui #1
13
.vscode/settings.json
vendored
Normal file
13
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
"**/.retool_types/**": true,
|
||||
"**/*tsconfig.json": true,
|
||||
".cache": true,
|
||||
"retool.config.json": true
|
||||
}
|
||||
}
|
||||
19
app/package-lock.json
generated
19
app/package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "react",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"chart.js": "^4.4.8",
|
||||
"path": "^0.12.7",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
@@ -1029,6 +1030,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -2159,6 +2166,18 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.4.8",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz",
|
||||
"integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "^4.4.8",
|
||||
"path": "^0.12.7",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
||||
BIN
app/src/assets/orgTemp.png
Normal file
BIN
app/src/assets/orgTemp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -378,8 +378,8 @@ export function UndoIcon() {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.76516 1.73483C3.91161 1.88128 3.91161 2.11872 3.76516 2.26516L2.90533 3.125H7.5C9.0878 3.125 10.375 4.41218 10.375 6C10.375 7.5878 9.0878 8.875 7.5 8.875H4C3.79289 8.875 3.625 8.7071 3.625 8.5C3.625 8.2929 3.79289 8.125 4 8.125H7.5C8.6736 8.125 9.625 7.1736 9.625 6C9.625 4.82639 8.6736 3.875 7.5 3.875H2.90533L3.76516 4.73483C3.91161 4.88128 3.91161 5.1187 3.76516 5.26515C3.61872 5.4116 3.38128 5.4116 3.23483 5.26515L1.73483 3.76516C1.58839 3.61872 1.58839 3.38128 1.73483 3.23483L3.23483 1.73483C3.38128 1.58839 3.61872 1.58839 3.76516 1.73483Z"
|
||||
fill="var(--text-color)"
|
||||
/>
|
||||
@@ -397,8 +397,8 @@ export function RedoIcon() {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8.23484 1.73483C8.08839 1.88128 8.08839 2.11872 8.23484 2.26516L9.09467 3.125H4.5C2.9122 3.125 1.625 4.41218 1.625 6C1.625 7.5878 2.9122 8.875 4.5 8.875H8C8.20711 8.875 8.375 8.7071 8.375 8.5C8.375 8.2929 8.20711 8.125 8 8.125H4.5C3.3264 8.125 2.375 7.1736 2.375 6C2.375 4.82639 3.3264 3.875 4.5 3.875H9.09467L8.23484 4.73483C8.08839 4.88128 8.08839 5.1187 8.23484 5.26515C8.38128 5.4116 8.61872 5.4116 8.76517 5.26515L10.2652 3.76516C10.4116 3.61872 10.4116 3.38128 10.2652 3.23483L8.76517 1.73483C8.61872 1.58839 8.38128 1.58839 8.23484 1.73483Z"
|
||||
fill="var(--text-color)"
|
||||
/>
|
||||
@@ -436,7 +436,7 @@ export function RemoveIcon() {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M3 6.5H9" stroke="var(--text-color)" stroke-linecap="round" />
|
||||
<path d="M3 6.5H9" stroke="var(--text-color)" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -458,8 +458,8 @@ export function InfoIcon() {
|
||||
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"
|
||||
stroke="var(--text-color)"
|
||||
stroke-width="0.72"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -100,3 +100,34 @@ export function VisualizationIcon({ isActive }: { isActive: boolean }) {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function CartIcon({ isActive }: { isActive: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.33337 2L1.50998 2.05887C2.39001 2.35221 2.83002 2.49888 3.08169 2.84807C3.33337 3.19725 3.33337 3.66106 3.33337 4.58869V6.33333C3.33337 8.21893 3.33337 9.16173 3.91916 9.74753C4.50495 10.3333 5.44775 10.3333 7.33337 10.3333H8.66671M12.6667 10.3333H11.3334"
|
||||
stroke={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.00005 12C5.55233 12 6.00005 12.4477 6.00005 13C6.00005 13.5523 5.55233 14 5.00005 14C4.44776 14 4.00005 13.5523 4.00005 13C4.00005 12.4477 4.44776 12 5.00005 12Z"
|
||||
stroke={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<path
|
||||
d="M11 12C11.5523 12 12 12.4477 12 13C12 13.5523 11.5523 14 11 14C10.4478 14 10 13.5523 10 13C10 12.4477 10.4478 12 11 12Z"
|
||||
stroke={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<path
|
||||
d="M3.33337 4H5.33337M3.66671 8.66667H10.6812C11.3208 8.66667 11.6406 8.66667 11.8911 8.50153C12.1416 8.33633 12.2676 8.0424 12.5195 7.45453L12.8052 6.78787C13.3449 5.52863 13.6148 4.89902 13.3184 4.44951C13.0219 4 12.337 4 10.967 4H8.00004"
|
||||
stroke={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@ export function ZoneIcon({ isActive }: { isActive: boolean }) {
|
||||
<g clipPath="url(#clip0_111_378)">
|
||||
<path
|
||||
d="M1.66665 16.6667H2.49998V17.5H0.833313V15.8333H1.66665V16.6667ZM18.3333 16.6667H17.5V17.5H19.1666V15.8333H18.3333V16.6667ZM0.833313 4.16667H1.66665V3.33333H2.49998V2.5H0.833313V4.16667ZM1.66665 5.83333H0.833313V7.5H1.66665V5.83333ZM1.66665 9.16667H0.833313V10.8333H1.66665V9.16667ZM18.3333 7.5H19.1666V5.83333H18.3333V7.5ZM18.3333 10.8333H19.1666V9.16667H18.3333V10.8333ZM1.66665 12.5H0.833313V14.1667H1.66665V12.5ZM18.3333 14.1667H19.1666V12.5H18.3333V14.1667ZM4.16665 3.33333H5.83331V2.5H4.16665V3.33333ZM9.16665 3.33333V2.5H7.49998V3.33333H9.16665ZM10.8333 3.33333H12.5V2.5H10.8333V3.33333ZM15.8333 2.5H14.1666V3.33333H15.8333V2.5ZM4.16665 17.5H5.83331V16.6667H4.16665V17.5ZM7.49998 17.5H9.16665V16.6667H7.49998V17.5ZM10.8333 17.5H12.5V16.6667H10.8333V17.5ZM14.1666 17.5H15.8333V16.6667H14.1666V17.5ZM17.5 3.33333H18.3333V4.16667H19.1666V2.5H17.5V3.33333ZM10.4166 6.66667V8.33333C10.4164 8.55428 10.3286 8.76611 10.1723 8.92235C10.0161 9.07858 9.80426 9.16645 9.58331 9.16667H4.16665C3.9457 9.16645 3.73387 9.07858 3.57763 8.92235C3.4214 8.76611 3.33353 8.55428 3.33331 8.33333V6.66667C3.33353 6.44572 3.4214 6.23389 3.57763 6.07765C3.73387 5.92142 3.9457 5.83355 4.16665 5.83333H9.58331C9.80426 5.83355 10.0161 5.92142 10.1723 6.07765C10.3286 6.23389 10.4164 6.44572 10.4166 6.66667ZM9.58415 8.33333L9.58331 6.66667H4.16665V8.33333H9.58415ZM16.6666 11.6667V13.3333C16.6664 13.5543 16.5786 13.7661 16.4223 13.9223C16.2661 14.0786 16.0543 14.1664 15.8333 14.1667H11.25C11.029 14.1664 10.8172 14.0786 10.661 13.9223C10.5047 13.7661 10.4169 13.5543 10.4166 13.3333V11.6667C10.4169 11.4457 10.5047 11.2339 10.661 11.0777C10.8172 10.9214 11.029 10.8336 11.25 10.8333H15.8333C16.0543 10.8336 16.2661 10.9214 16.4223 11.0777C16.5786 11.2339 16.6664 11.4457 16.6666 11.6667ZM15.8341 13.3333L15.8333 11.6667H11.25V13.3333H15.8341Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={
|
||||
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
|
||||
}
|
||||
/>
|
||||
<path
|
||||
d="M7.7443 13.5777V11.911C7.74408 11.69 7.65621 11.4782 7.49998 11.322C7.34375 11.1657 7.13191 11.0779 6.91097 11.0777H4.16665C3.9457 11.0779 3.73387 11.1657 3.57764 11.322C3.4214 11.4782 3.33353 11.69 3.33331 11.911V13.5777C3.33353 13.7986 3.4214 14.0104 3.57764 14.1667C3.73387 14.3229 3.9457 14.4108 4.16665 14.411H6.91097C7.13191 14.4108 7.34375 14.3229 7.49998 14.1667C7.65621 14.0104 7.74408 13.7986 7.7443 13.5777Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={
|
||||
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
|
||||
}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
@@ -37,27 +41,27 @@ export function AsileIcon({ isActive }: { isActive: boolean }) {
|
||||
>
|
||||
<path
|
||||
d="M3.25821 9.16667H1.13638L1.13638 13.4832L3.25821 9.16667Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<path
|
||||
d="M4.57633 16.6667H1.53737L5.22405 9.16667L8.26301 9.16667L4.57633 16.6667Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<path
|
||||
d="M9.82919 16.6667H6.79023L10.4769 9.16667L13.5159 9.16667L9.82919 16.6667Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<path
|
||||
d="M15.1917 16.6667H12.1528L15.8395 9.16667L18.8637 9.16667V9.1967L15.1917 16.6667Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<path
|
||||
d="M18.8637 14.3162V16.6667H17.7083L18.8637 14.3162Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<path
|
||||
d="M7.75002 0.833332L10 3.08333L4.75002 8.33333H2.50002V6.08333L7.75002 0.833332Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -82,35 +86,37 @@ export function FloorIcon({ isActive }: { isActive: boolean }) {
|
||||
/>
|
||||
<path
|
||||
d="M15.8333 15.8333V3.33333H3.33333V15.8333"
|
||||
stroke={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
stroke={
|
||||
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
|
||||
}
|
||||
/>
|
||||
<path
|
||||
d="M11.0833 6.66667L13.3333 8.91667L8.08333 14.1667H5.83333V11.9167L11.0833 6.66667Z"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<circle
|
||||
cx="3.30001"
|
||||
cy="3.3"
|
||||
r="1.2"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<circle
|
||||
cx="15.8"
|
||||
cy="3.3"
|
||||
r="1.2"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<circle
|
||||
cx="15.8"
|
||||
cy="15.8"
|
||||
r="1.2"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
<circle
|
||||
cx="3.30001"
|
||||
cy="15.8"
|
||||
r="1.2"
|
||||
fill={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "var(--text-color)"}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -366,7 +372,10 @@ export function PillerIcon({ isActive }: { isActive: boolean }) {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M7 5L5.5 6V18.5H14.5V6.5L13.5 5H7Z" fill="var(--highlight-accent-color)" />
|
||||
<path
|
||||
d="M7 5L5.5 6V18.5H14.5V6.5L13.5 5H7Z"
|
||||
fill="var(--highlight-accent-color)"
|
||||
/>
|
||||
<path
|
||||
d="M14.7545 1.94309H5.22935C4.9007 1.94074 4.57782 2.04074 4.29614 2.23213C4.01447 2.42352 3.78489 2.69889 3.63259 3.02804C3.4803 3.35718 3.41117 3.72738 3.4328 4.098C3.45444 4.46862 3.56599 4.82535 3.75522 5.12904C3.94445 5.43272 4.20405 5.67163 4.50553 5.81955C4.807 5.96747 5.13871 6.01868 5.46425 5.96756C5.78978 5.91644 6.09657 5.76497 6.35094 5.52976C6.60531 5.29456 6.79744 4.98471 6.90624 4.63423H13.1088C13.2185 4.98209 13.4107 5.2892 13.6644 5.522C13.918 5.75479 14.2234 5.90432 14.5472 5.95425C14.871 6.00417 15.2007 5.95258 15.5003 5.80509C15.7999 5.65761 16.0578 5.41991 16.246 5.11797C16.4341 4.81603 16.5452 4.46146 16.5671 4.093C16.589 3.72454 16.5209 3.35636 16.3702 3.02869C16.2195 2.70102 15.992 2.42646 15.7125 2.235C15.4331 2.04355 15.1125 1.94257 14.7857 1.94309H14.7545Z"
|
||||
fill="var(--highlight-accent-color)"
|
||||
@@ -475,7 +484,7 @@ export function FreeMoveIcon({ isActive }: { isActive: boolean }) {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.21757 16.8828L6.21566 16.8809C4.68158 15.3449 4.04131 13.1249 3.70825 11.97C3.65833 11.7969 3.61531 11.6477 3.57721 11.5284C3.50044 11.2891 3.40551 11.0392 3.30564 10.7763C3.24797 10.6245 3.18866 10.4684 3.13024 10.3074C2.98703 9.9129 2.83332 9.45028 2.83332 9.08537C2.83332 8.6844 3.10122 8.39598 3.31123 8.24069C3.53078 8.07835 3.82912 7.95218 4.1196 7.95218C4.55725 7.95218 4.93181 8.04977 5.25662 8.33367C5.54723 8.58768 5.75625 8.95913 5.96553 9.37774L5.96584 9.37836C6.08939 9.62636 6.21705 9.92271 6.34124 10.2145C6.35686 10.2512 6.37243 10.2879 6.38797 10.3244C6.4984 10.5843 6.60695 10.8398 6.7149 11.0731C6.75596 11.1619 6.79562 11.2445 6.8338 11.3204C6.83726 11.2299 6.8391 11.1288 6.8391 11.0163V3.4725C6.8391 3.16159 6.93896 2.83429 7.17719 2.58029C7.42159 2.31972 7.76795 2.18294 8.16676 2.18294C8.56324 2.18294 8.90801 2.32082 9.15193 2.57892C9.2602 2.69348 9.3408 2.82347 9.39652 2.96076V2.44843C9.39652 2.13309 9.50121 1.80699 9.74134 1.55631C9.98647 1.30041 10.331 1.16668 10.7242 1.16668C11.1174 1.16668 11.4619 1.30041 11.707 1.55632C11.9471 1.80701 12.0518 2.13311 12.0518 2.44843V2.54199C12.1086 2.40242 12.1909 2.27083 12.3008 2.15544C12.5442 1.89979 12.8875 1.76081 13.2816 1.76081C13.6757 1.76081 14.019 1.89979 14.2624 2.15545C14.5005 2.40556 14.6092 2.73172 14.6092 3.05037V4.9622C14.6621 4.83073 14.738 4.70531 14.8404 4.59356C15.0814 4.33067 15.4252 4.19203 15.8225 4.19203C16.2229 4.19203 16.5718 4.32703 16.8195 4.58529C17.0621 4.83817 17.1667 5.16647 17.1667 5.48156V12.7517C17.1667 14.7775 16.5703 16.3184 15.4877 17.3498C14.4096 18.3768 12.9178 18.8333 11.2539 18.8333C9.13033 18.8333 7.51063 18.1635 6.21757 16.8828ZM7.1414 11.8084C7.14138 11.8084 7.14074 11.8079 7.1395 11.807C7.14078 11.808 7.14141 11.8085 7.1414 11.8084Z"
|
||||
d="M16.6667 12.7517C16.6667 16.6057 14.4072 18.3333 11.2539 18.3333C9.25097 18.3333 7.76123 17.708 6.56942 16.5275C4.91414 14.8702 4.36788 12.3609 4.05339 11.3759C3.80507 10.602 3.33332 9.60916 3.33332 9.08537C3.33332 8.74923 3.80507 8.45218 4.1196 8.45218C4.83136 8.45218 5.09621 8.75703 5.5183 9.60133C5.98178 10.5316 6.54456 12.2671 6.99152 12.2671C7.24806 12.2671 7.3391 11.8371 7.3391 11.0163V3.4725C7.3391 3.05818 7.60395 2.68294 8.16676 2.68294C8.72128 2.68294 8.99442 3.05818 8.99442 3.4725V9.64822C9.25927 9.57788 9.56547 9.52314 9.89652 9.47625V2.44843C9.89652 2.0341 10.1697 1.66668 10.7242 1.66668C11.2787 1.66668 11.5518 2.0341 11.5518 2.44843V9.42151C11.858 9.43716 12.156 9.46844 12.4539 9.51534V3.05037C12.4539 2.63605 12.7354 2.26081 13.2816 2.26081C13.8279 2.26081 14.1092 2.63605 14.1092 3.05037V9.95311C14.432 10.0782 14.7383 10.2267 15.0114 10.3909V5.48156C15.0114 5.06723 15.268 4.69203 15.8225 4.69203C16.3935 4.69203 16.6667 5.06723 16.6667 5.48156V12.7517Z"
|
||||
stroke="var(--text-color)"
|
||||
/>
|
||||
</svg>
|
||||
@@ -563,3 +572,67 @@ export function CursorIcon({ isActive }: { isActive: boolean }) {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlayIcon({ isActive }: { isActive: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.9111 8.51444C17.363 9.37988 17.363 11.6201 15.9111 12.4856L7.14505 17.7109C5.73403 18.552 4 17.4572 4 15.7253V5.27468C4 3.54276 5.73403 2.44801 7.14505 3.28911L15.9111 8.51444Z"
|
||||
stroke={isActive ? "none" : "var(--text-color)"}
|
||||
fill={isActive ? "var(--highlight-accent-color)" : "none"}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PenIcon({ isActive }: { isActive: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.5 15.5L12.7929 16.7929C13.1834 17.1834 13.8166 17.1834 14.2071 16.7929L16.2929 14.7071C16.6834 14.3166 16.6834 13.6834 16.2929 13.2929L15 12M11.5 15.5L5.58952 13.6474C5.22732 13.5568 4.94573 13.2721 4.85925 12.9089L2.5 3M11.5 15.5L15 12M2.5 3L12.4089 5.35925C12.7721 5.44573 13.0568 5.72732 13.1474 6.08952L15 12M2.5 3L7.29941 7.78644M9 8.5C9 9.05228 8.55228 9.5 8 9.5C7.44772 9.5 7 9.05228 7 8.5C7 7.94772 7.44772 7.5 8 7.5C8.55228 7.5 9 7.94772 9 8.5Z"
|
||||
stroke={
|
||||
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
|
||||
}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function SaveTemplateIcon({ isActive }: { isActive: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.5 13.4104V9.24922C17.5 5.67539 17.5 3.88847 16.4017 2.77822C15.3033 1.66797 13.5355 1.66797 10 1.66797C6.46447 1.66797 4.6967 1.66797 3.59835 2.77822C2.5 3.88847 2.5 5.67539 2.5 9.24922V13.4104C2.5 15.9909 2.5 17.2811 3.11176 17.8449C3.40351 18.1138 3.77179 18.2827 4.1641 18.3276C4.98668 18.4217 5.94727 17.5721 7.86847 15.8728C8.71767 15.1217 9.14233 14.7461 9.63358 14.6472C9.8755 14.5985 10.1245 14.5985 10.3664 14.6472C10.8577 14.7461 11.2823 15.1217 12.1315 15.8728C14.0527 17.5721 15.0133 18.4217 15.8359 18.3276C16.2282 18.2827 16.5965 18.1138 16.8882 17.8449C17.5 17.2811 17.5 15.9909 17.5 13.4104Z"
|
||||
stroke={
|
||||
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
|
||||
}
|
||||
/>
|
||||
<path
|
||||
d="M12.5 5H7.5"
|
||||
stroke={
|
||||
isActive ? "var(--highlight-accent-color)" : "var(--text-color)"
|
||||
}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
100
app/src/components/icons/RealTimeVisulationIcons.tsx
Normal file
100
app/src/components/icons/RealTimeVisulationIcons.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
export function CleanPannel() {
|
||||
return (
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_1782_1158)">
|
||||
<path d="M12 0H0V12H12V0Z" fill="white" fillOpacity="0.01" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5 1.47852H7V3.47853H10.75V5.47853H1.25V3.47853H5V1.47852Z"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M2 10H10V5.5H2V10Z" stroke="#2B3344" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M4 9.97439V8.47852"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M6 9.97461V8.47461"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 9.97439V8.47852"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 10H9"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1782_1158">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function EyeIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="15"
|
||||
viewBox="0 0 14 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.75047 7.4375C8.75047 8.40402 7.967 9.1875 7.00047 9.1875C6.034 9.1875 5.25049 8.40402 5.25049 7.4375C5.25049 6.47097 6.034 5.6875 7.00047 5.6875C7.967 5.6875 8.75047 6.47097 8.75047 7.4375Z"
|
||||
stroke="#1D1E21"
|
||||
strokeOpacity="0.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.00086 3.35419C4.3889 3.35419 2.1779 5.07087 1.43457 7.43752C2.17789 9.80416 4.3889 11.5209 7.00086 11.5209C9.6128 11.5209 11.8238 9.80416 12.5671 7.43752C11.8238 5.07088 9.6128 3.35419 7.00086 3.35419Z"
|
||||
stroke="#1D1E21"
|
||||
strokeOpacity="0.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function LockIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="15"
|
||||
viewBox="0 0 14 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.0835 6.28763C4.35849 6.27083 4.69751 6.27083 5.1335 6.27083H8.86683C9.30281 6.27083 9.64185 6.27083 9.91683 6.28763M4.0835 6.28763C3.74031 6.30857 3.49683 6.35571 3.28901 6.46158C2.95973 6.62935 2.69201 6.89704 2.52423 7.22633C2.3335 7.60072 2.3335 8.09072 2.3335 9.07083V9.8875C2.3335 10.8676 2.3335 11.3576 2.52423 11.732C2.69201 12.0613 2.95973 12.329 3.28901 12.4967C3.66336 12.6875 4.1534 12.6875 5.1335 12.6875H8.86683C9.84695 12.6875 10.3369 12.6875 10.7113 12.4967C11.0406 12.329 11.3083 12.0613 11.4761 11.732C11.6668 11.3576 11.6668 10.8676 11.6668 9.8875V9.07083C11.6668 8.09072 11.6668 7.60072 11.4761 7.22633C11.3083 6.89704 11.0406 6.62935 10.7113 6.46158C10.5035 6.35571 10.26 6.30857 9.91683 6.28763M4.0835 6.28763V5.10417C4.0835 3.49334 5.38933 2.1875 7.00016 2.1875C8.61098 2.1875 9.91683 3.49334 9.91683 5.10417V6.28763"
|
||||
stroke="#1D1E21"
|
||||
strokeOpacity="0.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -10,14 +10,14 @@ export function AnalysisIcon({ isActive }: { isActive: boolean }) {
|
||||
<path
|
||||
d="M17.5002 12.4987L15.1418 10.1404M10.8335 14.1654H5.8335M7.50016 10.832H5.8335M10.8335 8.33203C10.8335 8.82648 10.9801 9.30983 11.2548 9.72096C11.5295 10.1321 11.92 10.4525 12.3768 10.6417C12.8336 10.8309 13.3363 10.8805 13.8212 10.784C14.3062 10.6875 14.7516 10.4494 15.1013 10.0998C15.4509 9.75017 15.689 9.30471 15.7855 8.81976C15.8819 8.3348 15.8324 7.83214 15.6432 7.37532C15.454 6.91851 15.1335 6.52806 14.7224 6.25336C14.3113 5.97865 13.8279 5.83203 13.3335 5.83203C12.6705 5.83203 12.0346 6.09542 11.5657 6.56426C11.0969 7.03311 10.8335 7.66899 10.8335 8.33203Z"
|
||||
stroke={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M14.1667 14.1667V16.6667C14.1667 16.8877 14.0789 17.0996 13.9226 17.2559C13.7663 17.4122 13.5543 17.5 13.3333 17.5H3.33333C3.11232 17.5 2.90036 17.4122 2.74408 17.2559C2.5878 17.0996 2.5 16.8877 2.5 16.6667V3.33333C2.5 3.11232 2.5878 2.90036 2.74408 2.74408C2.90036 2.5878 3.11232 2.5 3.33333 2.5H13.3333"
|
||||
stroke={isActive ? "var(--primary-color)" : "var(--text-color)"}
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -5,21 +5,31 @@ import Header from "./Header";
|
||||
import useToggleStore from "../../../store/useUIToggleStore";
|
||||
import Assets from "./Assets";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import Widgets from "./visualization/widgets/Widgets";
|
||||
import Templates from "./visualization/Templates";
|
||||
import Search from "../../ui/inputs/Search";
|
||||
|
||||
const SideBarLeft: React.FC = () => {
|
||||
const [activeOption, setActiveOption] = useState("Outline");
|
||||
const [activeOption, setActiveOption] = useState("Widgets");
|
||||
|
||||
const { toggleUI } = useToggleStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
// Reset activeList whenever activeModule changes
|
||||
// Reset activeOption whenever activeModule changes
|
||||
useEffect(() => {
|
||||
setActiveOption("Outline");
|
||||
if (activeModule === "visualization") setActiveOption("Widgets");
|
||||
}, [activeModule]);
|
||||
|
||||
const handleToggleClick = (option: string) => {
|
||||
setActiveOption(option); // Update the active option
|
||||
};
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
// Log the search value for now
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sidebar-left-wrapper">
|
||||
<Header />
|
||||
@@ -28,11 +38,17 @@ const SideBarLeft: React.FC = () => {
|
||||
{activeModule === "visualization" ? (
|
||||
<>
|
||||
<ToggleHeader
|
||||
options={["Outline", "Widgets", "Templates"]}
|
||||
options={["Widgets", "Templates"]}
|
||||
activeOption={activeOption}
|
||||
handleClick={handleToggleClick}
|
||||
/>
|
||||
<Search onChange={handleSearchChange} />
|
||||
<div className="sidebar-left-content-container">
|
||||
{activeOption === "Widgets" ? <Widgets /> : <Templates />}
|
||||
</div>
|
||||
</>
|
||||
) : activeModule === "market" ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
<ToggleHeader
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import React from "react";
|
||||
import useTemplateStore from "../../../../store/useTemplateStore";
|
||||
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
|
||||
|
||||
const Templates = () => {
|
||||
const { templates, removeTemplate } = useTemplateStore();
|
||||
const { setSelectedZone } = useSelectedZoneStore();
|
||||
|
||||
console.log('templates: ', templates);
|
||||
const handleDeleteTemplate = (id: string) => {
|
||||
removeTemplate(id);
|
||||
};
|
||||
|
||||
const handleLoadTemplate = (template: any) => {
|
||||
setSelectedZone((prev) => ({
|
||||
...prev,
|
||||
panelOrder: template.panelOrder,
|
||||
activeSides: Array.from(new Set([...prev.activeSides, ...template.panelOrder])),
|
||||
widgets: template.widgets,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="template-list" style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))',
|
||||
gap: '1rem',
|
||||
padding: '1rem'
|
||||
}}>
|
||||
{templates.map((template) => (
|
||||
<div key={template.id} className="template-item" style={{
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: '8px',
|
||||
padding: '1rem',
|
||||
transition: 'box-shadow 0.3s ease',
|
||||
|
||||
|
||||
}}>
|
||||
{template.snapshot && (
|
||||
<div style={{ position: 'relative', paddingBottom: '56.25%' }}> {/* 16:9 aspect ratio */}
|
||||
<img
|
||||
src={template.snapshot} // Corrected from template.image to template.snapshot
|
||||
alt={`${template.name} preview`}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.3s ease',
|
||||
// ':hover': {
|
||||
// transform: 'scale(1.05)'
|
||||
// }
|
||||
}}
|
||||
onClick={() => handleLoadTemplate(template)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: '0.5rem'
|
||||
}}>
|
||||
<div
|
||||
onClick={() => handleLoadTemplate(template)}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
fontWeight: '500',
|
||||
// ':hover': {
|
||||
// textDecoration: 'underline'
|
||||
// }
|
||||
}}
|
||||
>
|
||||
{template.name}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDeleteTemplate(template.id)}
|
||||
style={{
|
||||
padding: '0.25rem 0.5rem',
|
||||
background: '#ff4444',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
transition: 'opacity 0.3s ease',
|
||||
// ':hover': {
|
||||
// opacity: 0.8
|
||||
// }
|
||||
}}
|
||||
aria-label="Delete template"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{templates.length === 0 && (
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
color: '#666',
|
||||
padding: '2rem',
|
||||
gridColumn: '1 / -1'
|
||||
}}>
|
||||
No saved templates yet. Create one in the visualization view!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Templates;
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import React, { useEffect, useRef, useMemo } from "react";
|
||||
import { Chart } from "chart.js/auto";
|
||||
import { useThemeStore } from "../../../../../store/useThemeStore";
|
||||
|
||||
// Define Props Interface
|
||||
interface ChartComponentProps {
|
||||
type: any; // Type of chart (e.g., "bar", "line", etc.)
|
||||
title: string; // Title of the chart
|
||||
fontFamily?: string; // Optional font family for the chart title
|
||||
fontSize?: string; // Optional font size for the chart title
|
||||
fontWeight?: "Light" | "Regular" | "Bold"; // Optional font weight for the chart title
|
||||
data: {
|
||||
labels: string[]; // Labels for the x-axis
|
||||
datasets: {
|
||||
data: number[]; // Data points for the chart
|
||||
backgroundColor: string; // Background color for the chart
|
||||
borderColor: string; // Border color for the chart
|
||||
borderWidth: number; // Border width for the chart
|
||||
}[];
|
||||
}; // Data for the chart
|
||||
}
|
||||
|
||||
const ChartComponent = ({
|
||||
type,
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular", // Default to "Regular"
|
||||
data: propsData,
|
||||
}: ChartComponentProps) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const { themeColor } = useThemeStore();
|
||||
|
||||
// Memoize Theme Colors to Prevent Unnecessary Recalculations
|
||||
const buttonActionColor = useMemo(
|
||||
() => themeColor[0] || "#5c87df",
|
||||
[themeColor]
|
||||
);
|
||||
const buttonAbortColor = useMemo(
|
||||
() => themeColor[1] || "#ffffff",
|
||||
[themeColor]
|
||||
);
|
||||
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
color: "#2B3344",
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
// Memoize Chart Data
|
||||
const data = useMemo(() => propsData, [propsData]);
|
||||
|
||||
// Memoize Chart Options
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
align: "start", // Left align the title
|
||||
padding: {
|
||||
top: 10, // Add padding above the title
|
||||
bottom: 20, // Add padding between the title and the chart
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
// Initialize Chart on Component Mount
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return;
|
||||
|
||||
const ctx = canvasRef.current.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const chart = new Chart(ctx, { type, data, options });
|
||||
|
||||
// Cleanup: Destroy the chart instance when the component unmounts
|
||||
return () => chart.destroy();
|
||||
}, [type, data, options]); // Only recreate the chart when these dependencies change
|
||||
|
||||
return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
|
||||
};
|
||||
|
||||
export default React.memo(ChartComponent, (prevProps, nextProps) => {
|
||||
// Custom comparison function to prevent unnecessary re-renders
|
||||
return (
|
||||
prevProps.type === nextProps.type &&
|
||||
prevProps.title === nextProps.title &&
|
||||
prevProps.fontFamily === nextProps.fontFamily &&
|
||||
prevProps.fontSize === nextProps.fontSize &&
|
||||
prevProps.fontWeight === nextProps.fontWeight &&
|
||||
JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data)
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useState } from "react";
|
||||
import ToggleHeader from "../../../../ui/inputs/ToggleHeader";
|
||||
import Widgets2D from "./Widgets2D";
|
||||
import Widgets3D from "./Widgets3D";
|
||||
import WidgetsTemplate from "./WidgetsTemplate";
|
||||
|
||||
const Widgets = () => {
|
||||
const [activeOption, setActiveOption] = useState("2D");
|
||||
|
||||
const handleToggleClick = (option: string) => {
|
||||
setActiveOption(option);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="widget-left-sideBar">
|
||||
<ToggleHeader
|
||||
options={["2D", "3D", "Templates"]}
|
||||
activeOption={activeOption}
|
||||
handleClick={handleToggleClick}
|
||||
/>
|
||||
{activeOption === "2D" && <Widgets2D />}
|
||||
{activeOption === "3D" && <Widgets3D />}
|
||||
{activeOption === "Templates" && <WidgetsTemplate />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Widgets;
|
||||
@@ -0,0 +1,138 @@
|
||||
import React from "react";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import { ChartType } from "chart.js/auto";
|
||||
import ChartComponent from "./ChartComponent";
|
||||
|
||||
const chartTypes: ChartType[] = [
|
||||
"bar",
|
||||
"line",
|
||||
"pie",
|
||||
"doughnut",
|
||||
"radar",
|
||||
"polarArea",
|
||||
];
|
||||
|
||||
const sampleData = {
|
||||
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"],
|
||||
datasets: [
|
||||
{
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1",
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
interface WidgetProps {
|
||||
type: ChartType;
|
||||
index: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const ChartWidget: React.FC<WidgetProps> = ({ type, index, title }) => {
|
||||
const { setDraggedAsset } = useWidgetStore((state) => state);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`chart chart-${index + 1}`}
|
||||
draggable
|
||||
onDragStart={() => {
|
||||
setDraggedAsset({
|
||||
type,
|
||||
id: `widget-${index + 1}`,
|
||||
title,
|
||||
panel: "top",
|
||||
data: sampleData,
|
||||
});
|
||||
}}
|
||||
onDragEnd={() => setDraggedAsset(null)}
|
||||
>
|
||||
<ChartComponent type={type} title={title} data={sampleData} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProgressBarWidget = ({
|
||||
id,
|
||||
title,
|
||||
data,
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
data: any;
|
||||
}) => {
|
||||
const { setDraggedAsset } = useWidgetStore((state) => state);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="chart progressBar"
|
||||
draggable
|
||||
onDragStart={() => {
|
||||
setDraggedAsset({
|
||||
type: "progress",
|
||||
id,
|
||||
title,
|
||||
panel: "top",
|
||||
data,
|
||||
});
|
||||
}}
|
||||
onDragEnd={() => setDraggedAsset(null)}
|
||||
>
|
||||
<div className="header">{title}</div>
|
||||
{data.stocks.map((stock: any, index: number) => (
|
||||
<div className="stock" key={index}>
|
||||
<span className="stock-item">
|
||||
<span className="stockValues">
|
||||
<div className="key">{stock.key}</div>
|
||||
<div className="value">{stock.value}</div>
|
||||
</span>
|
||||
<div className="stock-description">{stock.description}</div>
|
||||
</span>
|
||||
<div className="icon">Icon</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Widgets2D = () => {
|
||||
return (
|
||||
<div className="widget2D">
|
||||
<div className="chart-container">
|
||||
{chartTypes.map((type, index) => {
|
||||
const widgetTitle = `Widget ${index + 1}`;
|
||||
return (
|
||||
<ChartWidget
|
||||
key={index}
|
||||
type={type}
|
||||
index={index}
|
||||
title={widgetTitle}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<ProgressBarWidget
|
||||
id="widget-7"
|
||||
title="Widget 7"
|
||||
data={{
|
||||
stocks: [
|
||||
{ key: "units", value: 1000, description: "Initial stock" },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
<ProgressBarWidget
|
||||
id="widget-8"
|
||||
title="Widget 8"
|
||||
data={{
|
||||
stocks: [
|
||||
{ key: "units", value: 1000, description: "Initial stock" },
|
||||
{ key: "units", value: 500, description: "Additional stock" },
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Widgets2D;
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const Widgets3D = () => {
|
||||
return (
|
||||
<div>
|
||||
Widgets3D
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Widgets3D
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
|
||||
const WidgetsTemplate = () => {
|
||||
return (
|
||||
<div>
|
||||
WidgetsTemplate
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WidgetsTemplate
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { AppDockIcon } from "../../icons/HeaderIcons";
|
||||
import orgImg from "../../../assets/orgTemp.png"
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const guestUsers = [
|
||||
@@ -38,7 +39,7 @@ const Header: React.FC = () => {
|
||||
V
|
||||
</div>
|
||||
<div className="user-organization">
|
||||
<img src="" alt="" />
|
||||
<img src={orgImg} alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "../../icons/SimulationIcons";
|
||||
import useToggleStore from "../../../store/useUIToggleStore";
|
||||
import MachineMechanics from "./mechanics/MachineMechanics";
|
||||
import Visualization from "./visualization/Visualization";
|
||||
|
||||
const SideBarRight: React.FC = () => {
|
||||
const { activeModule } = useModuleStore();
|
||||
@@ -54,13 +55,17 @@ const SideBarRight: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{toggleUI && (
|
||||
{/* process builder */}
|
||||
{toggleUI && activeModule === "builder" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<MachineMechanics />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* realtime visualization */}
|
||||
{activeModule === "visualization" && <Visualization />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useState } from "react";
|
||||
import Search from "../../../ui/inputs/Search";
|
||||
import ToggleHeader from "../../../ui/inputs/ToggleHeader";
|
||||
import Data from "./data/Data";
|
||||
import Design from "./design/Design";
|
||||
|
||||
const Visualization = () => {
|
||||
const [activeOption, setActiveOption] = useState("Data");
|
||||
|
||||
const handleToggleClick = (option: string) => {
|
||||
setActiveOption(option); // Update the active option
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="visualization-right-sideBar">
|
||||
<ToggleHeader
|
||||
options={["Data", "Design"]}
|
||||
activeOption={activeOption}
|
||||
handleClick={handleToggleClick}
|
||||
/>
|
||||
<div className="sidebar-left-content-container">
|
||||
{activeOption === "Data" ? <Data /> : <Design />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Visualization;
|
||||
@@ -0,0 +1,138 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import { RemoveIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import MultiLevelDropDown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
|
||||
interface Child {
|
||||
id: number;
|
||||
easing: string;
|
||||
}
|
||||
const DATA_STRUCTURE = {
|
||||
furnace: {
|
||||
coolingRate: "coolingRate",
|
||||
furnaceTemperature: "furnaceTemperature",
|
||||
heatingRate: "heatingRate",
|
||||
machineId: "machineId",
|
||||
powerConsumption: "powerConsumption",
|
||||
status: "status",
|
||||
timestamp: "timestamp",
|
||||
vacuumLevel: "vacuumLevel",
|
||||
},
|
||||
testDevice: {
|
||||
abrasiveLevel: {
|
||||
data1: "Data 1",
|
||||
data2: "Data 2",
|
||||
data3: "Data 3",
|
||||
},
|
||||
airPressure: "airPressure",
|
||||
machineId: "machineId",
|
||||
powerConsumption: "powerConsumption",
|
||||
status: "status",
|
||||
temperature: {
|
||||
data1: "Data 1",
|
||||
data2: "Data 2",
|
||||
data3: "Data 3",
|
||||
},
|
||||
timestamp: {
|
||||
data1: "Data 1",
|
||||
data2: "Data 2",
|
||||
data3: "Data 3",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
interface Group {
|
||||
id: number;
|
||||
easing: string;
|
||||
children: Child[];
|
||||
}
|
||||
|
||||
const Data = () => {
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
|
||||
// State to store groups for all widgets (using Widget.id as keys)
|
||||
const [chartDataGroups, setChartDataGroups] = useState<
|
||||
Record<string, Group[]>
|
||||
>({});
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize data groups for the newly selected widget if it doesn't exist
|
||||
if (selectedChartId && !chartDataGroups[selectedChartId.id]) {
|
||||
setChartDataGroups((prev) => ({
|
||||
...prev,
|
||||
[selectedChartId.id]: [
|
||||
{
|
||||
id: Date.now(),
|
||||
easing: "Connecter 1",
|
||||
children: [
|
||||
{ id: Date.now(), easing: "Linear" },
|
||||
{ id: Date.now() + 1, easing: "Ease Out" },
|
||||
{ id: Date.now() + 2, easing: "Linear" },
|
||||
],
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
}, [selectedChartId]);
|
||||
|
||||
// Handle adding a new child to the group
|
||||
const handleAddClick = (groupId: number) => {
|
||||
setChartDataGroups((prevGroups) => {
|
||||
const currentGroups = prevGroups[selectedChartId.id] || [];
|
||||
const group = currentGroups.find((g) => g.id === groupId);
|
||||
|
||||
if (group && group.children.length < 7) {
|
||||
const newChild = { id: Date.now(), easing: "Linear" };
|
||||
return {
|
||||
...prevGroups,
|
||||
[selectedChartId.id]: currentGroups.map((g) =>
|
||||
g.id === groupId ? { ...g, children: [...g.children, newChild] } : g
|
||||
),
|
||||
};
|
||||
}
|
||||
return prevGroups;
|
||||
});
|
||||
};
|
||||
|
||||
// Remove a child from a group
|
||||
const removeChild = (groupId: number, childId: number) => {
|
||||
setChartDataGroups((currentGroups) => {
|
||||
const currentChartData = currentGroups[selectedChartId.id] || [];
|
||||
|
||||
return {
|
||||
...currentGroups,
|
||||
[selectedChartId.id]: currentChartData.map((group) =>
|
||||
group.id === groupId
|
||||
? {
|
||||
...group,
|
||||
children: group.children.filter(
|
||||
(child) => child.id !== childId
|
||||
),
|
||||
}
|
||||
: group
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dataSideBar">
|
||||
{selectedChartId?.title && (
|
||||
<div className="sideBarHeader">{selectedChartId?.title}</div>
|
||||
)}
|
||||
{/* <MultiLevelDropDown data={DATA_STRUCTURE} /> */}
|
||||
<div className="infoBox">
|
||||
<span className="infoIcon">i</span>
|
||||
<p>
|
||||
<em>
|
||||
By adding templates and widgets, you create a customizable and
|
||||
dynamic environment.
|
||||
</em>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Data;
|
||||
@@ -0,0 +1,199 @@
|
||||
import React, { useState } from "react";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
|
||||
// Define Props Interface
|
||||
interface Widget {
|
||||
id: string;
|
||||
type: string; // Chart type (e.g., "bar", "line")
|
||||
panel: "top" | "bottom" | "left" | "right"; // Panel location
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: string;
|
||||
data: {
|
||||
labels: string[];
|
||||
datasets: {
|
||||
data: number[];
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
borderWidth: number;
|
||||
}[];
|
||||
}; // Data for the chart
|
||||
}
|
||||
|
||||
const Design = () => {
|
||||
const [selectedName, setSelectedName] = useState("drop down");
|
||||
const [selectedElement, setSelectedElement] = useState("drop down");
|
||||
const [selectedFont, setSelectedFont] = useState("drop down");
|
||||
const [selectedSize, setSelectedSize] = useState("drop down");
|
||||
const [selectedWeight, setSelectedWeight] = useState("drop down");
|
||||
const [elementColor, setElementColor] = useState("#6f42c1"); // Default color for elements
|
||||
const [showColorPicker, setShowColorPicker] = useState(false); // Manage visibility of the color picker
|
||||
|
||||
// Zustand Store Hooks
|
||||
const { selectedChartId, setSelectedChartId, widgets, setWidgets } =
|
||||
useWidgetStore();
|
||||
|
||||
// Handle Widget Updates
|
||||
const handleUpdateWidget = (updatedProperties: Partial<Widget>) => {
|
||||
if (!selectedChartId) return;
|
||||
|
||||
// Update the selectedChartId
|
||||
const updatedChartId = {
|
||||
...selectedChartId,
|
||||
...updatedProperties,
|
||||
};
|
||||
setSelectedChartId(updatedChartId);
|
||||
|
||||
// Update the widgets array
|
||||
const updatedWidgets = widgets.map((widget) =>
|
||||
widget.id === selectedChartId.id
|
||||
? { ...widget, ...updatedProperties }
|
||||
: widget
|
||||
);
|
||||
setWidgets(updatedWidgets);
|
||||
};
|
||||
|
||||
// Default Chart Data
|
||||
const defaultChartData = {
|
||||
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"],
|
||||
datasets: [
|
||||
{
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: elementColor, // Default background color
|
||||
borderColor: "#ffffff", // Default border color
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="design">
|
||||
{/* Title of the Selected Widget */}
|
||||
<div className="selectedWidget">
|
||||
{selectedChartId?.title || "Widget 1"}
|
||||
</div>
|
||||
|
||||
{/* Chart Component */}
|
||||
<div className="reviewChart">
|
||||
{selectedChartId && (
|
||||
<ChartComponent
|
||||
type={selectedChartId.type}
|
||||
title={selectedChartId.title}
|
||||
data={selectedChartId.data || defaultChartData} // Use widget data or default
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Options Container */}
|
||||
<div className="optionsContainer">
|
||||
{/* Name Dropdown */}
|
||||
<div className="option">
|
||||
<span>Name</span>
|
||||
<RegularDropDown
|
||||
header={selectedChartId?.title || "Select Name"}
|
||||
options={["Option 1", "Option 2", "Option 3"]}
|
||||
onSelect={(value) => {
|
||||
setSelectedName(value);
|
||||
handleUpdateWidget({ title: value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Element Dropdown */}
|
||||
<div className="option">
|
||||
<span>Element</span>
|
||||
<RegularDropDown
|
||||
header={selectedChartId?.type || "Select Element"}
|
||||
options={["bar", "line", "pie", "doughnut", "radar", "polarArea"]} // Valid chart types
|
||||
onSelect={(value) => {
|
||||
setSelectedElement(value);
|
||||
handleUpdateWidget({ type: value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Font Family Dropdown */}
|
||||
<div className="option">
|
||||
<span>Font Family</span>
|
||||
<RegularDropDown
|
||||
header={selectedChartId?.fontFamily || "Select Font"}
|
||||
options={["Arial", "Roboto", "Sans-serif"]}
|
||||
onSelect={(value) => {
|
||||
setSelectedFont(value);
|
||||
handleUpdateWidget({ fontFamily: value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Size Dropdown */}
|
||||
<div className="option">
|
||||
<span>Size</span>
|
||||
<RegularDropDown
|
||||
header={selectedChartId?.fontSize || "Select Size"}
|
||||
options={["12px", "14px", "16px", "18px"]}
|
||||
onSelect={(value) => {
|
||||
setSelectedSize(value);
|
||||
handleUpdateWidget({ fontSize: value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Weight Dropdown */}
|
||||
<div className="option">
|
||||
<span>Weight</span>
|
||||
<RegularDropDown
|
||||
header={selectedChartId?.fontWeight || "Select Weight"}
|
||||
options={["Light", "Regular", "Bold"]}
|
||||
onSelect={(value) => {
|
||||
setSelectedWeight(value);
|
||||
handleUpdateWidget({ fontWeight: value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Element Color Picker */}
|
||||
<div className="option">
|
||||
<div
|
||||
className="header"
|
||||
onClick={() => setShowColorPicker((prev) => !prev)}
|
||||
>
|
||||
<span>Element Color</span>
|
||||
<div className="icon">▾</div>{" "}
|
||||
{/* Change icon based on the visibility */}
|
||||
</div>
|
||||
|
||||
{/* Show color picker only when 'showColorPicker' is true */}
|
||||
{showColorPicker && (
|
||||
<div className="colorDisplayer">
|
||||
<input
|
||||
type="color"
|
||||
value={elementColor}
|
||||
onChange={(e) => {
|
||||
setElementColor(e.target.value);
|
||||
handleUpdateWidget({
|
||||
data: {
|
||||
labels: selectedChartId?.data?.labels || [],
|
||||
datasets: [
|
||||
{
|
||||
...selectedChartId?.data?.datasets[0],
|
||||
backgroundColor: e.target.value, // Update the element color
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/* Display the selected color value */}
|
||||
<span style={{ marginLeft: "10px" }}>{elementColor}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Design;
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import useModuleStore from "../../store/useModuleStore";
|
||||
import {
|
||||
BuilderIcon,
|
||||
CartIcon,
|
||||
SimulationIcon,
|
||||
VisualizationIcon,
|
||||
} from "../icons/ExportModuleIcons";
|
||||
@@ -40,6 +41,17 @@ const ModuleToggle: React.FC = () => {
|
||||
</div>
|
||||
<div className="module">Visualization</div>
|
||||
</div>
|
||||
<div
|
||||
className={`module-list ${
|
||||
activeModule === "market" && "active"
|
||||
}`}
|
||||
onClick={() => setActiveModule("market")}
|
||||
>
|
||||
<div className="icon">
|
||||
<CartIcon isActive={activeModule === "market"} />
|
||||
</div>
|
||||
<div className="module">Market Place</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,243 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
AsileIcon,
|
||||
CommentIcon,
|
||||
CursorIcon,
|
||||
FloorIcon,
|
||||
FreeMoveIcon,
|
||||
PenIcon,
|
||||
PlayIcon,
|
||||
SaveTemplateIcon,
|
||||
WallIcon,
|
||||
ZoneIcon,
|
||||
} from "../icons/ExportToolsIcons";
|
||||
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
|
||||
import useModuleStore from "../../store/useModuleStore";
|
||||
import { handleSaveTemplate } from "../../modules/visualization/handleSaveTemplate";
|
||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||
import useTemplateStore from "../../store/useTemplateStore";
|
||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||
|
||||
const Tools: React.FC = () => {
|
||||
const [activeTool, setActiveTool] = useState("cursor");
|
||||
const [activeSubTool, setActiveSubTool] = useState("cursor");
|
||||
const [toggleThreeD, setToggleThreeD] = useState(true);
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [openDrop, setOpenDrop] = useState(false);
|
||||
|
||||
const { activeModule } = useModuleStore();
|
||||
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||
const { addTemplate } = useTemplateStore();
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
|
||||
// Reset activeTool whenever activeModule changes
|
||||
useEffect(() => {
|
||||
setActiveTool(activeSubTool);
|
||||
setActiveSubTool(activeSubTool);
|
||||
}, [activeModule]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOutsideClick = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpenDrop(false); // Close the dropdown
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleOutsideClick);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleOutsideClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>Tools</div>
|
||||
<div className="tools-container">
|
||||
<div className="drop-down-icons">
|
||||
<div className="activeDropicon">
|
||||
{activeSubTool == "cursor" && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "cursor" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("cursor");
|
||||
}}
|
||||
>
|
||||
<CursorIcon isActive={activeTool === "cursor"} />
|
||||
</div>
|
||||
)}
|
||||
{activeSubTool == "free-hand" && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "free-hand" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("free-hand");
|
||||
}}
|
||||
>
|
||||
<FreeMoveIcon isActive={activeTool === "free-hand"} />
|
||||
</div>
|
||||
)}
|
||||
{activeModule !== "visualization" && (
|
||||
<div
|
||||
className="drop-down-option-button"
|
||||
ref={dropdownRef}
|
||||
onClick={() => {
|
||||
setOpenDrop(!openDrop);
|
||||
console.log(openDrop);
|
||||
}}
|
||||
>
|
||||
<ArrowIcon />
|
||||
{openDrop && (
|
||||
<div className="drop-down-container">
|
||||
<div
|
||||
className="option-list"
|
||||
onClick={() => {
|
||||
setOpenDrop(false);
|
||||
setActiveTool("cursor");
|
||||
setActiveSubTool("cursor");
|
||||
}}
|
||||
>
|
||||
<div className="active-option">
|
||||
{activeSubTool === "cursor" && <TickIcon />}
|
||||
</div>
|
||||
<CursorIcon isActive={false} />
|
||||
<div className="option">Cursor</div>
|
||||
</div>
|
||||
<div
|
||||
className="option-list"
|
||||
onClick={() => {
|
||||
setOpenDrop(false);
|
||||
setActiveTool("free-hand");
|
||||
setActiveSubTool("free-hand");
|
||||
}}
|
||||
>
|
||||
<div className="active-option">
|
||||
{activeSubTool === "free-hand" && <TickIcon />}
|
||||
</div>
|
||||
<FreeMoveIcon isActive={false} />
|
||||
<div className="option">Free Hand</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!toggleThreeD && activeModule === "builder" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-wall" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-wall");
|
||||
}}
|
||||
>
|
||||
<WallIcon isActive={activeTool === "draw-wall"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-zone" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-zone");
|
||||
}}
|
||||
>
|
||||
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-aisle" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-aisle");
|
||||
}}
|
||||
>
|
||||
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-floor" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-floor");
|
||||
}}
|
||||
>
|
||||
<FloorIcon isActive={activeTool === "draw-floor"} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button ${activeTool === "pen" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setActiveTool("pen");
|
||||
}}
|
||||
>
|
||||
<PenIcon isActive={activeTool === "pen"} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeModule === "visualization" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button`}
|
||||
onClick={() => handleSaveTemplate({ addTemplate, selectedZone })}
|
||||
>
|
||||
<SaveTemplateIcon isActive={false} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="split"></div>
|
||||
<div className="general-options">
|
||||
<div
|
||||
className={`tool-button ${activeTool === "comment" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setActiveTool("comment");
|
||||
}}
|
||||
>
|
||||
<CommentIcon isActive={activeTool === "comment"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${activeTool === "play" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setActiveTool("play");
|
||||
setIsPlaying(!isPlaying);
|
||||
}}
|
||||
>
|
||||
<PlayIcon isActive={activeTool === "play"} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
<div
|
||||
className={`toggle-threed-button${toggleThreeD ? " toggled" : ""}`}
|
||||
onClick={() => {
|
||||
setToggleThreeD(!toggleThreeD);
|
||||
}}
|
||||
>
|
||||
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>
|
||||
2d
|
||||
</div>
|
||||
<div className={`toggle-option${toggleThreeD ? " active" : ""}`}>
|
||||
3d
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
186
app/src/components/ui/componets/AddButtons.tsx
Normal file
186
app/src/components/ui/componets/AddButtons.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import React from "react";
|
||||
import {
|
||||
CleanPannel,
|
||||
EyeIcon,
|
||||
LockIcon,
|
||||
} from "../../icons/RealTimeVisulationIcons";
|
||||
|
||||
// Define the type for `Side`
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
// Define the type for the props passed to the Buttons component
|
||||
interface ButtonsProps {
|
||||
selectedZone: {
|
||||
zoneName: string; // Add zoneName property
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
};
|
||||
setSelectedZone: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
zoneName: string; // Ensure zoneName is included in the state type
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
const AddButtons: React.FC<ButtonsProps> = ({
|
||||
selectedZone,
|
||||
setSelectedZone,
|
||||
}) => {
|
||||
// Function to toggle lock/unlock a panel
|
||||
const toggleLockPanel = (side: Side) => {
|
||||
const newLockedPanels = selectedZone.lockedPanels.includes(side)
|
||||
? selectedZone.lockedPanels.filter((panel) => panel !== side)
|
||||
: [...selectedZone.lockedPanels, side];
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
lockedPanels: newLockedPanels,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
};
|
||||
|
||||
// Function to toggle visibility of a panel
|
||||
const toggleVisibility = (side: Side) => {
|
||||
const newActiveSides = selectedZone.activeSides.includes(side)
|
||||
? selectedZone.activeSides.filter((s) => s !== side)
|
||||
: [...selectedZone.activeSides, side];
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
activeSides: newActiveSides,
|
||||
panelOrder: newActiveSides,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
};
|
||||
|
||||
// Function to clean all widgets from a panel
|
||||
const cleanPanel = (side: Side) => {
|
||||
const cleanedWidgets = selectedZone.widgets.filter(
|
||||
(widget) => widget.panel !== side
|
||||
);
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
widgets: cleanedWidgets,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
};
|
||||
|
||||
// Function to handle "+" button click
|
||||
const handlePlusButtonClick = (side: Side) => {
|
||||
if (selectedZone.activeSides.includes(side)) {
|
||||
// If the panel is already active, remove all widgets and close the panel
|
||||
const cleanedWidgets = selectedZone.widgets.filter(
|
||||
(widget) => widget.panel !== side
|
||||
);
|
||||
const newActiveSides = selectedZone.activeSides.filter((s) => s !== side);
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
widgets: cleanedWidgets,
|
||||
activeSides: newActiveSides,
|
||||
panelOrder: newActiveSides,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
} else {
|
||||
// If the panel is not active, activate it
|
||||
const newActiveSides = [...selectedZone.activeSides, side];
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
activeSides: newActiveSides,
|
||||
panelOrder: newActiveSides,
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
setSelectedZone(updatedZone);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(["top", "right", "bottom", "left"] as Side[]).map((side) => (
|
||||
<div key={side} className={`side-button-container ${side}`}>
|
||||
{/* "+" Button */}
|
||||
<button
|
||||
className={`side-button ${side}`}
|
||||
onClick={() => handlePlusButtonClick(side)}
|
||||
title={
|
||||
selectedZone.activeSides.includes(side)
|
||||
? `Remove all items and close ${side} panel`
|
||||
: `Activate ${side} panel`
|
||||
}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
|
||||
{/* Extra Buttons */}
|
||||
{selectedZone.activeSides.includes(side) && (
|
||||
<div className="extra-Bs">
|
||||
{/* Hide Panel */}
|
||||
<div
|
||||
className="icon"
|
||||
title="Hide Panel"
|
||||
onClick={() => toggleVisibility(side)}
|
||||
>
|
||||
<EyeIcon />
|
||||
</div>
|
||||
|
||||
{/* Clean Panel */}
|
||||
<div
|
||||
className="icon"
|
||||
title="Clean Panel"
|
||||
onClick={() => cleanPanel(side)}
|
||||
>
|
||||
<CleanPannel />
|
||||
</div>
|
||||
|
||||
{/* Lock/Unlock Panel */}
|
||||
<div
|
||||
className={`icon ${
|
||||
selectedZone.lockedPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
title={
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "Unlock Panel"
|
||||
: "Lock Panel"
|
||||
}
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<LockIcon />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddButtons;
|
||||
217
app/src/components/ui/componets/DraggableWidget.tsx
Normal file
217
app/src/components/ui/componets/DraggableWidget.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import ChartComponent from "../../layout/sidebarLeft/visualization/widgets/ChartComponent";
|
||||
import { useWidgetStore } from "../../../store/useWidgetStore";
|
||||
|
||||
export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
const { selectedChartId, setSelectedChartId } = useWidgetStore();
|
||||
|
||||
// State for managing the popup visibility and customization options
|
||||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||||
const [customizationOptions, setCustomizationOptions] = useState({
|
||||
templateBackground: "#ffffff",
|
||||
cardBackground: "#ffffff",
|
||||
cardOpacity: 1,
|
||||
cardBlur: 0,
|
||||
font: "Arial",
|
||||
margin: 0,
|
||||
radius: 5,
|
||||
shadow: "Low",
|
||||
});
|
||||
|
||||
// Handle pointer down to select the chart
|
||||
const handlePointerDown = () => {
|
||||
if (selectedChartId?.id !== widget.id) {
|
||||
setSelectedChartId(widget);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle double-click to open the popup
|
||||
const handleDoubleClick = () => {
|
||||
setIsPopupOpen(true);
|
||||
};
|
||||
|
||||
// Close the popup
|
||||
const handleClosePopup = () => {
|
||||
setIsPopupOpen(false);
|
||||
};
|
||||
|
||||
// Save the changes made in the popup
|
||||
const handleSaveChanges = () => {
|
||||
// Here you can save the customizationOptions to your store or API
|
||||
console.log("Saved Customization Options:", customizationOptions);
|
||||
setIsPopupOpen(false);
|
||||
};
|
||||
|
||||
// Compute dynamic card styles based on customizationOptions
|
||||
const cardStyle = useMemo(() => {
|
||||
const shadowLevels = {
|
||||
Low: "0px 2px 4px rgba(0, 0, 0, 0.1)",
|
||||
Medium: "0px 4px 8px rgba(0, 0, 0, 0.2)",
|
||||
High: "0px 8px 16px rgba(0, 0, 0, 0.3)",
|
||||
};
|
||||
|
||||
return {
|
||||
backgroundColor: customizationOptions.cardBackground,
|
||||
opacity: customizationOptions.cardOpacity,
|
||||
filter: `blur(${customizationOptions.cardBlur}px)`,
|
||||
fontFamily: customizationOptions.font,
|
||||
margin: `${customizationOptions.margin}px`,
|
||||
borderRadius: `${customizationOptions.radius}px`,
|
||||
// boxShadow: shadowLevels[customizationOptions.shadow],
|
||||
};
|
||||
}, [customizationOptions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={widget.id}
|
||||
className={`chart-container ${selectedChartId?.id === widget.id && "activeChart"}`}
|
||||
style={cardStyle} // Apply dynamic card styles here
|
||||
onPointerDown={handlePointerDown}
|
||||
onDoubleClick={handleDoubleClick} // Add double-click event
|
||||
>
|
||||
{widget.type === "progress" ? (
|
||||
// <ProgressCard title={widget.title} data={widget.data} />
|
||||
<></>
|
||||
) : (
|
||||
<ChartComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontFamily={customizationOptions.font} // Pass font customization to ChartComponent
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={widget.data}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Popup for Customizing Template Theme */}
|
||||
{isPopupOpen && (
|
||||
<div className="popup-overlay">
|
||||
<div className="popup-content">
|
||||
<h2>Customize Template Theme</h2>
|
||||
<div className="form-group">
|
||||
<label>Template Background</label>
|
||||
<input
|
||||
type="color"
|
||||
value={customizationOptions.templateBackground}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
templateBackground: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Card Background</label>
|
||||
<input
|
||||
type="color"
|
||||
value={customizationOptions.cardBackground}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
cardBackground: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Card Opacity</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={customizationOptions.cardOpacity}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
cardOpacity: parseFloat(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Card Blur</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="10"
|
||||
value={customizationOptions.cardBlur}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
cardBlur: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Font</label>
|
||||
<select
|
||||
value={customizationOptions.font}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
font: e.target.value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="Times New Roman">Times New Roman</option>
|
||||
<option value="Courier New">Courier New</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Margin</label>
|
||||
<input
|
||||
type="number"
|
||||
value={customizationOptions.margin}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
margin: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Radius</label>
|
||||
<input
|
||||
type="number"
|
||||
value={customizationOptions.radius}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
radius: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Shadow</label>
|
||||
<select
|
||||
value={customizationOptions.shadow}
|
||||
onChange={(e) =>
|
||||
setCustomizationOptions((prev) => ({
|
||||
...prev,
|
||||
shadow: e.target.value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="Low">Low</option>
|
||||
<option value="Medium">Medium</option>
|
||||
<option value="High">High</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="popup-actions">
|
||||
<button onClick={handleClosePopup}>Cancel</button>
|
||||
<button onClick={handleSaveChanges}>Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
197
app/src/components/ui/componets/Panel.tsx
Normal file
197
app/src/components/ui/componets/Panel.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useWidgetStore } from "../../../store/useWidgetStore";
|
||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import { DraggableWidget } from "./DraggableWidget";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface Widget {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}
|
||||
|
||||
interface PanelProps {
|
||||
selectedZone: {
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
};
|
||||
setSelectedZone: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
const generateUniqueId = () =>
|
||||
`${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({});
|
||||
const [panelDimensions, setPanelDimensions] = useState<{
|
||||
[side in Side]?: { width: number; height: number };
|
||||
}>({});
|
||||
|
||||
const getPanelStyle = useMemo(
|
||||
() => (side: Side) => {
|
||||
const currentIndex = selectedZone.panelOrder.indexOf(side);
|
||||
const previousPanels = selectedZone.panelOrder.slice(0, currentIndex);
|
||||
const leftActive = previousPanels.includes("left");
|
||||
const rightActive = previousPanels.includes("right");
|
||||
const topActive = previousPanels.includes("top");
|
||||
const bottomActive = previousPanels.includes("bottom");
|
||||
|
||||
switch (side) {
|
||||
case "top":
|
||||
case "bottom":
|
||||
return {
|
||||
width: `calc(100% - ${
|
||||
(leftActive ? 204 : 0) + (rightActive ? 204 : 0)
|
||||
}px)`,
|
||||
left: leftActive ? "204px" : "0",
|
||||
right: rightActive ? "204px" : "0",
|
||||
[side]: "0",
|
||||
height: "200px",
|
||||
};
|
||||
case "left":
|
||||
case "right":
|
||||
return {
|
||||
height: `calc(100% - ${
|
||||
(topActive ? 204 : 0) + (bottomActive ? 204 : 0)
|
||||
}px)`,
|
||||
top: topActive ? "204px" : "0",
|
||||
bottom: bottomActive ? "204px" : "0",
|
||||
[side]: "0",
|
||||
width: "200px",
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
},
|
||||
[selectedZone.panelOrder]
|
||||
);
|
||||
|
||||
const handleDrop = (e: React.DragEvent, panel: Side) => {
|
||||
e.preventDefault();
|
||||
const { draggedAsset } = useWidgetStore.getState();
|
||||
|
||||
if (draggedAsset) {
|
||||
if (selectedZone.lockedPanels.includes(panel)) return;
|
||||
|
||||
const currentWidgetsInPanel = selectedZone.widgets.filter(
|
||||
(w) => w.panel === panel
|
||||
).length;
|
||||
|
||||
const dimensions = panelDimensions[panel];
|
||||
const CHART_WIDTH = 200;
|
||||
const CHART_HEIGHT = 200;
|
||||
let maxCharts = 0;
|
||||
|
||||
if (dimensions) {
|
||||
if (panel === "top" || panel === "bottom") {
|
||||
maxCharts = Math.floor(dimensions.width / CHART_WIDTH);
|
||||
} else {
|
||||
maxCharts = Math.floor(dimensions.height / CHART_HEIGHT);
|
||||
}
|
||||
} else {
|
||||
maxCharts = panel === "top" || panel === "bottom" ? 5 : 3;
|
||||
}
|
||||
|
||||
if (currentWidgetsInPanel >= maxCharts) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedZone = {
|
||||
...selectedZone,
|
||||
widgets: [
|
||||
...selectedZone.widgets,
|
||||
{
|
||||
...draggedAsset,
|
||||
id: generateUniqueId(),
|
||||
panel,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
setSelectedZone(updatedZone);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const observers: ResizeObserver[] = [];
|
||||
const currentPanelRefs = panelRefs.current;
|
||||
|
||||
selectedZone.activeSides.forEach((side) => {
|
||||
const element = currentPanelRefs[side];
|
||||
if (element) {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setPanelDimensions((prev) => ({
|
||||
...prev,
|
||||
[side]: { width, height },
|
||||
}));
|
||||
}
|
||||
});
|
||||
observer.observe(element);
|
||||
observers.push(observer);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
observers.forEach((observer) => observer.disconnect());
|
||||
};
|
||||
}, [selectedZone.activeSides]);
|
||||
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedZone.activeSides.map((side) => (
|
||||
<div
|
||||
key={side}
|
||||
className={`panel ${side}-panel absolute ${isPlaying && ""}`}
|
||||
style={getPanelStyle(side)}
|
||||
onDrop={(e) => handleDrop(e, side)}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
ref={(el) => {
|
||||
if (el) {
|
||||
panelRefs.current[side] = el;
|
||||
} else {
|
||||
delete panelRefs.current[side];
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="panel-content"
|
||||
style={{
|
||||
pointerEvents: selectedZone.lockedPanels.includes(side)
|
||||
? "none"
|
||||
: "auto",
|
||||
opacity: selectedZone.lockedPanels.includes(side) ? "0.8" : "1",
|
||||
}}
|
||||
>
|
||||
{selectedZone.widgets
|
||||
.filter((w) => w.panel === side)
|
||||
.map((widget) => (
|
||||
<DraggableWidget widget={widget} key={widget.id} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Panel;
|
||||
// only load selected template
|
||||
|
||||
115
app/src/components/ui/componets/RealTimeVisulization.tsx
Normal file
115
app/src/components/ui/componets/RealTimeVisulization.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import Panel from "./Panel";
|
||||
import AddButtons from "./AddButtons";
|
||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface Widget {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}
|
||||
|
||||
const RealTimeVisulization: React.FC = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [zonesData, setZonesData] = useState<{
|
||||
[key: string]: {
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
};
|
||||
}>({
|
||||
"Manufacturing unit": {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
"Assembly unit": {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
"Packing unit": {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
Warehouse: {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
Inventory: {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
});
|
||||
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
|
||||
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
||||
useEffect(() => {
|
||||
setZonesData((prev) => ({
|
||||
...prev,
|
||||
[selectedZone.zoneName]: selectedZone,
|
||||
}));
|
||||
}, [selectedZone]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
id="real-time-vis-canvas"
|
||||
className="realTime-viz canvas"
|
||||
style={{
|
||||
height: isPlaying ? "100vh" : "",
|
||||
width: isPlaying ? "100%" : "",
|
||||
left: isPlaying ? "50%" : "",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`zoon-wrapper ${
|
||||
selectedZone.activeSides.includes("bottom") && "bottom"
|
||||
}`}
|
||||
>
|
||||
{Object.keys(zonesData).map((zoneName, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`zone ${
|
||||
selectedZone.zoneName === zoneName ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedZone({
|
||||
zoneName,
|
||||
...zonesData[zoneName],
|
||||
});
|
||||
}}
|
||||
>
|
||||
{zoneName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!isPlaying && (
|
||||
<AddButtons
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Panel selectedZone={selectedZone} setSelectedZone={setSelectedZone} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RealTimeVisulization;
|
||||
94
app/src/components/ui/inputs/MultiLevelDropDown.tsx
Normal file
94
app/src/components/ui/inputs/MultiLevelDropDown.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
|
||||
// Dropdown Item Component
|
||||
const DropdownItem = ({
|
||||
label,
|
||||
href,
|
||||
onClick,
|
||||
}: {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}) => (
|
||||
<a
|
||||
href={href || "#"}
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick?.();
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
|
||||
// Nested Dropdown Component
|
||||
const NestedDropdown = ({
|
||||
label,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="nested-dropdown">
|
||||
{/* Dropdown Trigger */}
|
||||
<div
|
||||
className="dropdown-trigger"
|
||||
onClick={() => setOpen(!open)} // Toggle submenu on click
|
||||
>
|
||||
{label} <span className="icon">{open ? "▼" : "▶"}</span>
|
||||
</div>
|
||||
|
||||
{/* Submenu */}
|
||||
{open && <div className="submenu">{children}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Recursive Function to Render Nested Data
|
||||
const renderNestedData = (data: Record<string, any>) => {
|
||||
return Object.entries(data).map(([key, value]) => {
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
// If the value is an object, render it as a nested dropdown
|
||||
return (
|
||||
<NestedDropdown key={key} label={key}>
|
||||
{renderNestedData(value)}
|
||||
</NestedDropdown>
|
||||
);
|
||||
} else if (Array.isArray(value)) {
|
||||
// If the value is an array, render each item as a dropdown item
|
||||
return value.map((item, index) => (
|
||||
<DropdownItem key={index} label={item} />
|
||||
));
|
||||
} else {
|
||||
// If the value is a simple string, render it as a dropdown item
|
||||
return <DropdownItem key={key} label={value} />;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Main Multi-Level Dropdown Component
|
||||
const MultiLevelDropdown = ({ data }: { data: Record<string, any> }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="multi-level-dropdown">
|
||||
{/* Dropdown Trigger Button */}
|
||||
<button
|
||||
className="dropdown-button"
|
||||
onClick={() => setOpen(!open)} // Toggle main menu on click
|
||||
>
|
||||
Dropdown trigger <span className="icon">▼</span>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
{open && <div className="dropdown-menu">{renderNestedData(data)}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiLevelDropdown;
|
||||
82
app/src/components/ui/inputs/RegularDropDown.tsx
Normal file
82
app/src/components/ui/inputs/RegularDropDown.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
|
||||
interface DropdownProps {
|
||||
header: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
}
|
||||
|
||||
const RegularDropDown: React.FC<DropdownProps> = ({
|
||||
header,
|
||||
options,
|
||||
onSelect,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null); // Create a ref for the dropdown container
|
||||
|
||||
// Reset selectedOption when the dropdown closes
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedOption(null); // Clear local state when the dropdown closes
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// Reset selectedOption when the header prop changes
|
||||
useEffect(() => {
|
||||
setSelectedOption(null); // Ensure the dropdown reflects the updated header
|
||||
}, [header]);
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false); // Close the dropdown if clicked outside
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
// Cleanup the event listener on component unmount
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleOptionClick = (option: string) => {
|
||||
setSelectedOption(option);
|
||||
onSelect(option); // Call the onSelect function passed from the parent
|
||||
setIsOpen(false); // Close the dropdown after selection
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||
{/* Dropdown Header */}
|
||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||
<div className="key">{selectedOption || header}</div>
|
||||
<div className="icon">▾</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown Options */}
|
||||
{isOpen && (
|
||||
<div className="dropdown-options">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
className="option"
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegularDropDown;
|
||||
2
app/src/functions/generateUniqueId.ts
Normal file
2
app/src/functions/generateUniqueId.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const generateUniqueId = (): string =>
|
||||
`${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
0
app/src/hooks/temp.md
Normal file
0
app/src/hooks/temp.md
Normal file
@@ -2,7 +2,7 @@ import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './styles/main.scss'
|
||||
import App from './App.tsx'
|
||||
|
||||
//Test
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
|
||||
0
app/src/modules/builder/temp.md
Normal file
0
app/src/modules/builder/temp.md
Normal file
0
app/src/modules/simulation/temp.md
Normal file
0
app/src/modules/simulation/temp.md
Normal file
55
app/src/modules/visualization/captureVisualization.ts
Normal file
55
app/src/modules/visualization/captureVisualization.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export const captureVisualization = async (): Promise<string | null> => {
|
||||
const container = document.getElementById("real-time-vis-canvas");
|
||||
if (!container) return null;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return null;
|
||||
|
||||
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
|
||||
const svgs = container.querySelectorAll("svg");
|
||||
for (const svg of Array.from(svgs)) {
|
||||
const svgString = new XMLSerializer().serializeToString(svg);
|
||||
const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
|
||||
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");
|
||||
};
|
||||
36
app/src/modules/visualization/handleSaveTemplate.ts
Normal file
36
app/src/modules/visualization/handleSaveTemplate.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Template } from "../../store/useTemplateStore";
|
||||
import { captureVisualization } from "./captureVisualization";
|
||||
|
||||
type HandleSaveTemplateProps = {
|
||||
addTemplate: (template: Template) => void;
|
||||
selectedZone: {
|
||||
panelOrder: string[]; // Adjust the type based on actual data structure
|
||||
widgets: any[]; // Replace `any` with the actual widget type
|
||||
};
|
||||
};
|
||||
|
||||
// Generate a unique ID (placeholder function)
|
||||
const generateUniqueId = (): string => {
|
||||
return Math.random().toString(36).substring(2, 15);
|
||||
};
|
||||
|
||||
// Refactored function
|
||||
export const handleSaveTemplate = async ({
|
||||
addTemplate,
|
||||
selectedZone,
|
||||
}: HandleSaveTemplateProps): Promise<void> => {
|
||||
try {
|
||||
const snapshot = await captureVisualization();
|
||||
const template: Template = {
|
||||
id: generateUniqueId(),
|
||||
name: `Template ${Date.now()}`,
|
||||
panelOrder: selectedZone.panelOrder,
|
||||
widgets: selectedZone.widgets,
|
||||
snapshot,
|
||||
};
|
||||
console.log('template: ', template);
|
||||
addTemplate(template);
|
||||
} catch (error) {
|
||||
console.error('Failed to save template:', error);
|
||||
}
|
||||
};
|
||||
@@ -1,14 +1,21 @@
|
||||
import React from 'react';
|
||||
import ModuleToggle from '../components/ui/ModuleToggle';
|
||||
import SideBarLeft from '../components/layout/sidebarLeft/SideBarLeft';
|
||||
import SideBarRight from '../components/layout/sidebarRight/SideBarRight';
|
||||
import React from "react";
|
||||
import ModuleToggle from "../components/ui/ModuleToggle";
|
||||
import SideBarLeft from "../components/layout/sidebarLeft/SideBarLeft";
|
||||
import SideBarRight from "../components/layout/sidebarRight/SideBarRight";
|
||||
import useModuleStore from "../store/useModuleStore";
|
||||
import RealTimeVisulization from "../components/ui/componets/RealTimeVisulization";
|
||||
import Tools from "../components/ui/Tools";
|
||||
|
||||
const Project: React.FC = () => {
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
return (
|
||||
<div className="project-main">
|
||||
<ModuleToggle />
|
||||
<SideBarLeft />
|
||||
<SideBarRight />
|
||||
{activeModule === "visualization" && <RealTimeVisulization />}
|
||||
<Tools />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
0
app/src/services/temp.md
Normal file
0
app/src/services/temp.md
Normal file
@@ -1,5 +1,5 @@
|
||||
// store/useModuleStore.ts
|
||||
import { create } from 'zustand';
|
||||
import { create } from "zustand";
|
||||
|
||||
interface ModuleStore {
|
||||
activeModule: string;
|
||||
@@ -7,7 +7,7 @@ interface ModuleStore {
|
||||
}
|
||||
|
||||
const useModuleStore = create<ModuleStore>((set) => ({
|
||||
activeModule: 'builder', // Initial state
|
||||
activeModule: "visualization", // Initial state
|
||||
setActiveModule: (module) => set({ activeModule: module }), // Update state
|
||||
}));
|
||||
|
||||
|
||||
11
app/src/store/usePlayButtonStore.ts
Normal file
11
app/src/store/usePlayButtonStore.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
type PlayButtonStore = {
|
||||
isPlaying: boolean; // Updated state name to reflect the play/pause status more clearly
|
||||
setIsPlaying: (value: boolean) => void; // Updated setter function name for clarity
|
||||
};
|
||||
|
||||
export const usePlayButtonStore = create<PlayButtonStore>((set) => ({
|
||||
isPlaying: false, // Default state for play/pause
|
||||
setIsPlaying: (value) => set({ isPlaying: value }), // Update isPlaying state
|
||||
}));
|
||||
39
app/src/store/useTemplateStore.ts
Normal file
39
app/src/store/useTemplateStore.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
// type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
export interface Widget {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export interface Template {
|
||||
id: string;
|
||||
name: string;
|
||||
panelOrder: string[];
|
||||
widgets: Widget[];
|
||||
snapshot?: string | null; // Add an optional image property (base64)
|
||||
}
|
||||
|
||||
interface TemplateStore {
|
||||
templates: Template[];
|
||||
addTemplate: (template: Template) => void;
|
||||
removeTemplate: (id: string) => void;
|
||||
}
|
||||
|
||||
export const useTemplateStore = create<TemplateStore>((set) => ({
|
||||
templates: [],
|
||||
addTemplate: (template) =>
|
||||
set((state) => ({
|
||||
templates: [...state.templates, template],
|
||||
})),
|
||||
removeTemplate: (id) =>
|
||||
set((state) => ({
|
||||
templates: state.templates.filter((t) => t.id !== id),
|
||||
})),
|
||||
}));
|
||||
|
||||
export default useTemplateStore;
|
||||
11
app/src/store/useThemeStore.ts
Normal file
11
app/src/store/useThemeStore.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
interface ThemeState {
|
||||
themeColor: string[]; // This should be an array of strings
|
||||
setThemeColor: (colors: string[]) => void; // This function will accept an array of strings
|
||||
}
|
||||
|
||||
export const useThemeStore = create<ThemeState>((set) => ({
|
||||
themeColor: ["#5c87df", "#EEEEFE", "#969BA7"],
|
||||
setThemeColor: (colors) => set({ themeColor: colors }),
|
||||
}));
|
||||
49
app/src/store/useWidgetStore.ts
Normal file
49
app/src/store/useWidgetStore.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
export interface Widget {
|
||||
id: string;
|
||||
type: string; // Can be chart type or "progress"
|
||||
panel: "top" | "bottom" | "left" | "right";
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: string;
|
||||
data: {
|
||||
// Chart data
|
||||
labels?: string[];
|
||||
datasets?: Array<{
|
||||
data: number[];
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
borderWidth: number;
|
||||
}>;
|
||||
// Progress card data
|
||||
stocks?: Array<{
|
||||
key: string;
|
||||
value: number;
|
||||
description: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
interface WidgetStore {
|
||||
draggedAsset: Widget | null; // The currently dragged widget asset
|
||||
widgets: Widget[]; // List of all widgets
|
||||
selectedChartId: any;
|
||||
setDraggedAsset: (asset: Widget | null) => void; // Setter for draggedAsset
|
||||
addWidget: (widget: Widget) => void; // Add a new widget
|
||||
setWidgets: (widgets: Widget[]) => void; // Replace the entire widgets array
|
||||
setSelectedChartId: (widget: Widget | null) => void; // Set the selected chart/widget
|
||||
}
|
||||
|
||||
// Create the store with Zustand
|
||||
export const useWidgetStore = create<WidgetStore>((set) => ({
|
||||
draggedAsset: null,
|
||||
widgets: [],
|
||||
selectedChartId: null, // Initialize as null, not as an array
|
||||
setDraggedAsset: (asset) => set({ draggedAsset: asset }),
|
||||
addWidget: (widget) =>
|
||||
set((state) => ({ widgets: [...state.widgets, widget] })),
|
||||
setWidgets: (widgets) => set({ widgets }),
|
||||
setSelectedChartId: (widget) => set({ selectedChartId: widget }),
|
||||
}));
|
||||
41
app/src/store/useZoneStore.ts
Normal file
41
app/src/store/useZoneStore.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface Widget {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}
|
||||
|
||||
interface SelectedZoneState {
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
}
|
||||
|
||||
interface SelectedZoneStore {
|
||||
selectedZone: SelectedZoneState;
|
||||
setSelectedZone: (zone: Partial<SelectedZoneState> | ((prev: SelectedZoneState) => SelectedZoneState)) => void;
|
||||
}
|
||||
|
||||
export const useSelectedZoneStore = create<SelectedZoneStore>((set) => ({
|
||||
selectedZone: {
|
||||
zoneName: "Manufacturing unit",
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
setSelectedZone: (zone) =>
|
||||
set((state) => ({
|
||||
selectedZone:
|
||||
typeof zone === "function"
|
||||
? zone(state.selectedZone) // Handle functional updates
|
||||
: { ...state.selectedZone, ...zone }, // Handle partial updates
|
||||
})),
|
||||
}));
|
||||
52
app/src/styles/components/_regularDropDown.scss
Normal file
52
app/src/styles/components/_regularDropDown.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
@use '../abstracts/variables.scss' as *;
|
||||
|
||||
|
||||
.regularDropdown-container {
|
||||
width: 104px;
|
||||
height: 22px;
|
||||
border: 1px solid var(--text-color); // Ensure $border-color is defined
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 0 6px;
|
||||
|
||||
.dropdown-header {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
// padding: 5px;
|
||||
border: 1px solid var(--primary-color);
|
||||
border-radius: 6px;
|
||||
background-color: var(--background-color);
|
||||
|
||||
// .icon {
|
||||
// padding-right: 6px;
|
||||
// }
|
||||
}
|
||||
|
||||
.dropdown-options {
|
||||
position: absolute; // Ensure dropdown options position correctly
|
||||
width: 100%; // Ensure options width matches the header
|
||||
background-color: var(--primary-color); // Optional: Background color
|
||||
border: 1px solid #ccc; // Optional: Border styling
|
||||
border-radius: 4px; // Optional: Border radius
|
||||
z-index: 10; // Ensure dropdown appears above other elements
|
||||
max-height: 200px; // Optional: Limit height
|
||||
overflow-y: auto; // Optional: Enable scrolling if content exceeds height
|
||||
left: 0;
|
||||
top: 104%;
|
||||
.option {
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-color); // Optional: Hover effect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
@use "../abstracts/variables" as *;
|
||||
@use "../abstracts/mixins" as *;
|
||||
|
||||
.tools-container {
|
||||
@include flex-center;
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
padding: 8px;
|
||||
gap: 10px;
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
border-radius: #{$border-radius-large};
|
||||
width: fit-content;
|
||||
transition: width 0.2s;
|
||||
.split {
|
||||
height: 20px;
|
||||
width: 2px;
|
||||
border-radius: 2px;
|
||||
background: var(--highlight-accent-color);
|
||||
}
|
||||
.draw-tools,
|
||||
.general-options,
|
||||
.activeDropicon {
|
||||
@include flex-center;
|
||||
gap: 8px;
|
||||
interpolate-size: allow-keywords;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
animation: expandWidth 0.2s ease-in-out forwards;
|
||||
.tool-button {
|
||||
@include flex-center;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
cursor: pointer;
|
||||
border-radius: #{$border-radius-small};
|
||||
&:hover {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
.active {
|
||||
background-color: var(--accent-color);
|
||||
&:hover {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.activeDropicon {
|
||||
gap: 2px;
|
||||
.drop-down-option-button {
|
||||
@include flex-center;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
border-radius: #{$border-radius-small};
|
||||
position: relative;
|
||||
&:hover {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
.drop-down-container {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
left: 0;
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
padding: 8px;
|
||||
border-radius: #{$border-radius-large};
|
||||
.option-list {
|
||||
margin: 4px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
border-radius: #{$border-radius-medium};
|
||||
gap: 6px;
|
||||
padding: 4px;
|
||||
&:hover {
|
||||
background-color: var(--highlight-accent-color);
|
||||
color: var(--accent-color);
|
||||
path {
|
||||
stroke: var(--accent-color);
|
||||
}
|
||||
}
|
||||
.active-option {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
@include flex-center;
|
||||
}
|
||||
.option {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.toggle-threed-button {
|
||||
@include flex-center;
|
||||
padding: 3px;
|
||||
border-radius: #{$border-radius-small};
|
||||
background-color: var(--highlight-accent-color);
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
.toggle-option {
|
||||
font-size: var(--font-size-small);
|
||||
padding: 2px;
|
||||
z-index: 1;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: var(--accent-color);
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: #{$border-radius-small};
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.active {
|
||||
color: var(--highlight-accent-color);
|
||||
}
|
||||
}
|
||||
.toggled {
|
||||
&::after {
|
||||
left: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes expandWidth {
|
||||
from {
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
width: fit-content;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,31 @@
|
||||
background-color: var(--background-color);
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
|
||||
.header-container {
|
||||
@include flex-space-between;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
|
||||
.header-content {
|
||||
@include flex-center;
|
||||
width: calc(100% - 34px);
|
||||
|
||||
.logo-container {
|
||||
@include flex-center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
padding: 0 8px;
|
||||
width: 100%;
|
||||
max-width: calc(100% - 32px);
|
||||
|
||||
.input-value {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-sidebar-ui-button {
|
||||
@include flex-center;
|
||||
cursor: pointer;
|
||||
@@ -36,31 +42,103 @@
|
||||
min-height: 32px;
|
||||
min-width: 32px;
|
||||
border-radius: #{$border-radius-small};
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: var(--background-color-secondary);
|
||||
outline: 1px solid var(--accent-color);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-left-container {
|
||||
min-height: 50vh;
|
||||
padding-bottom: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.sidebar-left-content-container {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
// flex: 1;
|
||||
height: calc(100% - 36px);
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
|
||||
.widget-left-sideBar {
|
||||
min-height: 50vh;
|
||||
max-height: 60vh;
|
||||
|
||||
.widget2D {
|
||||
.chart-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding-right: 6px;
|
||||
flex-wrap: nowrap;
|
||||
overflow: auto;
|
||||
|
||||
.chart {
|
||||
min-height: 170px;
|
||||
background: var(--background-primary, #fcfdfd);
|
||||
border: 1.23px solid var(--Grays-Gray-5, #e5e5ea);
|
||||
box-shadow: 0px 4.91px 4.91px 0px #0000001c;
|
||||
border-radius: $border-radius-medium;
|
||||
padding: 12px 6px;
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
height: auto !important;
|
||||
padding: 12px 10px 41px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.stock {
|
||||
padding: 13px 5px;
|
||||
background-color: #e0dfff80;
|
||||
border-radius: 6.33px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.stock-item {
|
||||
.stockValues {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: flex-end;
|
||||
gap: 3px;
|
||||
|
||||
.value {
|
||||
color: var(--accent-color);
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.stock-description {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.outline-container {
|
||||
height: 100%;
|
||||
|
||||
.outline-content-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
@@ -79,15 +157,18 @@
|
||||
background-color: var(--background-color);
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
|
||||
.header-container {
|
||||
@include flex-space-between;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
gap: 12px;
|
||||
height: 52px;
|
||||
|
||||
.options-container {
|
||||
@include flex-center;
|
||||
gap: 8px;
|
||||
|
||||
.share-button {
|
||||
padding: 4px 12px;
|
||||
color: var(--primary-color);
|
||||
@@ -96,18 +177,22 @@
|
||||
border-radius: #{$border-radius-medium};
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.app-docker-button {
|
||||
@include flex-center;
|
||||
}
|
||||
}
|
||||
|
||||
.split {
|
||||
height: 20px;
|
||||
width: 2px;
|
||||
background: var(--background-color-secondary);
|
||||
}
|
||||
|
||||
.users-container {
|
||||
width: 100%;
|
||||
@include flex-space-between;
|
||||
|
||||
.user-profile {
|
||||
@include flex-center;
|
||||
height: 26px;
|
||||
@@ -118,8 +203,10 @@
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.guest-users-container {
|
||||
display: flex;
|
||||
|
||||
.other-guest {
|
||||
@include flex-center;
|
||||
height: 26px;
|
||||
@@ -134,23 +221,32 @@
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-profile-container {
|
||||
display: flex;
|
||||
.user-organnization {
|
||||
|
||||
.user-organization {
|
||||
height: 100%;
|
||||
max-width: 52px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
margin-left: 2px;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-actions-container {
|
||||
position: absolute;
|
||||
left: -40px;
|
||||
|
||||
.sidebar-action-list {
|
||||
margin-bottom: 12px;
|
||||
@include flex-center;
|
||||
@@ -160,16 +256,19 @@
|
||||
background: var(--primary-color);
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
}
|
||||
|
||||
.active {
|
||||
background: var(--accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-right-container {
|
||||
min-height: 50vh;
|
||||
padding-bottom: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.sidebar-right-content-container {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
// flex: 1;
|
||||
@@ -178,12 +277,176 @@
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.visualization-right-sideBar {
|
||||
min-height: 50vh;
|
||||
max-height: 60vh;
|
||||
|
||||
.sidebar-left-content-container {
|
||||
.dataSideBar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
|
||||
.sideBarHeader {
|
||||
color: #5c87df;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.selectedMain-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 10px;
|
||||
|
||||
.selectedMain {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
main {
|
||||
width: 35%;
|
||||
white-space: nowrap;
|
||||
/* Prevent wrapping */
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
color: #5273eb;
|
||||
padding: 6px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.bulletPoint {
|
||||
color: #5273eb;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.regularDropdown-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.child {
|
||||
width: 100%;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.infoBox {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
color: #444;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
|
||||
.infoIcon {
|
||||
padding: 0px 7px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.design {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
color: #4a4a4a;
|
||||
|
||||
.selectedWidget {
|
||||
padding: 6px 12px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.reviewChart {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.optionsContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 0 12px;
|
||||
|
||||
.option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
|
||||
.regularDropdown-container {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.colorDisplayer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
|
||||
input[type="color"] {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
width: 24px;
|
||||
height: 26px;
|
||||
border-radius: 3.2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.machine-mechanics-container {
|
||||
.header {
|
||||
@include flex-space-between;
|
||||
padding: 6px 12px;
|
||||
|
||||
.add-button {
|
||||
@include flex-center;
|
||||
padding: 2px 4px;
|
||||
@@ -191,19 +454,23 @@
|
||||
color: var(--primary-color);
|
||||
border-radius: #{$border-radius-small};
|
||||
cursor: pointer;
|
||||
|
||||
path {
|
||||
stroke: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lists-main-container {
|
||||
margin: 2px 8px;
|
||||
width: calc(100% - 16px);
|
||||
background: var(--background-color-secondary);
|
||||
border-radius: #{$border-radius-small};
|
||||
|
||||
.list-container {
|
||||
min-height: 120px;
|
||||
padding: 4px;
|
||||
|
||||
.list-item {
|
||||
@include flex-space-between;
|
||||
padding: 4px 12px;
|
||||
@@ -211,15 +478,19 @@
|
||||
margin: 2px 0;
|
||||
border-radius: #{$border-radius-small};
|
||||
}
|
||||
|
||||
.active {
|
||||
background: var(--accent-color);
|
||||
|
||||
.value {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
path {
|
||||
stroke: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
@include flex-center;
|
||||
height: 12px;
|
||||
@@ -227,18 +498,22 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.resize-icon {
|
||||
@include flex-center;
|
||||
padding: 4px;
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-properties-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@include flex-center;
|
||||
justify-content: flex-start;
|
||||
@@ -247,3 +522,94 @@
|
||||
font-size: var(--font-size-tiny);
|
||||
}
|
||||
}
|
||||
|
||||
/* Base styles */
|
||||
.multi-level-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
|
||||
.dropdown-button {
|
||||
background-color: #3b82f6; /* Blue background */
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #2563eb; /* Darker blue on hover */
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.5rem); /* Add spacing below the button */
|
||||
left: 0;
|
||||
width: 12rem;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb; /* Light gray border */
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dropdown Item */
|
||||
.dropdown-item {
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563; /* Gray text */
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #f3f4f6; /* Light gray background on hover */
|
||||
}
|
||||
}
|
||||
|
||||
/* Nested Dropdown */
|
||||
.nested-dropdown {
|
||||
position: relative;
|
||||
|
||||
.dropdown-trigger {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563; /* Gray text */
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #f3f4f6; /* Light gray background on hover */
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%; /* Position submenu to the right */
|
||||
width: 12rem;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb; /* Light gray border */
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Subtle shadow */
|
||||
z-index: 20;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
@use 'components/moduleToggle';
|
||||
@use 'components/templates';
|
||||
@use 'components/tools';
|
||||
@use 'components/regularDropDown';
|
||||
|
||||
// layout
|
||||
@use 'layout/sidebar';
|
||||
|
||||
// pages
|
||||
@use 'pages/home';
|
||||
@use 'pages/realTimeViz';
|
||||
412
app/src/styles/pages/realTimeViz.scss
Normal file
412
app/src/styles/pages/realTimeViz.scss
Normal file
@@ -0,0 +1,412 @@
|
||||
@use "../abstracts/variables.scss" as *;
|
||||
|
||||
// Main Container
|
||||
.realTime-viz {
|
||||
background-color: var(--background-color);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 4px 8px rgba(60, 60, 67, 0.1019607843);
|
||||
width: calc(100% - (320px + 270px + 80px));
|
||||
height: 600px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: calc(270px + 40px);
|
||||
transform: translate(0, -50%);
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icons-container {
|
||||
.icon {
|
||||
&:first-child {
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.zoon-wrapper {
|
||||
display: flex;
|
||||
background-color: #e0dfff80;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
gap: 6px;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
max-width: 80%;
|
||||
overflow: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.zone {
|
||||
width: auto;
|
||||
background-color: #fcfdfd;
|
||||
border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
white-space: nowrap;
|
||||
font-size: $small;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--background-color);
|
||||
// color: #FCFDFD !important;
|
||||
}
|
||||
}
|
||||
|
||||
.zoon-wrapper.bottom {
|
||||
bottom: 210px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
width: 80%; // Increase width to take more space on smaller screens
|
||||
height: 500px; // Reduce height to fit smaller screens
|
||||
left: 50%; // Center horizontally
|
||||
|
||||
.main-container {
|
||||
margin: 0 15px; // Reduce margin for better spacing
|
||||
}
|
||||
|
||||
.zoon-wrapper {
|
||||
bottom: 5px; // Adjust position for smaller screens
|
||||
|
||||
&.bottom {
|
||||
bottom: 150px; // Adjust for bottom placement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 90%; // Take even more width on very small screens
|
||||
height: 400px; // Further reduce height
|
||||
top: 45%; // Adjust vertical position slightly upward
|
||||
|
||||
.panel {
|
||||
&.top-panel,
|
||||
&.bottom-panel {
|
||||
.panel-content {
|
||||
flex-direction: column; // Stack panels vertically on small screens
|
||||
|
||||
.chart-container {
|
||||
width: 100%; // Make charts full width
|
||||
height: 150px; // Reduce chart height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
width: 95%; // Take almost full width on very small devices
|
||||
height: 350px; // Further reduce height
|
||||
top: 40%; // Move slightly higher for better visibility
|
||||
|
||||
.side-button-container {
|
||||
flex-direction: row !important; // Force buttons into a row
|
||||
gap: 4px; // Reduce spacing between buttons
|
||||
|
||||
&.top,
|
||||
&.bottom {
|
||||
left: 50%; // Center horizontally
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 600px;
|
||||
background-color: rgb(235, 235, 235);
|
||||
margin: 0 30px;
|
||||
transition: height 0.3s ease, margin 0.3s ease;
|
||||
|
||||
.zoon-wrapper {
|
||||
display: flex;
|
||||
background-color: rgba(224, 223, 255, 0.5);
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
gap: 6px;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
max-width: 80%;
|
||||
overflow: auto;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.zone {
|
||||
width: auto;
|
||||
background-color: $background-color;
|
||||
border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
bottom: 210px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
background: white;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 6px;
|
||||
overflow: visible !important;
|
||||
|
||||
.panel-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
max-height: 100%;
|
||||
border: 1px dotted #a9a9a9;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 2px 6px 0px rgba(60, 60, 67, 0.1);
|
||||
padding: 6px 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.top-panel,
|
||||
&.bottom-panel {
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.panel-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.chart-container {
|
||||
height: 100%;
|
||||
width: 230px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.top-panel {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.bottom-panel {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&.left-panel {
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
&.right-panel {
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Side Buttons
|
||||
.side-button-container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
background-color: $background-color;
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
.extra-Bs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.side-button {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
// align-items: center;
|
||||
background-color: var(--accent-color);
|
||||
border: none;
|
||||
color: var(--background-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.top {
|
||||
top: -30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: -30px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
bottom: -30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&.left {
|
||||
left: -30px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.right.side-button-container {
|
||||
.extra-Bs {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.left.side-button-container {
|
||||
.extra-Bs {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
// Theme Container
|
||||
.theme-container {
|
||||
width: 250px;
|
||||
padding: 12px;
|
||||
box-shadow: 1px -3px 4px 0px rgba(0, 0, 0, 0.11);
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: -100%;
|
||||
transform: translate(-0%, 0);
|
||||
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
color: #2b3344;
|
||||
}
|
||||
|
||||
.theme-preset-wrapper {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.theme-preset {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid $border-color;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
transition: border 0.3s ease;
|
||||
|
||||
&.active {
|
||||
border: 1px solid var(--primary-color);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-color {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.color-displayer {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
border: 1px solid var(--accent-color);
|
||||
border-radius: 4px;
|
||||
padding: 0 5px;
|
||||
|
||||
input {
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user