Compare commits
354 Commits
7c2a019dd2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 03d5646565 | |||
| 6fdb259246 | |||
| 0aad43c751 | |||
| d988d47e30 | |||
|
|
e08eaaa43e | ||
|
|
f6ca54f5b4 | ||
|
|
a9b8053bd8 | ||
| 4d8af01316 | |||
| 600c101c68 | |||
| 8f32f29c03 | |||
|
|
14a6884f43 | ||
|
|
8e151ffe0b | ||
| 8f64843613 | |||
|
|
fe8f6d1002 | ||
|
|
5281e288a9 | ||
| 5a7d64a05b | |||
|
|
4ed05cc640 | ||
| e74b802a60 | |||
|
|
14e97fead2 | ||
|
|
0935d7c9f5 | ||
|
|
3266641f81 | ||
|
|
739c55c0bc | ||
|
|
5b4485d20d | ||
| f436865b7f | |||
| 98057beb0f | |||
|
|
b342289888 | ||
| d03bbf2a41 | |||
|
|
ec094b8f55 | ||
| 48437c3c50 | |||
| b6d941ba2d | |||
| 167abcaa43 | |||
| beb8ccbaee | |||
| 797f6c2383 | |||
| a33c9b3b92 | |||
| 016c77a586 | |||
| 36ebab7f9a | |||
| a5b1fbef74 | |||
|
|
cb0c53daea | ||
| 978e020305 | |||
| 4508738958 | |||
| 58d1f5c7ab | |||
| eb23612444 | |||
| c10a5bf62d | |||
| bee196e299 | |||
| d19f555c7c | |||
| 1d7253f9a1 | |||
| d8a1878ab1 | |||
| e4af2d6e18 | |||
| 8cf21643ea | |||
|
|
6b75f6f4b9 | ||
|
|
dc78add71d | ||
| 1ce922ff4c | |||
| 3a4b0f0748 | |||
| 4bffbb3798 | |||
| b9cdfcb1e9 | |||
| 49ba00d886 | |||
| cf1ccb0fd0 | |||
| fb790e5e37 | |||
| ea3adf40cc | |||
| 2ff50342e8 | |||
| 1f8ee62721 | |||
| 96d6367dbd | |||
| 8afff23ffe | |||
| 512282f367 | |||
| 4fab5df300 | |||
| 9e8191f8f8 | |||
| 13255f9713 | |||
| e23215b6c1 | |||
| a014d6b370 | |||
| 3225a0865e | |||
| 5703ff0e8d | |||
| 03be3a6a99 | |||
| fe5f081b92 | |||
| acaf1b3946 | |||
| 147171373c | |||
| f22e120204 | |||
| 1bc98de564 | |||
| 991a3eaa66 | |||
| 7a5a9aafd1 | |||
| 0694497f8d | |||
| c82d6d39c1 | |||
| edf20050db | |||
| 2e98dc168a | |||
| 1797058526 | |||
| 1c2ddca9d4 | |||
| 6cc5042956 | |||
| 61ca7249cd | |||
| 5079f8d316 | |||
| a98edbe658 | |||
| fbc5473f2b | |||
| 7073390de7 | |||
| 55a47c3a25 | |||
| 94e011e5c7 | |||
| db9b40f2fc | |||
| 4226a24e79 | |||
| 5fdfb47f9e | |||
| 55c50f6f7f | |||
| ed4570e8dd | |||
| 572042ab53 | |||
| afcb85a323 | |||
| 14f8a5d472 | |||
| 309d191bce | |||
| 038009433f | |||
| 7e5105392c | |||
| 7e16bf63aa | |||
| 3e384f89b1 | |||
| b05e3fe5d9 | |||
| 1986368c1c | |||
| 1cd9cf765c | |||
| 908788f41d | |||
| 899695f548 | |||
| 7d2b18a94d | |||
| 1eab3fe845 | |||
| c4f290bfcb | |||
| f304a28493 | |||
| 73b5cd6e97 | |||
| 2d0c28bc48 | |||
| 1413d0ef33 | |||
| fde71818e2 | |||
| 85017cd88c | |||
| f1c7ae5e20 | |||
| 5989948bf9 | |||
| 64ba51b17c | |||
| 6a21b65808 | |||
| b5fbf2f745 | |||
| 3b7ba28053 | |||
| 47d0638a42 | |||
| f4caac55e6 | |||
| 34e38b3969 | |||
| 3198b71f7e | |||
| 8405568e85 | |||
| ecf59fa9c6 | |||
| d8c5f3ed44 | |||
| 0e8078c29f | |||
| 1d06963f67 | |||
| affd9146bb | |||
| 8de195d961 | |||
| da9cf0d554 | |||
| 8cf5878d46 | |||
| 7dd38aa50c | |||
| b2bcaa6b5f | |||
| 5822dbbc82 | |||
| de8f0ba2b6 | |||
| 08f8c4708f | |||
| 85db9e0a52 | |||
| 4022b3f8f4 | |||
| 0916ea7103 | |||
| 5952858dca | |||
| 7064ec8587 | |||
| 446a4e2b95 | |||
| d2f9d6aacc | |||
| 7ef6d71be8 | |||
| 83a475c708 | |||
| d955cc7942 | |||
| ab1c510a77 | |||
| 2ac707611f | |||
| 59859c6d18 | |||
| 2bd27937dc | |||
| 1058c660d6 | |||
| 35b2167791 | |||
| ec676983d0 | |||
| c07c5f8235 | |||
| b32ad97034 | |||
| 71f42f4149 | |||
| 76244f6f6e | |||
| 4544e33d01 | |||
| 114ef96de8 | |||
| 0a128cbb3c | |||
| 259ee474aa | |||
| da14ed4e74 | |||
| 3738adf85a | |||
| 6b727c84d8 | |||
| 9a483aa873 | |||
| 1d408ef3c1 | |||
| e8c3f259bf | |||
| 7050d7ca84 | |||
| bd4ab26680 | |||
| fd361f21cf | |||
| 47f7c7b682 | |||
| 3e728a1ff5 | |||
| 39d8be10cc | |||
| 5a8e2dee2f | |||
| 9db143972e | |||
| a86795fdf6 | |||
| a3e5fdd138 | |||
| 2abed31bde | |||
| c3fadb9382 | |||
| 7eabb2c7c8 | |||
| 029ea269a7 | |||
| 50d040953f | |||
| dd874cbe9c | |||
| f2b652abe3 | |||
| b5c1888153 | |||
| cf1ad6d511 | |||
| a3d24a9426 | |||
| 3873ba5285 | |||
| 034cf636f9 | |||
| bddd249e07 | |||
| 1c3f80bc26 | |||
| 9ac2942ace | |||
| 4cdaa042da | |||
| 5a1bd4e16f | |||
| 8c9ef41704 | |||
| 4f518dba9c | |||
| 893852f929 | |||
| 3a057f7ef0 | |||
| 56af2a16c0 | |||
| 1189115359 | |||
| deadf2ffb4 | |||
| 15d836c627 | |||
| 5baaf14bd9 | |||
| 873434ff84 | |||
| e9d047fdf3 | |||
| 4079466deb | |||
| 98e5ed250c | |||
| f52d61da62 | |||
| 6e256e3c42 | |||
| 784ffc5e87 | |||
| cb98d91577 | |||
| 988dcda0e2 | |||
| 4da80c7089 | |||
| 85afb9d332 | |||
| dca0a37774 | |||
| 9091392dfb | |||
| 5ec26ecbe8 | |||
| 4bd0348a2a | |||
| 55213480c9 | |||
| cf063822eb | |||
| 94d395fe49 | |||
| cc12455564 | |||
| f7f11907dc | |||
| fb3e500139 | |||
| 6d45b5bf11 | |||
| 6a9bbb2b5e | |||
| d2c755c03d | |||
| 56e3ce78a6 | |||
| 4a9b6c9d01 | |||
| 6be90b6ea9 | |||
| 2df7c953c7 | |||
| e42d1fa3ce | |||
| 15b3339dcb | |||
| c7c5a33613 | |||
| f049902d2c | |||
| 2bf83619c7 | |||
| fcf1deaa26 | |||
| 6348c1e2b4 | |||
| 2f621fc6c2 | |||
| 956730135e | |||
| 7538c18624 | |||
| 61ec188d59 | |||
| 1ba83ec105 | |||
| 9f46908d79 | |||
| 9bb07b1224 | |||
| c0f7b8eeb4 | |||
| a4f7eaf422 | |||
| 172e14e77d | |||
| 77a89489cd | |||
| 23db974695 | |||
| 1bde2a0fd0 | |||
| 4e2da7d4fa | |||
| 05be0b6738 | |||
| 2d2b1a6b0c | |||
| aa68c6690e | |||
| 7b56f9690d | |||
| 9fc1c7cb40 | |||
| fc1ce7281e | |||
| 9f6cb66c37 | |||
| eb90d89e0e | |||
| 251ad44371 | |||
| e13d7eb3be | |||
| 973713286f | |||
| 5ed5ee26bf | |||
| bfe38d5955 | |||
| d9fb7c9fce | |||
| 577d8c8585 | |||
| 54290baaac | |||
| af6c6de301 | |||
| 5e728a6ff3 | |||
| be17c43499 | |||
| d0bf6782d5 | |||
| 337598085a | |||
| 6381235e14 | |||
| c5f0c73ae1 | |||
| d7a09840b9 | |||
| e00ecbf116 | |||
| 2817f3c31c | |||
| 7a8a46ee64 | |||
| c3b5ec2121 | |||
| 59c90c3519 | |||
| 76e40ced3f | |||
| 5f6c156c12 | |||
| c312577d50 | |||
| a3666dbf41 | |||
| 49dda8621b | |||
| bf03891142 | |||
| 3ce9b3772d | |||
| 27c901d08f | |||
| a6e8c39ed8 | |||
| 7d10c3f5b2 | |||
| 76cf2de6ed | |||
| 823492a381 | |||
| 25e31c58bd | |||
| f1fe0e0bc4 | |||
| c6957b46c6 | |||
| a7af974108 | |||
| dcdd8c9b8d | |||
| 406b306275 | |||
| 6807be41b6 | |||
| 7a9cf46e39 | |||
| 0a2c23fa9c | |||
| e13539618b | |||
| 8ef1bdb142 | |||
| 930ee1708a | |||
| 2fddc7f098 | |||
| 1613ee52d3 | |||
| 148bca0e55 | |||
| 7b78755ee1 | |||
| 678dd12a18 | |||
| bc46328832 | |||
| 9806593319 | |||
| dcedf9fe19 | |||
| 2f678cef03 | |||
| 32d88df400 | |||
| f21174e5d0 | |||
| 676fb64554 | |||
| 0beb46e318 | |||
| b2e1bca4ab | |||
| 317610e552 | |||
| 0d97a1978e | |||
| d469992ee2 | |||
| e3b7792f9b | |||
| 600e6c8593 | |||
| fbf8231050 | |||
| 50aa323b5f | |||
| 3484683074 | |||
| 1c0ac6930e | |||
| 1687f3b952 | |||
| 992dcc47ea | |||
| 2d5a1b00ea | |||
| 3f39923070 | |||
| 3b51f59679 | |||
| 2778c96143 | |||
| fb5528616c | |||
| 6a5b01bf0c | |||
| d362c041ac | |||
| ef67cdd91c | |||
| 6b4b511d66 | |||
| 9dabbd60ea | |||
| ba0b145bda | |||
| b73abf2fa2 | |||
| 6de6d35a6b | |||
| b9b5704232 | |||
| 42fff789e3 | |||
| c64b7b3490 |
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
VITE_API_SERVER=http://localhost:9530/api
|
||||||
|
# VITE_API_SERVER=https://117.102.231.130:9528/api
|
||||||
|
VITE_MQTT_SERVER=ws://localhost:1884
|
||||||
|
VITE_MQTT_USERNAME=
|
||||||
|
VITE_MQTT_PASSWORD=
|
||||||
|
VITE_KEY_SESSION=PetekRombonganPetekMorekMorakMarek
|
||||||
1
.gitignore
vendored
@@ -6,6 +6,7 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
*.config
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
|||||||
@@ -22,13 +22,16 @@
|
|||||||
"exceljs": "^4.4.0",
|
"exceljs": "^4.4.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jspdf": "^3.0.1",
|
"jspdf": "^3.0.4",
|
||||||
|
"jspdf-autotable": "^5.0.2",
|
||||||
"mqtt": "^5.14.0",
|
"mqtt": "^5.14.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
"react-router-dom": "^6.22.3",
|
"react-router-dom": "^6.22.3",
|
||||||
|
"react-svg": "^16.3.0",
|
||||||
|
"recharts": "^3.6.0",
|
||||||
"sweetalert2": "^11.17.2"
|
"sweetalert2": "^11.17.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
BIN
public/assets/defaultSparepartImg.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/assets/pupuk-indonesia-1.png
Normal file
|
After Width: | Height: | Size: 309 KiB |
BIN
public/assets/pupuk-indonesia-2.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
@@ -3,7 +3,7 @@
|
|||||||
<system.webServer>
|
<system.webServer>
|
||||||
<rewrite>
|
<rewrite>
|
||||||
<rules>
|
<rules>
|
||||||
<rule name="reactViteSypiu">
|
<rule name="CallOfDuty">
|
||||||
<match url=".*" />
|
<match url=".*" />
|
||||||
<conditions logicalGrouping="MatchAll">
|
<conditions logicalGrouping="MatchAll">
|
||||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/* Global Card Styling */
|
||||||
|
.ant-card {
|
||||||
|
border-radius: 12px !important;
|
||||||
|
box-shadow: 0 5px 10px 5px rgba(0, 0, 0, 0.07) !important;
|
||||||
|
margin-bottom: 24px !important;
|
||||||
|
}
|
||||||
|
|||||||
154
src/App.jsx
@@ -1,51 +1,155 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import SignIn from './pages/auth/SignIn';
|
import SignIn from './pages/auth/SignIn';
|
||||||
|
import SignUp from './pages/auth/Signup';
|
||||||
import { ProtectedRoute } from './ProtectedRoute';
|
import { ProtectedRoute } from './ProtectedRoute';
|
||||||
import NotFound from './pages/blank/NotFound';
|
import NotFound from './pages/blank/NotFound';
|
||||||
import { getSessionData } from './components/Global/Formatter';
|
|
||||||
|
|
||||||
// dashboard
|
// Dashboard
|
||||||
import Home from './pages/home/Home';
|
import Home from './pages/home/Home';
|
||||||
import Blank from './pages/blank/Blank';
|
import Blank from './pages/blank/Blank';
|
||||||
|
|
||||||
// master
|
// Master
|
||||||
|
import IndexPlantSubSection from './pages/master/plantSubSection/IndexPlantSubSection';
|
||||||
|
import IndexBrandDevice from './pages/master/brandDevice/IndexBrandDevice';
|
||||||
import IndexDevice from './pages/master/device/IndexDevice';
|
import IndexDevice from './pages/master/device/IndexDevice';
|
||||||
|
import IndexUnit from './pages/master/unit/IndexUnit';
|
||||||
|
import IndexTag from './pages/master/tag/IndexTag';
|
||||||
|
import IndexStatus from './pages/master/status/IndexStatus';
|
||||||
|
import IndexSparepart from './pages/master/sparepart/IndexSparepart';
|
||||||
|
import IndexShift from './pages/master/shift/IndexShift';
|
||||||
|
// Brand device
|
||||||
|
import AddBrandDevice from './pages/master/brandDevice/AddBrandDevice';
|
||||||
|
import EditBrandDevice from './pages/master/brandDevice/EditBrandDevice';
|
||||||
|
import ViewBrandDevice from './pages/master/brandDevice/ViewBrandDevice';
|
||||||
|
import ViewFilePage from './pages/master/brandDevice/ViewFilePage';
|
||||||
|
|
||||||
// Setting
|
// Jadwal Shift
|
||||||
|
import IndexJadwalShift from './pages/jadwalShift/IndexJadwalShift';
|
||||||
|
|
||||||
|
// History
|
||||||
|
import IndexTrending from './pages/report/trending/IndexTrending';
|
||||||
|
import IndexReport from './pages/report/report/IndexReport';
|
||||||
|
|
||||||
|
// Other Pages
|
||||||
|
import IndexNotification from './pages/notification/IndexNotification';
|
||||||
|
import IndexRole from './pages/role/IndexRole';
|
||||||
|
import IndexUser from './pages/user/IndexUser';
|
||||||
|
import IndexContact from './pages/contact/IndexContact';
|
||||||
|
import DetailNotificationTab from './pages/notificationDetail/IndexNotificationDetail';
|
||||||
|
import IndexVerificationSparepart from './pages/verificationSparepart/IndexVerificationSparepart';
|
||||||
|
|
||||||
|
import SvgTest from './pages/home/SvgTest';
|
||||||
|
import SvgOverviewCompressor from './pages/home/SvgOverviewCompressor';
|
||||||
|
import SvgCompressorA from './pages/home/SvgCompressorA';
|
||||||
|
import SvgCompressorB from './pages/home/SvgCompressorB';
|
||||||
|
import SvgCompressorC from './pages/home/SvgCompressorC';
|
||||||
|
import SvgOverviewAirDryer from './pages/home/SvgOverviewAirDryer';
|
||||||
|
import SvgAirDryerA from './pages/home/SvgAirDryerA';
|
||||||
|
import SvgAirDryerB from './pages/home/SvgAirDryerB';
|
||||||
|
import SvgAirDryerC from './pages/home/SvgAirDryerC';
|
||||||
|
import IndexHistoryAlarm from './pages/history/alarm/IndexHistoryAlarm';
|
||||||
|
import IndexHistoryEvent from './pages/history/event/IndexHistoryEvent';
|
||||||
|
|
||||||
|
// Image Viewer
|
||||||
|
import ImageViewer from './Utils/ImageViewer';
|
||||||
|
import RedirectWa from './pages/blank/RedirectWa';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const session = getSessionData();
|
|
||||||
// console.log(session);
|
|
||||||
|
|
||||||
const isAdmin =
|
|
||||||
session?.user?.role_id != `${import.meta.env.VITE_ROLE_VENDOR}` &&
|
|
||||||
session?.user?.role_id &&
|
|
||||||
session?.user?.role_id != null &&
|
|
||||||
session?.user?.role_id != 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter
|
<BrowserRouter>
|
||||||
future={{
|
|
||||||
v7_startTransition: true,
|
|
||||||
v7_relativeSplatPath: true,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Routes>
|
<Routes>
|
||||||
{isAdmin ? (
|
{/* Public Routes */}
|
||||||
<Route path="/" element={<Navigate to="/dashboard/home" />} />
|
<Route path="/" element={<Navigate to="/signin" replace />} />
|
||||||
) : (
|
|
||||||
<Route path="/" element={<Navigate to="/dashboard/home-vendor" />} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Route path="/signin" element={<SignIn />} />
|
<Route path="/signin" element={<SignIn />} />
|
||||||
|
<Route path="/signup" element={<SignUp />} />
|
||||||
|
<Route path="/svg" element={<SvgTest />} />
|
||||||
|
<Route
|
||||||
|
path="/notification-detail/:notificationId"
|
||||||
|
element={<DetailNotificationTab />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/verification-sparepart/:notificationId"
|
||||||
|
element={<IndexVerificationSparepart />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route path="/redirect" element={<RedirectWa />} />
|
||||||
|
|
||||||
|
{/* Protected Routes */}
|
||||||
<Route path="/dashboard" element={<ProtectedRoute />}>
|
<Route path="/dashboard" element={<ProtectedRoute />}>
|
||||||
<Route path="home" element={<Home />} />
|
<Route path="home" element={<Home />} />
|
||||||
<Route path="blank" element={<Blank />} />
|
<Route path="blank" element={<Blank />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/image-viewer/:fileName" element={<ImageViewer />} />
|
||||||
|
|
||||||
|
<Route path="/dashboard-svg" element={<ProtectedRoute />}>
|
||||||
|
<Route path="overview-compressor" element={<SvgOverviewCompressor />} />
|
||||||
|
<Route path="compressor-a" element={<SvgCompressorA />} />
|
||||||
|
<Route path="compressor-b" element={<SvgCompressorB />} />
|
||||||
|
<Route path="compressor-c" element={<SvgCompressorC />} />
|
||||||
|
<Route path="overview-airdryer" element={<SvgOverviewAirDryer />} />
|
||||||
|
<Route path="airdryer-a" element={<SvgAirDryerA />} />
|
||||||
|
<Route path="airdryer-b" element={<SvgAirDryerB />} />
|
||||||
|
<Route path="airdryer-c" element={<SvgAirDryerC />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path="/master" element={<ProtectedRoute />}>
|
<Route path="/master" element={<ProtectedRoute />}>
|
||||||
<Route path="device" element={<IndexDevice />} />
|
<Route path="device" element={<IndexDevice />} />
|
||||||
|
<Route path="tag" element={<IndexTag />} />
|
||||||
|
<Route path="unit" element={<IndexUnit />} />
|
||||||
|
<Route path="sparepart" element={<IndexSparepart />} />
|
||||||
|
<Route path="plant-sub-section" element={<IndexPlantSubSection />} />
|
||||||
|
<Route path="shift" element={<IndexShift />} />
|
||||||
|
<Route path="status" element={<IndexStatus />} />
|
||||||
|
|
||||||
|
{/* Brand Device Routes */}
|
||||||
|
<Route path="brand-device" element={<IndexBrandDevice />} />
|
||||||
|
<Route path="brand-device/add" element={<AddBrandDevice />} />
|
||||||
|
<Route path="brand-device/edit/:id" element={<EditBrandDevice />} />
|
||||||
|
<Route path="brand-device/view/:id" element={<ViewBrandDevice />} />
|
||||||
|
<Route
|
||||||
|
path="brand-device/edit/:id/files/:fileType/:fileName"
|
||||||
|
element={<ViewFilePage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="brand-device/view/:id/files/:fileType/:fileName"
|
||||||
|
element={<ViewFilePage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="brand-device/view/temp/files/:fileName"
|
||||||
|
element={<ViewFilePage />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/report" element={<ProtectedRoute />}>
|
||||||
|
<Route path="trending" element={<IndexTrending />} />
|
||||||
|
<Route path="report" element={<IndexReport />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/history" element={<ProtectedRoute />}>
|
||||||
|
<Route path="alarm" element={<IndexHistoryAlarm />} />
|
||||||
|
<Route path="event" element={<IndexHistoryEvent />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/notification" element={<ProtectedRoute />}>
|
||||||
|
<Route index element={<IndexNotification />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/role" element={<ProtectedRoute />}>
|
||||||
|
<Route index element={<IndexRole />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/user" element={<ProtectedRoute />}>
|
||||||
|
<Route index element={<IndexUser />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/contact" element={<ProtectedRoute />}>
|
||||||
|
<Route index element={<IndexContact />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/jadwal-shift" element={<ProtectedRoute />}>
|
||||||
|
<Route index element={<IndexJadwalShift />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Navigate, Outlet } from 'react-router-dom';
|
import { Navigate, Outlet } from 'react-router-dom';
|
||||||
import MainLayout from './layout/MainLayout';
|
import MainLayout from './layout/MainLayout';
|
||||||
|
import { NotifAlert } from './components/Global/ToastNotif';
|
||||||
import { getSessionData } from './components/Global/Formatter';
|
|
||||||
|
|
||||||
export const ProtectedRoute = () => {
|
export const ProtectedRoute = () => {
|
||||||
const session = getSessionData();
|
// cek token di localStorage
|
||||||
// console.log(session);
|
const token = localStorage.getItem('token');
|
||||||
|
const isAuthenticated = !!token;
|
||||||
|
|
||||||
const isAuthenticated = session?.auth ?? false;
|
if (!isAuthenticated) {
|
||||||
if (!isAuthenticated) {
|
NotifAlert({
|
||||||
return <Navigate to="/signin" replace />;
|
icon: 'warning',
|
||||||
}
|
title: 'Session Habis',
|
||||||
return (
|
message: 'Silahkan login terlebih dahulu',
|
||||||
<MainLayout>
|
});
|
||||||
<Outlet />
|
return <Navigate to="/signin" replace />;
|
||||||
</MainLayout>
|
}
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<MainLayout>
|
||||||
|
<Outlet />
|
||||||
|
</MainLayout>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
const handleLogOut = () => {
|
const handleLogOut = (navigate) => {
|
||||||
localStorage.removeItem('Auth');
|
// Hapus semua data localStorage
|
||||||
localStorage.removeItem('session');
|
localStorage.clear();
|
||||||
window.location.replace('/signin');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default handleLogOut;
|
// Redirect ke halaman signin
|
||||||
|
if (navigate) {
|
||||||
|
navigate('/signin', { replace: true });
|
||||||
|
} else {
|
||||||
|
window.location.replace('/signin');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handleLogOut;
|
||||||
|
|||||||
@@ -8,18 +8,14 @@ const handleSignIn = async (values) => {
|
|||||||
|
|
||||||
// return false
|
// return false
|
||||||
if (response?.status == 200) {
|
if (response?.status == 200) {
|
||||||
/* you can change this according to your authentication protocol */
|
|
||||||
let token = JSON.stringify(response.data?.token);
|
let token = JSON.stringify(response.data?.token);
|
||||||
let role = JSON.stringify(response.data?.user?.role_id);
|
|
||||||
|
|
||||||
localStorage.setItem('token', token);
|
localStorage.setItem('token', token);
|
||||||
response.data.auth = true;
|
response.data.auth = true;
|
||||||
localStorage.setItem('session', encryptData(response?.data));
|
localStorage.setItem('session', encryptData(response?.data));
|
||||||
if (role === `${import.meta.env.VITE_ROLE_VENDOR}`) {
|
|
||||||
window.location.replace('/dashboard/home-vendor');
|
// langsung redirect ke dashboard utama
|
||||||
} else {
|
window.location.replace('/dashboard/home');
|
||||||
window.location.replace('/dashboard/home');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
NotifAlert({
|
NotifAlert({
|
||||||
icon: 'error',
|
icon: 'error',
|
||||||
|
|||||||
248
src/Utils/ImageViewer.jsx
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { getFileUrl, getFolderFromFileType } from '../api/file-uploads';
|
||||||
|
|
||||||
|
const ImageViewer = () => {
|
||||||
|
const { fileName } = useParams();
|
||||||
|
const [fileUrl, setFileUrl] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [zoom, setZoom] = useState(1);
|
||||||
|
const [isImage, setIsImage] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!fileName) {
|
||||||
|
setError('No file specified');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decodedFileName = decodeURIComponent(fileName);
|
||||||
|
const fileExtension = decodedFileName.split('.').pop()?.toLowerCase();
|
||||||
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
||||||
|
|
||||||
|
setIsImage(imageExtensions.includes(fileExtension));
|
||||||
|
|
||||||
|
const folder = getFolderFromFileType(fileExtension);
|
||||||
|
|
||||||
|
const url = getFileUrl(folder, decodedFileName);
|
||||||
|
setFileUrl(url);
|
||||||
|
|
||||||
|
document.title = `File Viewer - ${decodedFileName}`;
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
setError('Failed to load file');
|
||||||
|
}
|
||||||
|
}, [fileName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (!isImage) return;
|
||||||
|
|
||||||
|
if (e.key === '+' || e.key === '=') {
|
||||||
|
setZoom(prev => Math.min(prev + 0.1, 3));
|
||||||
|
} else if (e.key === '-' || e.key === '_') {
|
||||||
|
setZoom(prev => Math.max(prev - 0.1, 0.1));
|
||||||
|
} else if (e.key === '0') {
|
||||||
|
setZoom(1);
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [isImage]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleWheel = (e) => {
|
||||||
|
if (!isImage || !e.ctrlKey) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
||||||
|
setZoom(prev => Math.min(Math.max(prev + delta, 0.1), 3));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleZoomIn = () => setZoom(prev => Math.min(prev + 0.1, 3));
|
||||||
|
const handleZoomOut = () => setZoom(prev => Math.max(prev - 0.1, 0.1));
|
||||||
|
const handleResetZoom = () => setZoom(1);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
fontFamily: 'Arial, sans-serif',
|
||||||
|
backgroundColor: '#f5f5f5'
|
||||||
|
}}>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<h1>Error</h1>
|
||||||
|
<p>{error}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!isImage) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
fontFamily: 'Arial, sans-serif',
|
||||||
|
backgroundColor: '#f5f5f5'
|
||||||
|
}}>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<h1>File Type Not Supported</h1>
|
||||||
|
<p>Image viewer only supports image files.</p>
|
||||||
|
<p>Please use direct file preview for PDFs and other documents.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
height: '100vh',
|
||||||
|
width: '100vw',
|
||||||
|
backgroundColor: '#000',
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'relative'
|
||||||
|
}}
|
||||||
|
onWheel={handleWheel}
|
||||||
|
>
|
||||||
|
|
||||||
|
{isImage && (
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: '20px',
|
||||||
|
right: '20px',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '10px',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
padding: '10px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
zIndex: 1000
|
||||||
|
}}>
|
||||||
|
<button
|
||||||
|
onClick={handleZoomOut}
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
color: '#fff',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '16px'
|
||||||
|
}}
|
||||||
|
title="Zoom Out (-)"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
<span style={{
|
||||||
|
color: '#fff',
|
||||||
|
padding: '8px 12px',
|
||||||
|
minWidth: '60px',
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
{Math.round(zoom * 100)}%
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={handleZoomIn}
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
color: '#fff',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '16px'
|
||||||
|
}}
|
||||||
|
title="Zoom In (+)"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleResetZoom}
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||||
|
color: '#fff',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}
|
||||||
|
title="Reset Zoom (0)"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{isImage && fileUrl ? (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto'
|
||||||
|
}}>
|
||||||
|
<img
|
||||||
|
src={fileUrl}
|
||||||
|
alt={decodeURIComponent(fileName)}
|
||||||
|
style={{
|
||||||
|
maxWidth: 'none',
|
||||||
|
maxHeight: 'none',
|
||||||
|
transform: `scale(${zoom})`,
|
||||||
|
transformOrigin: 'center',
|
||||||
|
transition: 'transform 0.1s ease-out',
|
||||||
|
cursor: zoom > 1 ? 'move' : 'default'
|
||||||
|
}}
|
||||||
|
onError={() => setError('Failed to load image')}
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : isImage ? (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
color: '#fff',
|
||||||
|
fontFamily: 'Arial, sans-serif'
|
||||||
|
}}>
|
||||||
|
<p>Loading image...</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
|
{isImage && (
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '20px',
|
||||||
|
left: '20px',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
color: '#fff',
|
||||||
|
padding: '10px 15px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontSize: '12px',
|
||||||
|
zIndex: 1000
|
||||||
|
}}>
|
||||||
|
<div>Mouse wheel + Ctrl: Zoom</div>
|
||||||
|
<div>Keyboard: +/− Zoom, 0: Reset, ESC: Close</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageViewer;
|
||||||
71
src/Utils/validate.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// utils/validate.js
|
||||||
|
|
||||||
|
// Daftar aturan validasi
|
||||||
|
const validationRules = [
|
||||||
|
{ field: 'name', label: 'Nama', required: true },
|
||||||
|
{
|
||||||
|
field: 'email',
|
||||||
|
label: 'Email',
|
||||||
|
required: true,
|
||||||
|
pattern: /\S+@\S+\.\S+/,
|
||||||
|
patternMessage: 'Format email tidak valid',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'age',
|
||||||
|
label: 'Umur',
|
||||||
|
required: true,
|
||||||
|
validator: (v) => !isNaN(v) && Number(v) >= 18,
|
||||||
|
message: 'Umur harus angka dan minimal 18 tahun',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fungsi validasi dinamis berbasis array objek aturan.
|
||||||
|
* @param {Object} data - data form (misal { name: '', email: '' })
|
||||||
|
* @param {Array} rules - array aturan validasi
|
||||||
|
* @returns {Object} errors - object berisi pesan error per field
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const validateRun = (data, rules, onError) => {
|
||||||
|
const errors = {};
|
||||||
|
const messages = [];
|
||||||
|
|
||||||
|
const ipRegex = /^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$/;
|
||||||
|
|
||||||
|
rules.forEach((rule) => {
|
||||||
|
const value = data[rule.field]?.toString().trim();
|
||||||
|
const fieldErrors = [];
|
||||||
|
|
||||||
|
if (rule.required && !value) {
|
||||||
|
fieldErrors.push(`${rule.label} wajib diisi`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ IP Address check
|
||||||
|
if (rule.ip && value && !ipRegex.test(value)) {
|
||||||
|
fieldErrors.push(`${rule.label} harus berupa alamat IP yang valid`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.pattern && value && !rule.pattern.test(value)) {
|
||||||
|
fieldErrors.push(rule.patternMessage || `${rule.label} tidak valid`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.validator && value && !rule.validator(value)) {
|
||||||
|
fieldErrors.push(rule.message || `${rule.label} tidak valid`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gabungkan error satu field jadi satu string (pisah baris)
|
||||||
|
if (fieldErrors.length > 0) {
|
||||||
|
errors[rule.field] = fieldErrors.join("\n");
|
||||||
|
messages.push(...fieldErrors);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Jika ada error total, tampilkan callback dan return false
|
||||||
|
|
||||||
|
if (messages.length > 0) {
|
||||||
|
if (onError) onError(messages.join("\n"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
|
};
|
||||||
@@ -10,20 +10,10 @@ const login = async (params) => {
|
|||||||
return response || [];
|
return response || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFile = async (formData) => {
|
|
||||||
const response = await RegistrationRequest({
|
|
||||||
method: 'post',
|
|
||||||
prefix: 'file-upload',
|
|
||||||
params: formData,
|
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
|
||||||
});
|
|
||||||
return response || {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const register = async (params) => {
|
const register = async (params) => {
|
||||||
const response = await RegistrationRequest({
|
const response = await RegistrationRequest({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
prefix: 'register',
|
prefix: 'auth/register',
|
||||||
params: params,
|
params: params,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
@@ -49,4 +39,4 @@ const checkUsername = async (queryParams) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export { login, uploadFile, register, verifyRedirect, checkUsername };
|
export { login, register, verifyRedirect, checkUsername };
|
||||||
|
|||||||
56
src/api/contact.jsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllContact = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `contact?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getContactById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `contact/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createContact = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `contact`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateContact = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `contact/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteContact = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `contact/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAllContact,
|
||||||
|
getContactById,
|
||||||
|
createContact,
|
||||||
|
updateContact,
|
||||||
|
deleteContact,
|
||||||
|
};
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { SendRequest } from '../components/Global/ApiRequest';
|
|
||||||
|
|
||||||
const getTotal = async (query = '') => {
|
|
||||||
const prefix = `${query ? `?${query}` : ''}`;
|
|
||||||
const fullUrl = `${import.meta.env.VITE_API_SERVER}/dashboard/${prefix}`;
|
|
||||||
try {
|
|
||||||
const response = await SendRequest({
|
|
||||||
method: 'get',
|
|
||||||
prefix: `dashboard/${prefix}`,
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[API Call] Failed: GET ${fullUrl}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTotalPermit = async (query = '') => {
|
|
||||||
const prefix = `dashboard/permit-total${query ? `?${query}` : ''}`;
|
|
||||||
const fullUrl = `${import.meta.env.VITE_API_SERVER}/${prefix}`;
|
|
||||||
try {
|
|
||||||
const response = await SendRequest({
|
|
||||||
method: 'get',
|
|
||||||
prefix,
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[API Call] Failed: GET ${fullUrl}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTotalPermitPerYear = async (query = '') => {
|
|
||||||
const prefix = `dashboard/permit-breakdown${query ? `?${query}` : ''}`;
|
|
||||||
const fullUrl = `${import.meta.env.VITE_API_SERVER}/${prefix}`;
|
|
||||||
try {
|
|
||||||
const response = await SendRequest({
|
|
||||||
method: 'get',
|
|
||||||
prefix,
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[API Call] Failed: GET ${fullUrl}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { getTotal, getTotalPermit, getTotalPermitPerYear };
|
|
||||||
135
src/api/file-uploads.jsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_BASE_URL = import.meta.env.VITE_API_SERVER;
|
||||||
|
|
||||||
|
// Get file from uploads directory
|
||||||
|
const getFile = async (folder, filename) => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('No authentication token found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(`${API_BASE_URL}/file-uploads/${folder}/${encodeURIComponent(filename)}`, {
|
||||||
|
responseType: 'blob',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token.replace(/"/g, '')}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Download file as blob with proper handling
|
||||||
|
const downloadFile = async (folder, filename) => {
|
||||||
|
try {
|
||||||
|
const response = await getFile(folder, filename);
|
||||||
|
|
||||||
|
const blob = new Blob([response], {
|
||||||
|
type: 'application/octet-stream'
|
||||||
|
});
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
return { success: true, filename };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading file:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get file info (metadata)
|
||||||
|
const getFileInfo = async (folder, filename) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'head',
|
||||||
|
prefix: `file-uploads/${folder}/${encodeURIComponent(filename)}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
throw new Error(response.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
contentType: response.headers?.['content-type'],
|
||||||
|
contentLength: response.headers?.['content-length'],
|
||||||
|
lastModified: response.headers?.['last-modified'],
|
||||||
|
filename: filename,
|
||||||
|
folder: folder
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get file URL for iframe
|
||||||
|
const getFileUrl = (folder, filename) => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
return `${API_BASE_URL}/file-uploads/${folder}/${encodeURIComponent(filename)}?token=${encodeURIComponent(token)}`;
|
||||||
|
}
|
||||||
|
return `${API_BASE_URL}/file-uploads/${folder}/${encodeURIComponent(filename)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
const checkFileExists = async (folder, filename) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'head',
|
||||||
|
prefix: `file-uploads/${folder}/${encodeURIComponent(filename)}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.error && response.statusCode === 404) {
|
||||||
|
return false;
|
||||||
|
} else if (response.error) {
|
||||||
|
throw new Error(response.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileType = (filename) => {
|
||||||
|
const ext = filename.split('.').pop().toLowerCase();
|
||||||
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
||||||
|
const pdfExtensions = ['pdf'];
|
||||||
|
|
||||||
|
if (imageExtensions.includes(ext)) {
|
||||||
|
return 'image';
|
||||||
|
} else if (pdfExtensions.includes(ext)) {
|
||||||
|
return 'pdf';
|
||||||
|
}
|
||||||
|
return 'unknown';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Upload file to server
|
||||||
|
const uploadFile = async (file, folder) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('folder', folder);
|
||||||
|
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: 'file-uploads',
|
||||||
|
params: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFolderFromFileType = (fileType) => {
|
||||||
|
return fileType === 'pdf' ? 'pdf' : 'images';
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getFile,
|
||||||
|
downloadFile,
|
||||||
|
getFileInfo,
|
||||||
|
getFileUrl,
|
||||||
|
checkFileExists,
|
||||||
|
getFileType,
|
||||||
|
getFolderFromFileType,
|
||||||
|
uploadFile
|
||||||
|
};
|
||||||
54
src/api/history-value.jsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllHistoryAlarm = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `history/alarm?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllHistoryEvent = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `history/event?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllHistoryValueReport = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `history/value-report?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllHistoryValueReportPivot = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `history/value-report-pivot?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllHistoryValueTrendingPivot = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `history/value-trending?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAllHistoryAlarm,
|
||||||
|
getAllHistoryEvent,
|
||||||
|
getAllHistoryValueReport,
|
||||||
|
getAllHistoryValueReportPivot,
|
||||||
|
getAllHistoryValueTrendingPivot,
|
||||||
|
};
|
||||||
55
src/api/jadwal-shift.jsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllJadwalShift = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `user-schedule?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getJadwalShiftById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `user-schedule/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createJadwalShift = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `user-schedule`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateJadwalShift = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `user-schedule/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteJadwalShift = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `user-schedule/${id}`,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAllJadwalShift,
|
||||||
|
getJadwalShiftById,
|
||||||
|
createJadwalShift,
|
||||||
|
updateJadwalShift,
|
||||||
|
deleteJadwalShift,
|
||||||
|
};
|
||||||
109
src/api/master-brand.jsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllBrands = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `brand?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBrandById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `brand/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBrand = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `brand`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBrand = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `brand/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteBrand = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `brand/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getErrorCodesByBrandId = async (brandId, queryParams) => {
|
||||||
|
const query = queryParams ? `?${queryParams.toString()}` : '';
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `error-code/brand/${brandId}${query}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getErrorCodeById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `error-code/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createErrorCode = async (brandId, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `error-code/brand/${brandId}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateErrorCode = async (brandId, errorCodeId, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `error-code/brand/${brandId}/${errorCodeId}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteErrorCode = async (brandId, errorCode) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `error-code/brand/${brandId}/${errorCode}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAllBrands,
|
||||||
|
getBrandById,
|
||||||
|
createBrand,
|
||||||
|
updateBrand,
|
||||||
|
deleteBrand,
|
||||||
|
getErrorCodesByBrandId,
|
||||||
|
getErrorCodeById,
|
||||||
|
createErrorCode,
|
||||||
|
updateErrorCode,
|
||||||
|
deleteErrorCode
|
||||||
|
};
|
||||||
50
src/api/master-device.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllDevice = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `device?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDeviceById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `device/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDevice = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `device`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDevice = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `device/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDevice = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `device/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getAllDevice, getDeviceById, createDevice, updateDevice, deleteDevice };
|
||||||
56
src/api/master-plant-section.jsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllPlantSection = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `plant-sub-section?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPlantSectionById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `plant-sub-section/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPlantSection = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `plant-sub-section`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePlantSection = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `plant-sub-section/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePlantSection = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `plant-sub-section/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAllPlantSection,
|
||||||
|
getPlantSectionById,
|
||||||
|
createPlantSection,
|
||||||
|
updatePlantSection,
|
||||||
|
deletePlantSection,
|
||||||
|
};
|
||||||
@@ -1,45 +1,50 @@
|
|||||||
import { SendRequest } from '../components/Global/ApiRequest';
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
const getAllApd = async (queryParams) => {
|
const getAllShift = async (queryParams) => {
|
||||||
const response = await SendRequest({
|
const response = await SendRequest({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
prefix: `apd?${queryParams.toString()}`,
|
prefix: `shift?${queryParams.toString()}`,
|
||||||
});
|
});
|
||||||
return response;
|
|
||||||
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createApd = async (queryParams) => {
|
const getShiftById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `shift/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createShift = async (queryParams) => {
|
||||||
const response = await SendRequest({
|
const response = await SendRequest({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
prefix: `apd`,
|
prefix: `shift`,
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateApd = async (vendor_id, queryParams) => {
|
const updateShift = async (id, queryParams) => {
|
||||||
const response = await SendRequest({
|
const response = await SendRequest({
|
||||||
method: 'put',
|
method: 'put',
|
||||||
prefix: `apd/${vendor_id}`,
|
prefix: `shift/${id}`,
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteApd = async (queryParams) => {
|
const deleteShift = async (id) => {
|
||||||
const response = await SendRequest({
|
const response = await SendRequest({
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
prefix: `apd/${queryParams}`,
|
prefix: `shift/${id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getJenisPermit = async () => {
|
export { getAllShift, getShiftById, createShift, updateShift, deleteShift };
|
||||||
const response = await SendRequest({
|
|
||||||
method: 'get',
|
|
||||||
prefix: `apd/jenis-permit`,
|
|
||||||
});
|
|
||||||
return response.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { getAllApd, createApd, updateApd, deleteApd, getJenisPermit };
|
|
||||||
50
src/api/master-status.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllStatuss = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `status?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `status/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createStatus = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `status`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStatus = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `status/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteStatus = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `status/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getAllStatuss, getStatusById, createStatus, updateStatus, deleteStatus };
|
||||||
50
src/api/master-tag.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllTag = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `tags?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTagById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `tags/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createTag = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `tags`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTag = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `tags/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTag = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `tags/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getAllTag, getTagById, createTag, updateTag, deleteTag };
|
||||||
50
src/api/master-unit.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllUnit = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `unit?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUnitById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `unit/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUnit = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `unit`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUnit = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `unit/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUnit = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `unit/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getAllUnit, getUnitById, createUnit, updateUnit, deleteUnit };
|
||||||
95
src/api/notification.jsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllNotification = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `notification?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNotificationById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `notification/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNotificationDetail = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `notification/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new notification log
|
||||||
|
const createNotificationLog = async (data) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: 'notification-log',
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get notification logs by notification_error_id
|
||||||
|
const getNotificationLogByNotificationId = async (notificationId) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `notification-log/notification_error/${notificationId}`,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// update is_read status
|
||||||
|
const updateIsRead = async (notificationId) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `notification/${notificationId}`,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resend notification to specific user
|
||||||
|
const resendNotificationToUser = async (notificationId, userId) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `notification/${notificationId}/resend/${userId}`,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resend Chat by User
|
||||||
|
const resendChatByUser = async (notificationId, userPhone) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `notification-user/resend/${notificationId}/${userPhone}`,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resend Chat All User
|
||||||
|
const resendChatAllUser = async (notificationId) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `notification/resend/${notificationId}`,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAllNotification,
|
||||||
|
getNotificationById,
|
||||||
|
getNotificationDetail,
|
||||||
|
createNotificationLog,
|
||||||
|
getNotificationLogByNotificationId,
|
||||||
|
updateIsRead,
|
||||||
|
resendNotificationToUser,
|
||||||
|
resendChatByUser,
|
||||||
|
resendChatAllUser,
|
||||||
|
};
|
||||||
50
src/api/role.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllRole = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `roles?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoleById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `roles/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRole = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `roles`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateRole = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `roles/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRole = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `roles/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getAllRole, getRoleById, createRole, updateRole, deleteRole };
|
||||||
50
src/api/sparepart.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllSparepart = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `sparepart?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSparepartById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `sparepart/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSparepart = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `sparepart`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSparepart = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `sparepart/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSparepart = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `sparepart/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getAllSparepart, getSparepartById, createSparepart, updateSparepart, deleteSparepart };
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
// user-admin.jsx
|
|
||||||
import axios from 'axios';
|
|
||||||
import { SendRequest } from '../components/Global/ApiRequest';
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
const baseURL = import.meta.env.VITE_API_SERVER;
|
|
||||||
|
|
||||||
const getAllUser = async (queryParams) => {
|
const getAllUser = async (queryParams) => {
|
||||||
const response = await SendRequest({
|
const response = await SendRequest({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
prefix: `admin-user?${queryParams.toString()}`,
|
prefix: `admin-user?${queryParams.toString()}`,
|
||||||
});
|
});
|
||||||
return response;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserDetail = async (id) => {
|
const getUserDetail = async (id) => {
|
||||||
@@ -17,7 +13,7 @@ const getUserDetail = async (id) => {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
prefix: `admin-user/${id}`,
|
prefix: `admin-user/${id}`,
|
||||||
});
|
});
|
||||||
return response;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateUser = async (id, data) => {
|
const updateUser = async (id, data) => {
|
||||||
@@ -26,7 +22,7 @@ const updateUser = async (id, data) => {
|
|||||||
prefix: `admin-user/${id}`,
|
prefix: `admin-user/${id}`,
|
||||||
params: data,
|
params: data,
|
||||||
});
|
});
|
||||||
return response;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUser = async (id) => {
|
const deleteUser = async (id) => {
|
||||||
@@ -34,7 +30,7 @@ const deleteUser = async (id) => {
|
|||||||
method: 'delete',
|
method: 'delete',
|
||||||
prefix: `admin-user/${id}`,
|
prefix: `admin-user/${id}`,
|
||||||
});
|
});
|
||||||
return response;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const approvalUser = async (id, queryParams) => {
|
const approvalUser = async (id, queryParams) => {
|
||||||
@@ -43,42 +39,7 @@ const approvalUser = async (id, queryParams) => {
|
|||||||
prefix: `admin-user/approve/${id}`,
|
prefix: `admin-user/approve/${id}`,
|
||||||
params: queryParams,
|
params: queryParams,
|
||||||
});
|
});
|
||||||
return response;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFile = async (formData) => {
|
export { getAllUser, getUserDetail, updateUser, deleteUser, approvalUser };
|
||||||
try {
|
|
||||||
const token = localStorage.getItem('token')?.replace(/"/g, '') || '';
|
|
||||||
const url = `${baseURL}/file-upload`;
|
|
||||||
|
|
||||||
const response = await axios.post(url, formData, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
'Accept-Language': 'en_US',
|
|
||||||
'Content-Type': 'multipart/form-data',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.data?.statusCode ?? 0,
|
|
||||||
message: response.data?.message ?? '',
|
|
||||||
data: response.data?.data ?? {},
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ ERROR di uploadFile:', error);
|
|
||||||
return {
|
|
||||||
statusCode: error?.response?.status || 500,
|
|
||||||
message: error?.response?.data?.message || 'Upload gagal',
|
|
||||||
data: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export { getAllUser, getUserDetail, updateUser, deleteUser, approvalUser, uploadFile };
|
|
||||||
|
|||||||
102
src/api/user.jsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { SendRequest } from '../components/Global/ApiRequest';
|
||||||
|
|
||||||
|
const getAllUser = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `user?${queryParams.toString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserById = async (id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `user/${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUser = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `user`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUser = async (user_id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `user/${user_id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUser = async (queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'delete',
|
||||||
|
prefix: `user/${queryParams}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const approveUser = async (user_id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `user/${user_id}/approve`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectUser = async (user_id) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `user/${user_id}/reject`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleActiveUser = async (user_id, is_active) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `user/${user_id}`,
|
||||||
|
params: {
|
||||||
|
is_active: is_active,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changePassword = async (user_id, new_password) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'put',
|
||||||
|
prefix: `user/change-password/${user_id}`,
|
||||||
|
params: {
|
||||||
|
new_password: new_password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAllUser,
|
||||||
|
getUserById,
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
deleteUser,
|
||||||
|
approveUser,
|
||||||
|
rejectUser,
|
||||||
|
toggleActiveUser,
|
||||||
|
changePassword,
|
||||||
|
};
|
||||||
BIN
src/assets/bg-cod-1.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/bg_cod.jpg
Normal file
|
After Width: | Height: | Size: 640 KiB |
261
src/assets/svg/air_dryer_A_rev.svg
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 500" xmlns:bx="https://boxy-svg.com">
|
||||||
|
<defs>
|
||||||
|
<bx:grid x="0" y="0" width="25" height="25"/>
|
||||||
|
</defs>
|
||||||
|
<rect y="10.407" width="972.648" height="440.159" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" x="12.119"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, 74.03907, 53.375034)">
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||||
|
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, 500.726135, 53.375034)">
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||||
|
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||||
|
</g>
|
||||||
|
<rect x="371.728" y="182.483" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-box: fill-box; transform-origin: 50% 50%;" d="M 551.111 -16.154 L 551.202 389.079" transform="matrix(0, -1.184039, 0.844567, 0, 0.000036, 0.000096)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 479.043 149.568 L 495.765 149.568 L 495.765 166.035 L 479.043 166.035 L 479.043 149.568 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 598.485px 226.003px;" d="M 478.737 156.666 L 495.763 156.666"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 465.169 193.204 L 511.13 179.759 L 511.13 193.204 L 465.169 179.759 L 465.169 193.204 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 487.934 186.514 L 487.934 157.095"/>
|
||||||
|
<rect x="715.724" y="182.138" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, -2.11712, 3.138935)">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 443.701px 171.141px;" d="M 443.542 155.983 L 443.859 186.298"/>
|
||||||
|
<g>
|
||||||
|
<rect x="622.282" y="251.383" width="35.093" height="2.463" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<g>
|
||||||
|
<rect x="625.861" y="254.048" width="28.143" height="37.69" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<rect x="625.711" y="248.983" width="28.143" height="2.361" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);" cx="640.016" cy="271.807" rx="9.717" ry="7.689"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<rect x="461.861" y="211.956" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px;" x="561" y="309.954" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp SP</text>
|
||||||
|
<rect x="461.861" y="221.924" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="609.476" y="330.521" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<rect x="461.424" y="242.149" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="567.471" y="352.188" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||||
|
<rect x="461.424" y="252.117" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="608.947" y="373.755" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<rect x="535.456" y="242.272" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="659" y="352.363" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||||
|
<rect x="535.456" y="252.24" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="698.476" y="373.93" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1; font-weight: bold;" x="748" y="347.676" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER</text>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 678.512px 258.693px;" d="M 678.467 229.321 L 678.558 288.066" transform="matrix(0, 1.184039, -0.844567, 0, -0.000022, -0.000005)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.162px 309.166px;" d="M 703.004 258.049 L 703.32 360.282"/>
|
||||||
|
<rect x="371.639" y="358.212" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.133px 361.256px;" d="M 692.126 397.022 L 692.14 325.49" transform="matrix(0, -1.184039, 0.844567, 0, 0.000011, 0.00005)"/>
|
||||||
|
<rect x="715.635" y="357.867" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 798px 493.641px;" d="M 661.975 334.874 L 661.975 360.911"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 552.59px 334.782px;" d="M 552.508 244.429 L 552.665 425.136" transform="matrix(0, 1.184039, -0.844567, 0, -0.000058, 0.000018)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 601.786px 283.137px;" d="M 601.741 312.51 L 601.832 253.764" transform="matrix(0, 1.184039, -0.844567, 0, -0.000063, 0.000028)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 577.266px 308.682px;" d="M 577.237 334.87 L 577.295 282.492" transform="matrix(-1, 0, 0, -1, -0.000041, -0.00003)"/>
|
||||||
|
<g transform="matrix(-0.491177, 0, 0, 0.523644, 491.13504, 29.785091)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 406.945px 361.188px;" d="M 406.92 329.322 L 406.969 393.054" transform="matrix(0, 1.184039, -0.844567, 0, 0.00001, 0.00007)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 521.472px 494.156px;" d="M 433.307 334.116 L 433.308 362.382"/>
|
||||||
|
<g transform="matrix(0.491177, 0, 0, 0.523644, 419.010895, 56.68491)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.491177, 0, 0, 0.523644, 603.674133, 29.692444)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.491177, 0, 0, 0.523644, 677.00824, 56.209183)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 548.454px 361.62px;" d="M 548.372 271.267 L 548.529 451.974" transform="matrix(0, 1.184039, -0.844567, 0, 0.00006, -0.000036)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 578.392px 383.459px;" d="M 578.329 405.407 L 578.456 361.51" transform="matrix(-1, 0, 0, -1, -0.000055, -0.000003)"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, -0.698383, 0.257882, 545.083069)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 704.638px 576.106px;" d="M 621.828 405.49 L 634.485 405.49"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 584.749px 405.496px;" d="M 591.042 405.497 L 578.456 405.498"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 606.436px 405.492px;" d="M 591.042 398.364 L 591.042 412.623 L 621.831 398.364 L 621.831 412.623 L 591.042 398.364 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 599.432px 413.831px;" d="M 592.464 422.169 L 592.464 405.493 L 606.401 405.493" transform="matrix(-1, 0, 0, -1, -0.000078, -0.000049)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 648.376px 405.65px;" d="M 648.369 435.621 L 648.383 375.678" transform="matrix(0, -1.184039, 0.844567, 0, 0.000012, -0.00004)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 673.882px 406.067px;" d="M 673.869 398.172 L 673.893 413.963" transform="matrix(-1, 0, 0, -1, -0.000092, -0.000022)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.821px 404.67px;" d="M 692.786 392.381 L 692.854 416.962" transform="matrix(0, 1.184039, -0.844567, 0, 0.000001, -0.000097)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.406px 383.726px;" d="M 703.396 405.334 L 703.415 362.116"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 681.807px 406.051px;" d="M 681.793 398.157 L 681.818 413.948" transform="matrix(-1, 0, 0, -1, 0.000055, 0.000046)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.987px 397.326px;" d="M 677.977 403.041 L 677.995 391.61"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.933px 411.78px;" d="M 677.924 417.977 L 677.941 405.582"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="650.811" cy="385.504" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(0.000343, -1, 1, 0.000266, 743.834961, 473.853179)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 651.034px 399.693px;" d="M 651.025 405.408 L 651.042 393.977"/>
|
||||||
|
<path d="M -546.834 -405.87 L -543.785 -398.138 L -550.56 -398.138 L -546.834 -405.87 Z" bx:shape="triangle -550.56 -405.87 6.775 7.732 0.55 0 1@11f2e68a" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(-1, 0, 0, -1, 1094.344971, 804.007996)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 546.953px 390.056px;" d="M 546.94 382.161 L 546.965 397.952" transform="matrix(-1, 0, 0, -1, -0.000013, 0.000046)"/>
|
||||||
|
<rect x="427.269" y="377.282" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.167" y="545.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||||
|
<rect x="427.269" y="387.25" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.643" y="567.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||||
|
<rect x="427.27" y="412.201" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.168" y="595.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||||
|
<rect x="427.27" y="422.169" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.644" y="617.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 24.207672, -7.192523)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>OUTLET</text>
|
||||||
|
<g transform="matrix(-0.387768, 0, 0, -0.200385, 743.634644, -199.991287)" style="transform-origin: 72.2405px 412.5px;">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||||
|
</g>
|
||||||
|
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, -0.563979, 0, 588.703491, -326.882202)">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 807.67px 264.838px;" d="M 807.657 276.289 L 807.693 253.387" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.000091)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 816.592px 247.829px;" d="M 816.56 230.114 L 816.655 265.543" transform="matrix(-1, 0, 0, -1, 0.000101, 0.000009)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="840.215" cy="233.608" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 933.237163, 321.956912)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="839.876" cy="254.847" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 932.895305, 343.197025)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 823.288px 234.151px;" d="M 823.282 242.163 L 823.294 226.137" transform="matrix(0, 1.184039, -0.844567, 0, -0.000084, 0.000084)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 822.751px 254.694px;" d="M 822.745 262.706 L 822.763 246.68" transform="matrix(0, 1.184039, -0.844567, 0, -0.00002, 0.000032)"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 909.661358, 277.142276)"/>
|
||||||
|
<g transform="matrix(0.387768, 0, 0, -0.200385, 207.60318, -199.315506)" style="transform-origin: 72.2406px 412.5px;">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||||
|
</g>
|
||||||
|
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, 0.563979, 0, 51.251434, -326.206329)">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 288.049px 265.514px;" d="M 288.037 254.064 L 288.073 276.966" transform="matrix(0, -1.184039, 0.844567, 0, -0.000019, 0.000042)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 279.127px 248.505px;" d="M 279.095 266.219 L 279.19 230.79"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.5" cy="234.284" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.108874, 140.490195)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.84" cy="255.523" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.450457, 161.730307)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.43px 234.827px;" d="M 272.424 226.815 L 272.436 242.841" transform="matrix(0, 1.184039, -0.844567, 0, 0.000012, -0.000015)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.968px 255.37px;" d="M 272.962 247.358 L 272.98 263.384" transform="matrix(0, 1.184039, -0.844567, 0, -0.000011, 0.000034)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 170.68468, 95.675559)"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 47.595016, -269.416931)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>INLET</text>
|
||||||
|
<path d="M -544.544 -165.9 L -541.495 -158.168 L -548.27 -158.168 L -544.544 -165.9 Z" bx:shape="triangle -548.27 -165.9 6.775 7.732 0.55 0 1@12a1c9af" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -544.876px -162.033px;" transform="matrix(-1, 0, 0, -1, 1089.751221, 324.066467)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 544.653px 150.086px;" d="M 544.639 142.192 L 544.664 157.983" transform="matrix(-1, 0, 0, -1, -0.000086, 0.000018)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 404.306 125.878 L 416.234 125.878 L 416.234 148.965 L 404.306 148.965 L 404.306 125.878 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000039, -0.000007)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 498.152px 180.679px;" d="M 408.92 144.738 L 408.92 130.357"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 427.836 146.217 L 460.619 127.367 L 460.619 146.217 L 427.836 127.367 L 427.836 146.217 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000009)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 426.851 157.594 L 426.851 116.351" transform="matrix(0, -1.18404, 0.844567, 0, -0.000439, -0.000081)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 647.335px 171.745px;" d="M 647.177 186.902 L 647.494 156.588" transform="matrix(-1, 0, 0, -1, -0.000021, -0.000027)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 680.766px 138.025px;" d="M 674.802 149.569 L 686.73 149.569 L 686.73 126.482 L 674.802 126.482 L 674.802 149.569 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000004, 0.000043)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 897.148px 118.148px;" d="M 682.129 145.344 L 682.129 130.964"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 646.809px 137.396px;" d="M 630.417 127.971 L 663.201 146.821 L 663.201 127.971 L 630.417 146.821 L 630.417 127.971 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000004, -0.000033)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 664.185px 137.577px;" d="M 664.185 116.956 L 664.186 158.199" transform="matrix(0, -1.184039, 0.844567, 0, -0.000026, -0.000041)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 590.855 148.509 L 607.577 148.509 L 607.577 164.977 L 590.855 164.977 L 590.855 148.509 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 733.701px 224.488px;" d="M 590.555 155.608 L 607.58 155.608"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 576.981 192.146 L 622.941 178.701 L 622.941 192.146 L 576.981 178.701 L 576.981 192.146 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 599.746 185.455 L 599.746 156.037"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 640.474 90.952 L 652.913 90.952 L 652.913 107.419 L 640.474 107.419 L 640.474 90.952 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 646.819px 113.115px;" d="M 646.799 107.896 L 646.839 118.334"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 650.466px 95.308px;" d="M 650.455 97.624 L 650.476 92.992" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.00002)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 642.857px 102.712px;" d="M 642.846 105.028 L 642.867 100.396" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000035)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 437.929 90.244 L 450.367 90.244 L 450.367 106.712 L 437.929 106.712 L 437.929 90.244 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 444.274px 112.408px;" d="M 444.253 107.189 L 444.293 117.627"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 447.919px 94.601px;" d="M 447.909 96.917 L 447.93 92.285" transform="matrix(0, -1.184039, 0.844567, 0, -0.000022, 0.000005)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 440.31px 102.004px;" d="M 440.3 104.321 L 440.32 99.689" transform="matrix(0, -1.184039, 0.844567, 0, -0.000028, -0.000004)"/>
|
||||||
|
<rect x="43.443" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="53.987" y="423.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RUN HOUR</text>
|
||||||
|
<rect x="126.135" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="424.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="461.382" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">PURGE HOUR</text>
|
||||||
|
<rect x="126.135" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="463.357" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; font-weight: 700; white-space: pre;" x="53.987" y="498.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER HOUR</text>
|
||||||
|
<rect x="126.135" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="499.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.65" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(248, 213, 14);"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="536.777" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Info</text>
|
||||||
|
<rect x="126.341" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.3" y="537.792" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="537.752" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="56.987" y="250.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT or LT Dry</text>
|
||||||
|
<rect x="126.135" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="180.05" y="251.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">LT Dry</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177" y="251.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT Dry</text>
|
||||||
|
<rect x="43.443" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="288.876" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Opmode</text>
|
||||||
|
<rect x="126.135" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HTD</text>
|
||||||
|
<rect x="43.443" y="214.051" width="165.383" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="53.987" y="322.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Step</text>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="233" y="323.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||||
|
<rect x="43.443" y="241.422" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="364.271" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Cycle Timer</text>
|
||||||
|
<rect x="126.341" y="241.068" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="365.246" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="141.894" y="324.069" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Time</text>
|
||||||
|
|
||||||
|
<rect x="870.356" y="142.816" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1060.06" y="224.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dryer Status</text>
|
||||||
|
<rect x="870.356" y="170.304" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="192.401" rx="20.673" ry="17.46"/>
|
||||||
|
<rect x="870.356" y="230.113" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1069.06" y="349.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Shutdown</text>
|
||||||
|
<rect x="870.356" y="257.602" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="279.699" rx="20.673" ry="17.46"/>
|
||||||
|
<rect x="870.356" y="317.411" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1059.06" y="474.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Status</text>
|
||||||
|
<rect x="870.356" y="344.9" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="366.997" rx="20.673" ry="17.46"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="380.451" y="296.591" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="336.418" cy="237.483" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="379.214" y="423.395" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.418" cy="321.016" rx="13.582" ry="12.517"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.623" cy="320.662" rx="13.582" ry="12.517" id="c_4021"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="897.237" y="299.014" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="761.772" cy="233.876" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="896" y="425.818" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.96" cy="322.354" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 35px; stroke-width: 1; font-weight: bold;" x="348.875" y="78.242" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">AIR DRYER UNIT A (01-CL-10532-A)</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 1.386371, 4.000207)">HTLS</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, -1.613663, 3.937793)">BLWR</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.447" y="617.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4005">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.446" y="567.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4004">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="653.279" y="373.97" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4001">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="563.75" y="373.795" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4002">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="564.279" y="330.561" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4003">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="424.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4009">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="463.397" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4010">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="499.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4011">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177.05" y="323.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4008">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="168.775" y="365.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4007">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="92.151" y="325.554" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_4006">##</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="336.418" cy="237.483" rx="13.582" ry="12.517" id="c_4018"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(255, 172, 63);" cx="640.283" cy="271.689" rx="9.717" ry="7.689" id="c_4019"/>
|
||||||
|
<ellipse style="fill: rgb(63, 255, 69); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.254" cy="192.696" rx="20.673" ry="17.46" id="c_4016"/>
|
||||||
|
<ellipse style="fill: rgb(255, 159, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.352" cy="279.12" rx="20.673" ry="17.46" id="c_4017"/>
|
||||||
|
<ellipse style="fill: rgb(255, 63, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.352" cy="366.862" rx="20.673" ry="17.46" id="c_4020"/>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.685" cy="322.259" rx="13.582" ry="12.517" id="c_4018"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.031" cy="234.094" rx="13.582" ry="12.517" id="c_4021"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 41 KiB |
250
src/assets/svg/air_dryer_B_rev.svg
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 500" xmlns:bx="https://boxy-svg.com">
|
||||||
|
<defs>
|
||||||
|
<bx:grid x="0" y="0" width="25" height="25"/>
|
||||||
|
</defs>
|
||||||
|
<rect y="10.407" width="972.648" height="440.159" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" x="12.119"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, 74.03907, 53.375034)">
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||||
|
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, 500.726135, 53.375034)">
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||||
|
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||||
|
</g>
|
||||||
|
<rect x="371.728" y="182.483" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-box: fill-box; transform-origin: 50% 50%;" d="M 551.111 -16.154 L 551.202 389.079" transform="matrix(0, -1.184039, 0.844567, 0, 0.000036, 0.000096)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 479.043 149.568 L 495.765 149.568 L 495.765 166.035 L 479.043 166.035 L 479.043 149.568 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 598.485px 226.003px;" d="M 478.737 156.666 L 495.763 156.666"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 465.169 193.204 L 511.13 179.759 L 511.13 193.204 L 465.169 179.759 L 465.169 193.204 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 487.934 186.514 L 487.934 157.095"/>
|
||||||
|
<rect x="715.724" y="182.138" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, -2.11712, 3.138935)">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 443.701px 171.141px;" d="M 443.542 155.983 L 443.859 186.298"/>
|
||||||
|
<g>
|
||||||
|
<rect x="622.282" y="251.383" width="35.093" height="2.463" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<g>
|
||||||
|
<rect x="625.861" y="254.048" width="28.143" height="37.69" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<rect x="625.711" y="248.983" width="28.143" height="2.361" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);" cx="640.016" cy="271.807" rx="9.717" ry="7.689"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<rect x="461.861" y="211.956" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px;" x="561" y="309.954" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp SP</text>
|
||||||
|
<rect x="461.861" y="221.924" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="609.476" y="330.521" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<rect x="461.424" y="242.149" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="567.471" y="352.188" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||||
|
<rect x="461.424" y="252.117" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="608.947" y="373.755" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<rect x="535.456" y="242.272" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="659" y="352.363" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||||
|
<rect x="535.456" y="252.24" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="698.476" y="373.93" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1; font-weight: bold;" x="748" y="347.676" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER</text>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 678.512px 258.693px;" d="M 678.467 229.321 L 678.558 288.066" transform="matrix(0, 1.184039, -0.844567, 0, -0.000022, -0.000005)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.162px 309.166px;" d="M 703.004 258.049 L 703.32 360.282"/>
|
||||||
|
<rect x="371.639" y="358.212" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.133px 361.256px;" d="M 692.126 397.022 L 692.14 325.49" transform="matrix(0, -1.184039, 0.844567, 0, 0.000011, 0.00005)"/>
|
||||||
|
<rect x="715.635" y="357.867" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 798px 493.641px;" d="M 661.975 334.874 L 661.975 360.911"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 552.59px 334.782px;" d="M 552.508 244.429 L 552.665 425.136" transform="matrix(0, 1.184039, -0.844567, 0, -0.000058, 0.000018)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 601.786px 283.137px;" d="M 601.741 312.51 L 601.832 253.764" transform="matrix(0, 1.184039, -0.844567, 0, -0.000063, 0.000028)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 577.266px 308.682px;" d="M 577.237 334.87 L 577.295 282.492" transform="matrix(-1, 0, 0, -1, -0.000041, -0.00003)"/>
|
||||||
|
<g transform="matrix(-0.491177, 0, 0, 0.523644, 491.13504, 29.785091)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 406.945px 361.188px;" d="M 406.92 329.322 L 406.969 393.054" transform="matrix(0, 1.184039, -0.844567, 0, 0.00001, 0.00007)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 521.472px 494.156px;" d="M 433.307 334.116 L 433.308 362.382"/>
|
||||||
|
<g transform="matrix(0.491177, 0, 0, 0.523644, 419.010895, 56.68491)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.491177, 0, 0, 0.523644, 603.674133, 29.692444)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.491177, 0, 0, 0.523644, 677.00824, 56.209183)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 548.454px 361.62px;" d="M 548.372 271.267 L 548.529 451.974" transform="matrix(0, 1.184039, -0.844567, 0, 0.00006, -0.000036)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 578.392px 383.459px;" d="M 578.329 405.407 L 578.456 361.51" transform="matrix(-1, 0, 0, -1, -0.000055, -0.000003)"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, -0.698383, 0.257882, 545.083069)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 704.638px 576.106px;" d="M 621.828 405.49 L 634.485 405.49"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 584.749px 405.496px;" d="M 591.042 405.497 L 578.456 405.498"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 606.436px 405.492px;" d="M 591.042 398.364 L 591.042 412.623 L 621.831 398.364 L 621.831 412.623 L 591.042 398.364 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 599.432px 413.831px;" d="M 592.464 422.169 L 592.464 405.493 L 606.401 405.493" transform="matrix(-1, 0, 0, -1, -0.000078, -0.000049)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 648.376px 405.65px;" d="M 648.369 435.621 L 648.383 375.678" transform="matrix(0, -1.184039, 0.844567, 0, 0.000012, -0.00004)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 673.882px 406.067px;" d="M 673.869 398.172 L 673.893 413.963" transform="matrix(-1, 0, 0, -1, -0.000092, -0.000022)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.821px 404.67px;" d="M 692.786 392.381 L 692.854 416.962" transform="matrix(0, 1.184039, -0.844567, 0, 0.000001, -0.000097)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.406px 383.726px;" d="M 703.396 405.334 L 703.415 362.116"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 681.807px 406.051px;" d="M 681.793 398.157 L 681.818 413.948" transform="matrix(-1, 0, 0, -1, 0.000055, 0.000046)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.987px 397.326px;" d="M 677.977 403.041 L 677.995 391.61"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.933px 411.78px;" d="M 677.924 417.977 L 677.941 405.582"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="650.811" cy="385.504" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(0.000343, -1, 1, 0.000266, 743.834961, 473.853179)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 651.034px 399.693px;" d="M 651.025 405.408 L 651.042 393.977"/>
|
||||||
|
<path d="M -546.834 -405.87 L -543.785 -398.138 L -550.56 -398.138 L -546.834 -405.87 Z" bx:shape="triangle -550.56 -405.87 6.775 7.732 0.55 0 1@11f2e68a" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(-1, 0, 0, -1, 1094.344971, 804.007996)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 546.953px 390.056px;" d="M 546.94 382.161 L 546.965 397.952" transform="matrix(-1, 0, 0, -1, -0.000013, 0.000046)"/>
|
||||||
|
<rect x="427.269" y="377.282" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.167" y="545.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||||
|
<rect x="427.269" y="387.25" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.643" y="567.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||||
|
<rect x="427.27" y="412.201" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.168" y="595.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||||
|
<rect x="427.27" y="422.169" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.644" y="617.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 24.207672, -7.192523)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>OUTLET</text>
|
||||||
|
<g transform="matrix(-0.387768, 0, 0, -0.200385, 743.634644, -199.991287)" style="transform-origin: 72.2405px 412.5px;">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||||
|
</g>
|
||||||
|
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, -0.563979, 0, 588.703491, -326.882202)">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 807.67px 264.838px;" d="M 807.657 276.289 L 807.693 253.387" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.000091)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 816.592px 247.829px;" d="M 816.56 230.114 L 816.655 265.543" transform="matrix(-1, 0, 0, -1, 0.000101, 0.000009)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="840.215" cy="233.608" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 933.237163, 321.956912)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="839.876" cy="254.847" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 932.895305, 343.197025)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 823.288px 234.151px;" d="M 823.282 242.163 L 823.294 226.137" transform="matrix(0, 1.184039, -0.844567, 0, -0.000084, 0.000084)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 822.751px 254.694px;" d="M 822.745 262.706 L 822.763 246.68" transform="matrix(0, 1.184039, -0.844567, 0, -0.00002, 0.000032)"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 909.661358, 277.142276)"/>
|
||||||
|
<g transform="matrix(0.387768, 0, 0, -0.200385, 207.60318, -199.315506)" style="transform-origin: 72.2406px 412.5px;">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||||
|
</g>
|
||||||
|
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, 0.563979, 0, 51.251434, -326.206329)">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 288.049px 265.514px;" d="M 288.037 254.064 L 288.073 276.966" transform="matrix(0, -1.184039, 0.844567, 0, -0.000019, 0.000042)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 279.127px 248.505px;" d="M 279.095 266.219 L 279.19 230.79"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.5" cy="234.284" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.108874, 140.490195)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.84" cy="255.523" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.450457, 161.730307)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.43px 234.827px;" d="M 272.424 226.815 L 272.436 242.841" transform="matrix(0, 1.184039, -0.844567, 0, 0.000012, -0.000015)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.968px 255.37px;" d="M 272.962 247.358 L 272.98 263.384" transform="matrix(0, 1.184039, -0.844567, 0, -0.000011, 0.000034)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 170.68468, 95.675559)"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 47.595016, -269.416931)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>INLET</text>
|
||||||
|
<path d="M -544.544 -165.9 L -541.495 -158.168 L -548.27 -158.168 L -544.544 -165.9 Z" bx:shape="triangle -548.27 -165.9 6.775 7.732 0.55 0 1@12a1c9af" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -544.876px -162.033px;" transform="matrix(-1, 0, 0, -1, 1089.751221, 324.066467)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 544.653px 150.086px;" d="M 544.639 142.192 L 544.664 157.983" transform="matrix(-1, 0, 0, -1, -0.000086, 0.000018)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 404.306 125.878 L 416.234 125.878 L 416.234 148.965 L 404.306 148.965 L 404.306 125.878 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000039, -0.000007)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 498.152px 180.679px;" d="M 408.92 144.738 L 408.92 130.357"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 427.836 146.217 L 460.619 127.367 L 460.619 146.217 L 427.836 127.367 L 427.836 146.217 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000009)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 426.851 157.594 L 426.851 116.351" transform="matrix(0, -1.18404, 0.844567, 0, -0.000439, -0.000081)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 647.335px 171.745px;" d="M 647.177 186.902 L 647.494 156.588" transform="matrix(-1, 0, 0, -1, -0.000021, -0.000027)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 680.766px 138.025px;" d="M 674.802 149.569 L 686.73 149.569 L 686.73 126.482 L 674.802 126.482 L 674.802 149.569 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000004, 0.000043)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 897.148px 118.148px;" d="M 682.129 145.344 L 682.129 130.964"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 646.809px 137.396px;" d="M 630.417 127.971 L 663.201 146.821 L 663.201 127.971 L 630.417 146.821 L 630.417 127.971 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000004, -0.000033)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 664.185px 137.577px;" d="M 664.185 116.956 L 664.186 158.199" transform="matrix(0, -1.184039, 0.844567, 0, -0.000026, -0.000041)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 590.855 148.509 L 607.577 148.509 L 607.577 164.977 L 590.855 164.977 L 590.855 148.509 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 733.701px 224.488px;" d="M 590.555 155.608 L 607.58 155.608"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 576.981 192.146 L 622.941 178.701 L 622.941 192.146 L 576.981 178.701 L 576.981 192.146 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 599.746 185.455 L 599.746 156.037"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 640.474 90.952 L 652.913 90.952 L 652.913 107.419 L 640.474 107.419 L 640.474 90.952 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 646.819px 113.115px;" d="M 646.799 107.896 L 646.839 118.334"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 650.466px 95.308px;" d="M 650.455 97.624 L 650.476 92.992" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.00002)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 642.857px 102.712px;" d="M 642.846 105.028 L 642.867 100.396" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000035)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 437.929 90.244 L 450.367 90.244 L 450.367 106.712 L 437.929 106.712 L 437.929 90.244 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 444.274px 112.408px;" d="M 444.253 107.189 L 444.293 117.627"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 447.919px 94.601px;" d="M 447.909 96.917 L 447.93 92.285" transform="matrix(0, -1.184039, 0.844567, 0, -0.000022, 0.000005)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 440.31px 102.004px;" d="M 440.3 104.321 L 440.32 99.689" transform="matrix(0, -1.184039, 0.844567, 0, -0.000028, -0.000004)"/>
|
||||||
|
<rect x="43.443" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="53.987" y="423.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RUN HOUR</text>
|
||||||
|
<rect x="126.135" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="424.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="461.382" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">PURGE HOUR</text>
|
||||||
|
<rect x="126.135" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="463.357" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; font-weight: 700; white-space: pre;" x="53.987" y="498.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER HOUR</text>
|
||||||
|
<rect x="126.135" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="499.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.65" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(248, 213, 14);"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="536.777" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Info</text>
|
||||||
|
<rect x="126.341" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.3" y="537.792" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="537.752" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="56.987" y="250.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT or LT Dry</text>
|
||||||
|
<rect x="126.135" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="180.05" y="251.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">LT Dry</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177" y="251.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT Dry</text>
|
||||||
|
<rect x="43.443" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="288.876" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Opmode</text>
|
||||||
|
<rect x="126.135" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HTD</text>
|
||||||
|
<rect x="43.443" y="214.051" width="165.383" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="53.987" y="322.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Step</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="233" y="323.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||||
|
<rect x="43.443" y="241.422" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="364.271" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Cycle Timer</text>
|
||||||
|
<rect x="126.341" y="241.068" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="365.246" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="141.894" y="324.069" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Time</text>
|
||||||
|
<rect x="870.356" y="142.816" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1060.06" y="224.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dryer Status</text>
|
||||||
|
<rect x="870.356" y="170.304" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="192.401" rx="20.673" ry="17.46"/>
|
||||||
|
<rect x="870.356" y="230.113" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1069.06" y="349.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Shutdown</text>
|
||||||
|
<rect x="870.356" y="257.602" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="279.699" rx="20.673" ry="17.46"/>
|
||||||
|
<rect x="870.356" y="317.411" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1059.06" y="474.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Status</text>
|
||||||
|
<rect x="870.356" y="344.9" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="366.997" rx="20.673" ry="17.46"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="380.451" y="296.591" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="336.418" cy="237.483" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="379.214" y="423.395" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.418" cy="321.016" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="897.237" y="299.014" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="761.772" cy="233.876" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="896" y="425.818" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.96" cy="322.354" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 35px; stroke-width: 1; font-weight: bold;" x="348.875" y="78.242" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">AIR DRYER UNIT B (01-CL-10535-B)</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 1.386371, 4.000207)">HTLS</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, -1.613663, 3.937793)">BLWR</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.447" y="617.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5005">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.446" y="567.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5004">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="653.279" y="373.97" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5001">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="563.75" y="373.795" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5002">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="564.279" y="330.561" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5003">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="424.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5009">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="463.397" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5010">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="499.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5011">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177.05" y="323.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5008">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="168.775" y="365.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5007">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="92.151" y="325.554" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_5006">##</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="336.418" cy="237.483" rx="13.582" ry="12.517" id="c_5018"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(255, 172, 63);" cx="640.283" cy="271.689" rx="9.717" ry="7.689" id="c_5019"/>
|
||||||
|
<ellipse style="fill: rgb(63, 255, 69); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.254" cy="192.696" rx="20.673" ry="17.46" id="c_5016"/>
|
||||||
|
<ellipse style="fill: rgb(255, 159, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.352" cy="279.12" rx="20.673" ry="17.46" id="c_5017"/>
|
||||||
|
<ellipse style="fill: rgb(255, 63, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.352" cy="366.862" rx="20.673" ry="17.46" id="c_5020"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.623" cy="320.662" rx="13.582" ry="12.517" id="c_5021"/>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.685" cy="322.259" rx="13.582" ry="12.517" id="c_5018"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.031" cy="234.094" rx="13.582" ry="12.517" id="c_5021"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 41 KiB |
251
src/assets/svg/air_dryer_C_rev.svg
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 500" xmlns:bx="https://boxy-svg.com">
|
||||||
|
<defs>
|
||||||
|
<bx:grid x="0" y="0" width="25" height="25"/>
|
||||||
|
</defs>
|
||||||
|
<rect y="10.407" width="972.648" height="440.159" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" x="12.119"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, 74.03907, 53.375034)">
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||||
|
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, 500.726135, 53.375034)">
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||||
|
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||||
|
</g>
|
||||||
|
<rect x="371.728" y="182.483" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-box: fill-box; transform-origin: 50% 50%;" d="M 551.111 -16.154 L 551.202 389.079" transform="matrix(0, -1.184039, 0.844567, 0, 0.000036, 0.000096)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 479.043 149.568 L 495.765 149.568 L 495.765 166.035 L 479.043 166.035 L 479.043 149.568 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 598.485px 226.003px;" d="M 478.737 156.666 L 495.763 156.666"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 465.169 193.204 L 511.13 179.759 L 511.13 193.204 L 465.169 179.759 L 465.169 193.204 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 487.934 186.514 L 487.934 157.095"/>
|
||||||
|
<rect x="715.724" y="182.138" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, 0.698383, -2.11712, 3.138935)">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 443.701px 171.141px;" d="M 443.542 155.983 L 443.859 186.298"/>
|
||||||
|
<g>
|
||||||
|
<rect x="622.282" y="251.383" width="35.093" height="2.463" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<g>
|
||||||
|
<rect x="625.861" y="254.048" width="28.143" height="37.69" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<rect x="625.711" y="248.983" width="28.143" height="2.361" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(243, 243, 243);" cx="640.016" cy="271.807" rx="9.717" ry="7.689"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<rect x="461.861" y="211.956" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px;" x="561" y="309.954" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp SP</text>
|
||||||
|
<rect x="461.861" y="221.924" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="609.476" y="330.521" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<rect x="461.424" y="242.149" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="567.471" y="352.188" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||||
|
<rect x="461.424" y="252.117" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="608.947" y="373.755" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<rect x="535.456" y="242.272" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="659" y="352.363" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||||
|
<rect x="535.456" y="252.24" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="698.476" y="373.93" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1; font-weight: bold;" x="748" y="347.676" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER</text>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 678.512px 258.693px;" d="M 678.467 229.321 L 678.558 288.066" transform="matrix(0, 1.184039, -0.844567, 0, -0.000022, -0.000005)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.162px 309.166px;" d="M 703.004 258.049 L 703.32 360.282"/>
|
||||||
|
<rect x="371.639" y="358.212" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.133px 361.256px;" d="M 692.126 397.022 L 692.14 325.49" transform="matrix(0, -1.184039, 0.844567, 0, 0.000011, 0.00005)"/>
|
||||||
|
<rect x="715.635" y="357.867" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 798px 493.641px;" d="M 661.975 334.874 L 661.975 360.911"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 552.59px 334.782px;" d="M 552.508 244.429 L 552.665 425.136" transform="matrix(0, 1.184039, -0.844567, 0, -0.000058, 0.000018)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 601.786px 283.137px;" d="M 601.741 312.51 L 601.832 253.764" transform="matrix(0, 1.184039, -0.844567, 0, -0.000063, 0.000028)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 577.266px 308.682px;" d="M 577.237 334.87 L 577.295 282.492" transform="matrix(-1, 0, 0, -1, -0.000041, -0.00003)"/>
|
||||||
|
<g transform="matrix(-0.491177, 0, 0, 0.523644, 491.13504, 29.785091)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 406.945px 361.188px;" d="M 406.92 329.322 L 406.969 393.054" transform="matrix(0, 1.184039, -0.844567, 0, 0.00001, 0.00007)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 521.472px 494.156px;" d="M 433.307 334.116 L 433.308 362.382"/>
|
||||||
|
<g transform="matrix(0.491177, 0, 0, 0.523644, 419.010895, 56.68491)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.491177, 0, 0, 0.523644, 603.674133, 29.692444)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.491177, 0, 0, 0.523644, 677.00824, 56.209183)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||||
|
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 548.454px 361.62px;" d="M 548.372 271.267 L 548.529 451.974" transform="matrix(0, 1.184039, -0.844567, 0, 0.00006, -0.000036)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 578.392px 383.459px;" d="M 578.329 405.407 L 578.456 361.51" transform="matrix(-1, 0, 0, -1, -0.000055, -0.000003)"/>
|
||||||
|
<g transform="matrix(0.826913, 0, 0, -0.698383, 0.257882, 545.083069)" style="">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 704.638px 576.106px;" d="M 621.828 405.49 L 634.485 405.49"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 584.749px 405.496px;" d="M 591.042 405.497 L 578.456 405.498"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 606.436px 405.492px;" d="M 591.042 398.364 L 591.042 412.623 L 621.831 398.364 L 621.831 412.623 L 591.042 398.364 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 599.432px 413.831px;" d="M 592.464 422.169 L 592.464 405.493 L 606.401 405.493" transform="matrix(-1, 0, 0, -1, -0.000078, -0.000049)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 648.376px 405.65px;" d="M 648.369 435.621 L 648.383 375.678" transform="matrix(0, -1.184039, 0.844567, 0, 0.000012, -0.00004)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 673.882px 406.067px;" d="M 673.869 398.172 L 673.893 413.963" transform="matrix(-1, 0, 0, -1, -0.000092, -0.000022)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.821px 404.67px;" d="M 692.786 392.381 L 692.854 416.962" transform="matrix(0, 1.184039, -0.844567, 0, 0.000001, -0.000097)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.406px 383.726px;" d="M 703.396 405.334 L 703.415 362.116"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 681.807px 406.051px;" d="M 681.793 398.157 L 681.818 413.948" transform="matrix(-1, 0, 0, -1, 0.000055, 0.000046)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.987px 397.326px;" d="M 677.977 403.041 L 677.995 391.61"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.933px 411.78px;" d="M 677.924 417.977 L 677.941 405.582"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="650.811" cy="385.504" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(0.000343, -1, 1, 0.000266, 743.834961, 473.853179)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 651.034px 399.693px;" d="M 651.025 405.408 L 651.042 393.977"/>
|
||||||
|
<path d="M -546.834 -405.87 L -543.785 -398.138 L -550.56 -398.138 L -546.834 -405.87 Z" bx:shape="triangle -550.56 -405.87 6.775 7.732 0.55 0 1@11f2e68a" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(-1, 0, 0, -1, 1094.344971, 804.007996)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 546.953px 390.056px;" d="M 546.94 382.161 L 546.965 397.952" transform="matrix(-1, 0, 0, -1, -0.000013, 0.000046)"/>
|
||||||
|
<rect x="427.269" y="377.282" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.167" y="545.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||||
|
<rect x="427.269" y="387.25" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.643" y="567.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||||
|
<rect x="427.27" y="412.201" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.168" y="595.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||||
|
<rect x="427.27" y="422.169" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.644" y="617.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 24.207672, -7.192523)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>OUTLET</text>
|
||||||
|
<g transform="matrix(-0.387768, 0, 0, -0.200385, 743.634644, -199.991287)" style="transform-origin: 72.2405px 412.5px;">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||||
|
</g>
|
||||||
|
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, -0.563979, 0, 588.703491, -326.882202)">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 807.67px 264.838px;" d="M 807.657 276.289 L 807.693 253.387" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.000091)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 816.592px 247.829px;" d="M 816.56 230.114 L 816.655 265.543" transform="matrix(-1, 0, 0, -1, 0.000101, 0.000009)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="840.215" cy="233.608" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 933.237163, 321.956912)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="839.876" cy="254.847" rx="10.336" ry="8.73"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 932.895305, 343.197025)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 823.288px 234.151px;" d="M 823.282 242.163 L 823.294 226.137" transform="matrix(0, 1.184039, -0.844567, 0, -0.000084, 0.000084)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 822.751px 254.694px;" d="M 822.745 262.706 L 822.763 246.68" transform="matrix(0, 1.184039, -0.844567, 0, -0.00002, 0.000032)"/>
|
||||||
|
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 909.661358, 277.142276)"/>
|
||||||
|
<g transform="matrix(0.387768, 0, 0, -0.200385, 207.60318, -199.315506)" style="transform-origin: 72.2406px 412.5px;">
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||||
|
</g>
|
||||||
|
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, 0.563979, 0, 51.251434, -326.206329)">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 288.049px 265.514px;" d="M 288.037 254.064 L 288.073 276.966" transform="matrix(0, -1.184039, 0.844567, 0, -0.000019, 0.000042)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 279.127px 248.505px;" d="M 279.095 266.219 L 279.19 230.79"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.5" cy="234.284" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.108874, 140.490195)"/>
|
||||||
|
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.84" cy="255.523" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.450457, 161.730307)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.43px 234.827px;" d="M 272.424 226.815 L 272.436 242.841" transform="matrix(0, 1.184039, -0.844567, 0, 0.000012, -0.000015)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.968px 255.37px;" d="M 272.962 247.358 L 272.98 263.384" transform="matrix(0, 1.184039, -0.844567, 0, -0.000011, 0.000034)"/>
|
||||||
|
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 170.68468, 95.675559)"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 47.595016, -269.416931)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>INLET</text>
|
||||||
|
<path d="M -544.544 -165.9 L -541.495 -158.168 L -548.27 -158.168 L -544.544 -165.9 Z" bx:shape="triangle -548.27 -165.9 6.775 7.732 0.55 0 1@12a1c9af" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -544.876px -162.033px;" transform="matrix(-1, 0, 0, -1, 1089.751221, 324.066467)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 544.653px 150.086px;" d="M 544.639 142.192 L 544.664 157.983" transform="matrix(-1, 0, 0, -1, -0.000086, 0.000018)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 404.306 125.878 L 416.234 125.878 L 416.234 148.965 L 404.306 148.965 L 404.306 125.878 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000039, -0.000007)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 498.152px 180.679px;" d="M 408.92 144.738 L 408.92 130.357"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 427.836 146.217 L 460.619 127.367 L 460.619 146.217 L 427.836 127.367 L 427.836 146.217 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000009)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 426.851 157.594 L 426.851 116.351" transform="matrix(0, -1.18404, 0.844567, 0, -0.000439, -0.000081)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 647.335px 171.745px;" d="M 647.177 186.902 L 647.494 156.588" transform="matrix(-1, 0, 0, -1, -0.000021, -0.000027)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 680.766px 138.025px;" d="M 674.802 149.569 L 686.73 149.569 L 686.73 126.482 L 674.802 126.482 L 674.802 149.569 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000004, 0.000043)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 897.148px 118.148px;" d="M 682.129 145.344 L 682.129 130.964"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 646.809px 137.396px;" d="M 630.417 127.971 L 663.201 146.821 L 663.201 127.971 L 630.417 146.821 L 630.417 127.971 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000004, -0.000033)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 664.185px 137.577px;" d="M 664.185 116.956 L 664.186 158.199" transform="matrix(0, -1.184039, 0.844567, 0, -0.000026, -0.000041)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 590.855 148.509 L 607.577 148.509 L 607.577 164.977 L 590.855 164.977 L 590.855 148.509 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 733.701px 224.488px;" d="M 590.555 155.608 L 607.58 155.608"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 576.981 192.146 L 622.941 178.701 L 622.941 192.146 L 576.981 178.701 L 576.981 192.146 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 599.746 185.455 L 599.746 156.037"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 640.474 90.952 L 652.913 90.952 L 652.913 107.419 L 640.474 107.419 L 640.474 90.952 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 646.819px 113.115px;" d="M 646.799 107.896 L 646.839 118.334"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 650.466px 95.308px;" d="M 650.455 97.624 L 650.476 92.992" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.00002)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 642.857px 102.712px;" d="M 642.846 105.028 L 642.867 100.396" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000035)"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 437.929 90.244 L 450.367 90.244 L 450.367 106.712 L 437.929 106.712 L 437.929 90.244 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 444.274px 112.408px;" d="M 444.253 107.189 L 444.293 117.627"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 447.919px 94.601px;" d="M 447.909 96.917 L 447.93 92.285" transform="matrix(0, -1.184039, 0.844567, 0, -0.000022, 0.000005)"/>
|
||||||
|
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 440.31px 102.004px;" d="M 440.3 104.321 L 440.32 99.689" transform="matrix(0, -1.184039, 0.844567, 0, -0.000028, -0.000004)"/>
|
||||||
|
<rect x="43.443" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="53.987" y="423.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RUN HOUR</text>
|
||||||
|
<rect x="126.135" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="424.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="461.382" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">PURGE HOUR</text>
|
||||||
|
<rect x="126.135" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="463.357" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; font-weight: 700; white-space: pre;" x="53.987" y="498.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER HOUR</text>
|
||||||
|
<rect x="126.135" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="499.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.65" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(248, 213, 14);"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="536.777" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Info</text>
|
||||||
|
<rect x="126.341" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.3" y="537.792" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="537.752" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||||
|
<rect x="43.443" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="56.987" y="250.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT or LT Dry</text>
|
||||||
|
<rect x="126.135" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="180.05" y="251.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">LT Dry</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177" y="251.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT Dry</text>
|
||||||
|
<rect x="43.443" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="288.876" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Opmode</text>
|
||||||
|
<rect x="126.135" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HTD</text>
|
||||||
|
<rect x="43.443" y="214.051" width="165.383" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="53.987" y="322.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Step</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="233" y="323.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||||
|
<rect x="43.443" y="241.422" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="364.271" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Cycle Timer</text>
|
||||||
|
<rect x="126.341" y="241.068" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="365.246" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="141.894" y="324.069" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Time</text>
|
||||||
|
<rect x="870.356" y="142.816" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1060.06" y="224.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dryer Status</text>
|
||||||
|
<rect x="870.356" y="170.304" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="192.401" rx="20.673" ry="17.46"/>
|
||||||
|
<rect x="870.356" y="230.113" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1069.06" y="349.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Shutdown</text>
|
||||||
|
<rect x="870.356" y="257.602" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="279.699" rx="20.673" ry="17.46"/>
|
||||||
|
<rect x="870.356" y="317.411" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1059.06" y="474.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Status</text>
|
||||||
|
<rect x="870.356" y="344.9" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="366.997" rx="20.673" ry="17.46"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="380.451" y="296.591" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="336.418" cy="237.483" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="379.214" y="423.395" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.418" cy="321.016" rx="13.582" ry="12.517"/>
|
||||||
|
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="897.237" y="299.014" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="761.772" cy="233.876" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="896" y="425.818" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.96" cy="322.354" rx="13.582" ry="12.517"/>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 35px; stroke-width: 1; font-weight: bold;" x="348.875" y="78.242" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">AIR DRYER UNIT C (01-CL-10539-C)</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 1.386371, 4.000207)">HTLS</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, -1.613663, 3.937793)">BLWR</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.447" y="617.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6005">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.446" y="567.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6004">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="653.279" y="373.97" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6001">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="563.75" y="373.795" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6002">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="564.279" y="330.561" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6003">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="424.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6009">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="463.397" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6010">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="499.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6011">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177.05" y="323.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6008">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="168.775" y="365.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6007">####.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="92.151" y="325.554" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="c_6006">##</text>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="336.418" cy="237.483" rx="13.582" ry="12.517" id="c_6018"/>
|
||||||
|
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(255, 172, 63);" cx="640.283" cy="271.689" rx="9.717" ry="7.689" id="c_6019"/>
|
||||||
|
<ellipse style="fill: rgb(63, 255, 69); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.254" cy="192.696" rx="20.673" ry="17.46" id="c_6016"/>
|
||||||
|
<ellipse style="fill: rgb(255, 159, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.352" cy="279.12" rx="20.673" ry="17.46" id="c_6017"/>
|
||||||
|
<ellipse style="fill: rgb(255, 63, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.352" cy="366.862" rx="20.673" ry="17.46" id="c_6020"/>
|
||||||
|
<ellipse style="fill: rgb(255, 204, 63); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.685" cy="322.259" rx="13.582" ry="12.517" id="c_6018"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.623" cy="320.662" rx="13.582" ry="12.517" id="c_6021"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.031" cy="234.094" rx="13.582" ry="12.517" id="c_6021"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 41 KiB |
1983
src/assets/svg/compressorA_rev.svg
Normal file
|
After Width: | Height: | Size: 177 KiB |
1983
src/assets/svg/compressorB_rev.svg
Normal file
|
After Width: | Height: | Size: 177 KiB |
1983
src/assets/svg/compressorC_rev.svg
Normal file
|
After Width: | Height: | Size: 177 KiB |
443
src/assets/svg/overview-airdryer.svg
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com" viewBox="0 0 950 500">
|
||||||
|
<defs>
|
||||||
|
<bx:grid x="0" y="0" width="25" height="25"/>
|
||||||
|
</defs>
|
||||||
|
<rect x="12.226" y="12.005" width="924.818" height="476.396" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"/>
|
||||||
|
<rect x="25" y="75" width="900" height="400" style="stroke: rgb(0, 0, 0); fill: rgb(255, 255, 255);"/>
|
||||||
|
<rect x="50" y="100.548" width="100.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="50" y="125" width="100.168" height="50" style="stroke: rgb(0, 0, 0); stroke-width: 0.693; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.693;" cx="125.124" cy="137.355" rx="11.269" ry="10.987"/>
|
||||||
|
<g transform="matrix(1.13391, 0, 0, 1.234446, -9.410634, 162.99009)" style="">
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 363.181 109.151 L 363.181 110.989 L 460.721 110.989 L 469.183 107.313 L 469.183 105.958 L 460.721 109.151 L 363.181 109.151 Z"/>
|
||||||
|
<path style="fill: rgb(115, 135, 166); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 361.061 109.151 L 361.061 53.909 L 462.836 53.909 L 462.836 109.151 L 361.061 109.151 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 445.801 105.475 L 445.801 57.586 L 454.374 57.586 L 454.374 105.475 L 445.801 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 433.101 105.475 L 433.101 57.586 L 441.571 57.586 L 441.571 105.475 L 433.101 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 420.411 105.475 L 420.411 57.586 L 428.871 57.586 L 428.871 105.475 L 420.411 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 407.721 105.475 L 407.721 57.586 L 416.181 57.586 L 416.181 105.475 L 407.721 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 395.021 105.475 L 395.021 57.586 L 403.481 57.586 L 403.481 105.475 L 395.021 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 382.221 105.475 L 382.221 57.586 L 390.791 57.586 L 390.791 105.475 L 382.221 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 369.521 105.475 L 369.521 57.586 L 377.991 57.586 L 377.991 105.475 L 369.521 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 371.641 81.579 L 371.641 76.064 L 375.871 76.064 L 375.871 81.579 L 371.641 81.579 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 371.531 94.446 L 371.531 88.931 L 375.871 88.931 L 375.871 94.446 L 371.531 94.446 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 371.641 87.093 L 371.641 83.417 L 375.981 83.417 L 375.981 87.093 L 371.641 87.093 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 371.641 99.96 L 371.641 96.284 L 375.871 96.284 L 375.871 99.96 L 371.641 99.96 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 384.331 81.579 L 384.331 76.064 L 388.561 76.064 L 388.561 81.579 L 384.331 81.579 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 384.331 94.446 L 384.331 88.931 L 388.561 88.931 L 388.561 94.446 L 384.331 94.446 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 384.331 87.093 L 384.331 83.417 L 388.671 83.417 L 388.671 87.093 L 384.331 87.093 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 384.331 99.96 L 384.331 96.284 L 388.561 96.284 L 388.561 99.96 L 384.331 99.96 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 397.031 81.579 L 397.031 76.064 L 401.371 76.064 L 401.371 81.579 L 397.031 81.579 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 397.031 94.446 L 397.031 88.931 L 401.261 88.931 L 401.261 94.446 L 397.031 94.446 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 397.141 87.093 L 397.141 83.417 L 401.371 83.417 L 401.371 87.093 L 397.141 87.093 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 397.031 99.96 L 397.031 96.284 L 401.371 96.284 L 401.371 99.96 L 397.031 99.96 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 385.451 105.475 L 385.451 101.798 L 387.561 101.798 L 387.561 105.475 L 385.451 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 372.751 105.475 L 372.751 101.798 L 374.871 101.798 L 374.871 105.475 L 372.751 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 372.751 61.262 L 372.751 57.586 L 374.871 57.586 L 374.871 61.262 L 372.751 61.262 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 385.451 61.262 L 385.451 57.586 L 387.561 57.586 L 387.561 61.262 L 385.451 61.262 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 398.141 61.262 L 398.141 57.586 L 400.261 57.586 L 400.261 61.262 L 398.141 61.262 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 398.141 105.475 L 398.141 101.798 L 400.261 101.798 L 400.261 105.475 L 398.141 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 80.611 L 451.141 80.611 L 451.141 82.449 L 449.031 82.449 L 449.031 80.611 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 76.935 L 451.141 76.935 L 451.141 78.87 L 449.031 78.87 L 449.031 76.935 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 84.287 L 451.141 84.287 L 451.141 86.126 L 449.031 86.126 L 449.031 84.287 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 73.258 L 451.141 73.258 L 451.141 75.097 L 449.031 75.097 L 449.031 73.258 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 87.964 L 451.141 87.964 L 451.141 89.802 L 449.031 89.802 L 449.031 87.964 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 69.582 L 451.141 69.582 L 451.141 71.42 L 449.031 71.42 L 449.031 69.582 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 91.64 L 451.141 91.64 L 451.141 93.478 L 449.031 93.478 L 449.031 91.64 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 80.611 L 438.451 80.611 L 438.451 82.449 L 436.331 82.449 L 436.331 80.611 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 76.935 L 438.451 76.935 L 438.451 78.87 L 436.331 78.87 L 436.331 76.935 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 84.287 L 438.451 84.287 L 438.451 86.126 L 436.331 86.126 L 436.331 84.287 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 73.258 L 438.451 73.258 L 438.451 75.097 L 436.331 75.097 L 436.331 73.258 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 87.964 L 438.451 87.964 L 438.451 89.802 L 436.331 89.802 L 436.331 87.964 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 69.582 L 438.451 69.582 L 438.451 71.42 L 436.331 71.42 L 436.331 69.582 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 91.64 L 438.451 91.64 L 438.451 93.478 L 436.331 93.478 L 436.331 91.64 Z"/>
|
||||||
|
<path style="fill: rgb(89, 109, 140); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 462.836 53.909 L 471.299 50.233 L 471.299 105.475 L 462.836 109.151 L 462.836 53.909 Z"/>
|
||||||
|
<path style="fill: rgb(191, 211, 242); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 462.836 53.909 L 471.299 50.233 L 369.521 50.233 L 361.061 53.909 L 462.836 53.909 Z"/>
|
||||||
|
</g>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre;" x="56" y="116.982">AirDryer A</text>
|
||||||
|
<rect x="50" y="150" width="100" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="56.368" y="141.716">On/Off</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="120.949" y="168.123">H</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="56.007" y="167.208">####</text>
|
||||||
|
<rect x="49.832" y="225.548" width="100.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="49.832" y="250" width="100.168" height="50" style="stroke: rgb(0, 0, 0); stroke-width: 0.693; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.693;" cx="124.956" cy="262.355" rx="11.269" ry="10.987"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="55.832" y="241.982">AirDryer B</text>
|
||||||
|
<rect x="49.832" y="275" width="100" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="56.2" y="266.716">On/Off</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="120.781" y="293.123">H</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="55.839" y="292.208">####</text>
|
||||||
|
<rect x="49.832" y="350.548" width="100.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="49.832" y="375" width="100.168" height="50" style="stroke: rgb(0, 0, 0); stroke-width: 0.693; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.693;" cx="124.956" cy="387.355" rx="11.269" ry="10.987"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="55.832" y="366.982">AirDryer C</text>
|
||||||
|
<rect x="49.832" y="400" width="100" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="56.2" y="391.716">On/Off</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="120.781" y="418.123">H</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="55.839" y="417.208">####</text>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(0, 4, 255);" d="M 250 125 L 350 125 L 350 275 L 400 275"/>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(0, 4, 255);" d="M 250 250 L 350 250 L 350 275 L 400 275"/>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(0, 4, 255);" d="M 250 375 L 350 375 L 350 275 L 400 275"/>
|
||||||
|
<g transform="matrix(1, 0, 0, 1, 49.999999, -99.999998)" style="">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 292.93 L 717.857 292.651 L 717.857 292.442 L 717.857 292.163 L 717.857 291.954 L 717.857 291.744 L 717.857 291.535 L 717.857 291.396 L 717.914 291.186 L 717.914 291.046 L 717.972 290.907 L 718.029 290.837 L 718.086 290.698 L 718.143 290.558 L 718.201 290.488 L 718.258 290.419 L 718.372 290.349 L 718.487 290.279 L 718.602 290.209 L 718.716 290.14 L 718.888 290.14 L 719.06 290.07 L 719.231 290.07 L 719.46 290 L 719.632 290 L 719.919 290 L 720.147 290 L 720.434 290 L 720.72 290 L 721.063 290 L 721.407 290 L 721.751 290 L 722.151 290 L 770.706 290 L 771.106 290 L 771.45 290 L 771.794 290 L 772.137 290 L 772.423 290 L 772.71 290 L 772.939 290 L 773.225 290 L 773.397 290.07 L 773.626 290.07 L 773.798 290.14 L 773.969 290.14 L 774.141 290.209 L 774.256 290.279 L 774.37 290.349 L 774.485 290.419 L 774.599 290.488 L 774.657 290.558 L 774.714 290.698 L 774.771 290.837 L 774.828 290.977 L 774.886 291.117 L 774.943 291.256 L 774.943 291.465 L 775 291.674 L 775 291.884 L 775 292.093 L 775 292.303 L 775 292.581 L 775 292.86 L 775 293.209 L 775 293.488 L 775 335.558 L 775 335.907 L 775 336.256 L 775 336.604 L 775 336.884 L 775 337.163 L 775 337.442 L 775 337.72 L 775 337.93 L 774.943 338.14 L 774.943 338.349 L 774.943 338.488 L 774.886 338.628 L 774.886 338.767 L 774.828 338.907 L 774.771 339.047 L 774.714 339.117 L 774.657 339.256 L 774.599 339.326 L 774.485 339.396 L 774.427 339.465 L 774.313 339.465 L 774.198 339.535 L 774.084 339.535 L 773.969 339.604 L 773.798 339.604 L 773.683 339.604 L 773.511 339.604 L 773.339 339.674 L 773.168 339.674 L 772.939 339.674 L 772.71 339.674 L 772.481 339.674 L 721.579 339.674 L 721.235 339.674 L 720.892 339.674 L 720.663 339.674 L 720.377 339.674 L 720.09 339.604 L 719.861 339.604 L 719.69 339.604 L 719.46 339.604 L 719.289 339.535 L 719.117 339.535 L 718.945 339.465 L 718.831 339.465 L 718.659 339.396 L 718.544 339.326 L 718.43 339.256 L 718.372 339.117 L 718.258 339.047 L 718.201 338.907 L 718.143 338.767 L 718.086 338.628 L 718.029 338.488 L 717.972 338.349 L 717.914 338.14 L 717.914 337.93 L 717.914 337.72 L 717.857 337.442 L 717.857 337.163 L 717.857 336.884 L 717.857 336.604 L 717.857 336.256 L 717.857 335.907 L 717.857 335.558 L 717.857 292.93 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 732.114 339.674 L 760.743 339.674 L 760.743 342.884 L 732.114 342.884 L 732.114 339.674 Z"/>
|
||||||
|
<path style="fill: rgb(67, 67, 67); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 722.151 299.419 L 722.151 299.07 L 722.151 298.791 L 722.151 298.442 L 722.151 298.163 L 722.151 297.954 L 722.151 297.675 L 722.209 297.465 L 722.209 297.256 L 722.209 297.046 L 722.266 296.837 L 722.323 296.698 L 722.323 296.558 L 722.381 296.419 L 722.495 296.279 L 722.553 296.209 L 722.61 296.07 L 722.725 296 L 722.838 295.93 L 722.953 295.86 L 723.067 295.791 L 723.239 295.791 L 723.354 295.721 L 723.526 295.721 L 723.754 295.651 L 723.926 295.651 L 724.155 295.651 L 724.385 295.651 L 724.671 295.651 L 724.9 295.651 L 725.243 295.651 L 725.53 295.651 L 725.873 295.651 L 767.557 295.651 L 767.843 295.651 L 768.129 295.651 L 768.358 295.651 L 768.587 295.651 L 768.816 295.651 L 769.045 295.721 L 769.217 295.721 L 769.389 295.791 L 769.561 295.791 L 769.675 295.86 L 769.79 295.86 L 769.962 295.93 L 770.076 296 L 770.133 296.07 L 770.247 296.139 L 770.305 296.209 L 770.362 296.349 L 770.419 296.419 L 770.477 296.558 L 770.534 296.698 L 770.591 296.837 L 770.591 296.907 L 770.649 297.046 L 770.649 297.256 L 770.649 297.396 L 770.706 297.604 L 770.706 297.744 L 770.706 297.954 L 770.706 298.163 L 770.706 298.442 L 770.706 298.651 L 770.706 298.861 L 770.706 327.744 L 770.706 328.093 L 770.706 328.372 L 770.706 328.581 L 770.706 328.861 L 770.763 329.07 L 770.763 329.279 L 770.763 329.488 L 770.763 329.628 L 770.763 329.838 L 770.763 329.977 L 770.706 330.117 L 770.706 330.186 L 770.649 330.326 L 770.649 330.465 L 770.591 330.535 L 770.534 330.604 L 770.477 330.674 L 770.419 330.744 L 770.305 330.814 L 770.19 330.883 L 770.076 330.883 L 769.962 330.883 L 769.79 330.953 L 769.618 330.953 L 769.446 330.953 L 769.274 331.023 L 769.045 331.023 L 768.759 331.023 L 768.53 331.023 L 768.244 331.023 L 767.9 331.023 L 767.557 331.023 L 725.873 331.023 L 725.53 331.023 L 725.243 331.023 L 724.9 331.023 L 724.671 331.023 L 724.385 331.023 L 724.155 331.023 L 723.926 331.023 L 723.754 331.023 L 723.526 331.023 L 723.354 331.023 L 723.239 331.023 L 723.067 330.953 L 722.953 330.953 L 722.838 330.883 L 722.725 330.814 L 722.61 330.744 L 722.553 330.674 L 722.495 330.604 L 722.381 330.465 L 722.323 330.326 L 722.323 330.186 L 722.266 330.046 L 722.209 329.838 L 722.209 329.628 L 722.209 329.419 L 722.151 329.209 L 722.151 328.93 L 722.151 328.651 L 722.151 328.302 L 722.151 327.954 L 722.151 327.604 L 722.151 327.186 L 722.151 299.419 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 350 L 775 350 L 775 347.558 L 760.743 342.884 L 732.114 342.884 L 717.857 347.558 L 717.857 350 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 770.133 334.023 L 770.133 337.512 L 756.391 337.512 L 756.391 334.023 L 770.133 334.023 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 347.558 L 775 347.558"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 675 285.361 L 703.571 285.361 L 703.571 350 L 675 350 L 675 285.361 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 287.463 L 701.169 287.463 L 701.169 350 L 677.317 350 L 677.317 287.463 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 290.616 L 701.169 290.616 L 701.169 297.898 L 677.317 297.898 L 677.317 290.616 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 297.898 L 701.169 297.898 L 701.169 305.181 L 677.317 305.181 L 677.317 297.898 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 293.694 L 698.767 293.694 L 698.767 294.745 L 679.72 294.745 L 679.72 293.694 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 300 L 698.767 300 L 698.767 303.078 L 679.72 303.078 L 679.72 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 275 L 698.767 275 L 703.571 285.361 L 675 285.361 L 679.72 275 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 695.163 300 L 701.169 300 L 701.169 303.078 L 695.163 303.078 L 695.163 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 697.566 300 L 698.767 300 L 698.767 303.078 L 697.566 303.078 L 697.566 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 697.566 291.592 L 692.847 291.592 L 692.847 292.643 L 697.566 292.643 L 697.566 291.592 Z"/>
|
||||||
|
<circle style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="890.673" cy="114.148" r="1.051" transform="matrix(1.142857, 0, 0, 1.000013, -320.346635, 178.493448)"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(6, 255, 0);" d="M 525 250 L 575 250 L 575 200 L 725 200"/>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(6, 255, 0);" d="M 525 250 L 575 250 L 575 400 L 725 400"/>
|
||||||
|
<rect x="399.832" y="149.864" width="125.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="400" y="175" width="125" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="425.352" y="167.628">PLC AirDryer</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="426.101" y="192.839">IP : 192.168.0.3</text>
|
||||||
|
<rect x="725" y="99.864" width="125.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="725.168" y="125" width="125" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="759.52" y="117.628">PC Station</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="751.269" y="142.839">IP : 192.168.0.2</text>
|
||||||
|
<g transform="matrix(1, 0, 0, 1, 49.999999, 99.999998)" style="">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 292.93 L 717.857 292.651 L 717.857 292.442 L 717.857 292.163 L 717.857 291.954 L 717.857 291.744 L 717.857 291.535 L 717.857 291.396 L 717.914 291.186 L 717.914 291.046 L 717.972 290.907 L 718.029 290.837 L 718.086 290.698 L 718.143 290.558 L 718.201 290.488 L 718.258 290.419 L 718.372 290.349 L 718.487 290.279 L 718.602 290.209 L 718.716 290.14 L 718.888 290.14 L 719.06 290.07 L 719.231 290.07 L 719.46 290 L 719.632 290 L 719.919 290 L 720.147 290 L 720.434 290 L 720.72 290 L 721.063 290 L 721.407 290 L 721.751 290 L 722.151 290 L 770.706 290 L 771.106 290 L 771.45 290 L 771.794 290 L 772.137 290 L 772.423 290 L 772.71 290 L 772.939 290 L 773.225 290 L 773.397 290.07 L 773.626 290.07 L 773.798 290.14 L 773.969 290.14 L 774.141 290.209 L 774.256 290.279 L 774.37 290.349 L 774.485 290.419 L 774.599 290.488 L 774.657 290.558 L 774.714 290.698 L 774.771 290.837 L 774.828 290.977 L 774.886 291.117 L 774.943 291.256 L 774.943 291.465 L 775 291.674 L 775 291.884 L 775 292.093 L 775 292.303 L 775 292.581 L 775 292.86 L 775 293.209 L 775 293.488 L 775 335.558 L 775 335.907 L 775 336.256 L 775 336.604 L 775 336.884 L 775 337.163 L 775 337.442 L 775 337.72 L 775 337.93 L 774.943 338.14 L 774.943 338.349 L 774.943 338.488 L 774.886 338.628 L 774.886 338.767 L 774.828 338.907 L 774.771 339.047 L 774.714 339.117 L 774.657 339.256 L 774.599 339.326 L 774.485 339.396 L 774.427 339.465 L 774.313 339.465 L 774.198 339.535 L 774.084 339.535 L 773.969 339.604 L 773.798 339.604 L 773.683 339.604 L 773.511 339.604 L 773.339 339.674 L 773.168 339.674 L 772.939 339.674 L 772.71 339.674 L 772.481 339.674 L 721.579 339.674 L 721.235 339.674 L 720.892 339.674 L 720.663 339.674 L 720.377 339.674 L 720.09 339.604 L 719.861 339.604 L 719.69 339.604 L 719.46 339.604 L 719.289 339.535 L 719.117 339.535 L 718.945 339.465 L 718.831 339.465 L 718.659 339.396 L 718.544 339.326 L 718.43 339.256 L 718.372 339.117 L 718.258 339.047 L 718.201 338.907 L 718.143 338.767 L 718.086 338.628 L 718.029 338.488 L 717.972 338.349 L 717.914 338.14 L 717.914 337.93 L 717.914 337.72 L 717.857 337.442 L 717.857 337.163 L 717.857 336.884 L 717.857 336.604 L 717.857 336.256 L 717.857 335.907 L 717.857 335.558 L 717.857 292.93 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 732.114 339.674 L 760.743 339.674 L 760.743 342.884 L 732.114 342.884 L 732.114 339.674 Z"/>
|
||||||
|
<path style="fill: rgb(67, 67, 67); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 722.151 299.419 L 722.151 299.07 L 722.151 298.791 L 722.151 298.442 L 722.151 298.163 L 722.151 297.954 L 722.151 297.675 L 722.209 297.465 L 722.209 297.256 L 722.209 297.046 L 722.266 296.837 L 722.323 296.698 L 722.323 296.558 L 722.381 296.419 L 722.495 296.279 L 722.553 296.209 L 722.61 296.07 L 722.725 296 L 722.838 295.93 L 722.953 295.86 L 723.067 295.791 L 723.239 295.791 L 723.354 295.721 L 723.526 295.721 L 723.754 295.651 L 723.926 295.651 L 724.155 295.651 L 724.385 295.651 L 724.671 295.651 L 724.9 295.651 L 725.243 295.651 L 725.53 295.651 L 725.873 295.651 L 767.557 295.651 L 767.843 295.651 L 768.129 295.651 L 768.358 295.651 L 768.587 295.651 L 768.816 295.651 L 769.045 295.721 L 769.217 295.721 L 769.389 295.791 L 769.561 295.791 L 769.675 295.86 L 769.79 295.86 L 769.962 295.93 L 770.076 296 L 770.133 296.07 L 770.247 296.139 L 770.305 296.209 L 770.362 296.349 L 770.419 296.419 L 770.477 296.558 L 770.534 296.698 L 770.591 296.837 L 770.591 296.907 L 770.649 297.046 L 770.649 297.256 L 770.649 297.396 L 770.706 297.604 L 770.706 297.744 L 770.706 297.954 L 770.706 298.163 L 770.706 298.442 L 770.706 298.651 L 770.706 298.861 L 770.706 327.744 L 770.706 328.093 L 770.706 328.372 L 770.706 328.581 L 770.706 328.861 L 770.763 329.07 L 770.763 329.279 L 770.763 329.488 L 770.763 329.628 L 770.763 329.838 L 770.763 329.977 L 770.706 330.117 L 770.706 330.186 L 770.649 330.326 L 770.649 330.465 L 770.591 330.535 L 770.534 330.604 L 770.477 330.674 L 770.419 330.744 L 770.305 330.814 L 770.19 330.883 L 770.076 330.883 L 769.962 330.883 L 769.79 330.953 L 769.618 330.953 L 769.446 330.953 L 769.274 331.023 L 769.045 331.023 L 768.759 331.023 L 768.53 331.023 L 768.244 331.023 L 767.9 331.023 L 767.557 331.023 L 725.873 331.023 L 725.53 331.023 L 725.243 331.023 L 724.9 331.023 L 724.671 331.023 L 724.385 331.023 L 724.155 331.023 L 723.926 331.023 L 723.754 331.023 L 723.526 331.023 L 723.354 331.023 L 723.239 331.023 L 723.067 330.953 L 722.953 330.953 L 722.838 330.883 L 722.725 330.814 L 722.61 330.744 L 722.553 330.674 L 722.495 330.604 L 722.381 330.465 L 722.323 330.326 L 722.323 330.186 L 722.266 330.046 L 722.209 329.838 L 722.209 329.628 L 722.209 329.419 L 722.151 329.209 L 722.151 328.93 L 722.151 328.651 L 722.151 328.302 L 722.151 327.954 L 722.151 327.604 L 722.151 327.186 L 722.151 299.419 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 350 L 775 350 L 775 347.558 L 760.743 342.884 L 732.114 342.884 L 717.857 347.558 L 717.857 350 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 770.133 334.023 L 770.133 337.512 L 756.391 337.512 L 756.391 334.023 L 770.133 334.023 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 347.558 L 775 347.558"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 675 285.361 L 703.571 285.361 L 703.571 350 L 675 350 L 675 285.361 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 287.463 L 701.169 287.463 L 701.169 350 L 677.317 350 L 677.317 287.463 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 290.616 L 701.169 290.616 L 701.169 297.898 L 677.317 297.898 L 677.317 290.616 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 297.898 L 701.169 297.898 L 701.169 305.181 L 677.317 305.181 L 677.317 297.898 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 293.694 L 698.767 293.694 L 698.767 294.745 L 679.72 294.745 L 679.72 293.694 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 300 L 698.767 300 L 698.767 303.078 L 679.72 303.078 L 679.72 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 275 L 698.767 275 L 703.571 285.361 L 675 285.361 L 679.72 275 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 695.163 300 L 701.169 300 L 701.169 303.078 L 695.163 303.078 L 695.163 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 697.566 300 L 698.767 300 L 698.767 303.078 L 697.566 303.078 L 697.566 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 697.566 291.592 L 692.847 291.592 L 692.847 292.643 L 697.566 292.643 L 697.566 291.592 Z"/>
|
||||||
|
<circle style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="890.673" cy="114.148" r="1.051" transform="matrix(1.142857, 0, 0, 1.000013, -320.346635, 178.493448)"/>
|
||||||
|
</g>
|
||||||
|
<rect x="724.832" y="299.864" width="125.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="725" y="325" width="125" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="760.352" y="317.628">PC Server</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="751.1" y="317.975" transform="matrix(1, 0, 0, 1, -3.000031, 24.863983)">IP : xxx.xxx.xx.xx<tspan x="751.0999755859375" dy="1em"></tspan></text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 30px; white-space: pre; stroke-width: 1;" x="316.458" y="50.984">OVERVIEW AIR DRYER</text>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(0, 0, 0);" d="M 625 75 L 625 114.125 L 625 264.125 L 625 475"/>
|
||||||
|
<g transform="matrix(0.999999, 0, 0, 0.888921, -1058.006891, 44.212168)" style="">
|
||||||
|
<g id="Group_Base" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M4.392,105.741h62.834v2.253H4.392V105.741z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M0,112.498l1.014-1.126H2.14v-11.261H1.014L0,98.984h4.392v13.514H0z "/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M71.957,98.984l-1.127,1.127h-1.125v11.261h1.125l1.127,1.126h-4.504 V98.984H71.957z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_Supports" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M11.148,89.977h49.547v13.514H11.148V89.977z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M4.392,76.463h4.504v27.026H4.392V76.463z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M62.948,76.463h4.278v27.026h-4.278V76.463z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,102.363h5.631v-6.758h-5.631h-0.337l-0.451,0.338 l-0.338,0.339l-0.338,0.563l-0.338,0.563l-0.225,0.563l-0.112,0.563l-0.113,0.45l0.113,0.451l0.112,0.563l0.225,0.563l0.338,0.563 l0.338,0.449l0.338,0.338l0.451,0.338L15.652,102.363z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,102.363h-5.631v-6.758h5.631h0.451l0.338,0.338l0.449,0.339 l0.338,0.563l0.226,0.563l0.226,0.563l0.225,0.563v0.448v0.451l-0.225,0.563l-0.226,0.563l-0.226,0.563l-0.338,0.449l-0.449,0.338 l-0.338,0.338L56.191,102.363z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_Pipes" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="341.6875" y1="-275.623" x2="346.1914" y2="-275.623" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_1_)" d="M56.191,6.871h-4.504v4.504h4.504V6.871z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,11.375V6.871h-4.504v4.504H56.191z"/>
|
||||||
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="305.6523" y1="-275.623" x2="310.1572" y2="-275.623" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_2_)" d="M20.157,6.871h-4.504v4.504h4.504V6.871z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M20.157,11.375V6.871h-4.504v4.504H20.157z"/>
|
||||||
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="323.6699" y1="-283.5059" x2="328.1738" y2="-283.5059" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_3_)" stroke="#4C4C4C" stroke-width="0.25" d="M38.174,6.871H33.67v20.27h4.504V6.871z"/>
|
||||||
|
<path fill="#999999" d="M58.443,6.871V4.619H13.4v2.252H58.443z"/>
|
||||||
|
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="325.9219" y1="-271.0625" x2="325.9219" y2="-273.2832" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_4_)" stroke="#4C4C4C" stroke-width="0.25" d="M13.4,6.871h45.043V4.619H13.4V6.871"/>
|
||||||
|
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="325.9219" y1="-362.064" x2="325.9219" y2="-368.8364" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_5_)" stroke="#4C4C4C" stroke-width="0.25" d="M21.283,95.605h29.277v6.758H21.283V95.605z"/>
|
||||||
|
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="322.5439" y1="-355.3496" x2="329.3008" y2="-355.3496" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_6_)" stroke="#4C4C4C" stroke-width="0.25" d="M39.301,95.605V78.715h-6.757v16.893l0.112,0.676l0.226,0.563 l0.225,0.563l0.45,0.45l0.563,0.449l0.563,0.338l0.563,0.227l0.675,0.111l0.677-0.111l0.676-0.227l0.563-0.338l0.451-0.449 l0.449-0.45l0.339-0.563l0.112-0.563L39.301,95.605z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_Column2" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="298.8965" y1="-318.8652" x2="316.9141" y2="-318.8652" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#B2B2B2"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#E5E5E5"/>
|
||||||
|
<stop offset="1" style="stop-color:#B2B2B2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_7_)" d="M26.914,13.628V94.48H8.896V13.628c0,0,2.204-3.378,9.009-3.378 C25.176,10.25,26.914,13.628,26.914,13.628"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,13.628V94.48H8.896V13.628c0,0,1.972-3.378,9.043-3.378 C25.442,10.25,26.914,13.628,26.914,13.628z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_Column1" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="334.9316" y1="-318.7939" x2="352.9482" y2="-318.7939" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#B2B2B2"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#E5E5E5"/>
|
||||||
|
<stop offset="1" style="stop-color:#B2B2B2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_8_)" d="M62.948,13.628V94.48H44.932V13.628c0,0,1.692-3.52,8.67-3.52 C61.661,10.108,62.948,13.628,62.948,13.628z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M62.948,13.628V94.48H44.932V13.628c0,0,1.668-3.52,8.67-3.52 C61.589,10.108,62.948,13.628,62.948,13.628z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_Boards" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<circle fill="#666666" stroke="#4C4C4C" stroke-width="0.25" cx="31.981" cy="24.325" r="3.941"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M22.409,27.141h27.026v33.783H22.409V27.141z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M25.787,31.645h20.27v21.396h-20.27V31.645z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M25.787,57.545h20.27v21.17h-20.27V57.545z"/>
|
||||||
|
<path fill="#7F7F7F" stroke="#4C4C4C" stroke-width="0.25" d="M37.049,60.924h6.756v7.656h-6.756V60.924z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_Points" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,55.293h3.378v4.504h-3.378V55.293z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,58.671h3.378v4.505h-3.378V58.671z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,58.671h3.378v1.126h-3.378V58.671z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,55.293h3.378v1.126h-3.378V55.293z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,47.41h3.378v4.504h-3.378V47.41z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,47.41h3.378v1.126h-3.378V47.41z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M52.813,55.293h3.379v4.504h-3.379V55.293z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M52.813,55.293h3.379v1.126h-3.379V55.293z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_Connectors" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,1.917h0.676v43.692h-0.676V1.917z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,45.608h-7.207v-0.45h7.207V45.608z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,44.032h-7.207v-0.676h7.207V44.032z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,41.78h-7.207v-0.676h7.207V41.78z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,2.367H23.085v-0.45h30.179V2.367z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M23.085,2.367h0.45v2.252h-0.45V2.367z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,22.636h-40.54v-0.675h40.54V22.636z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M55.516,22.636h0.676v6.757h-0.676V22.636z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,22.636h0.676v6.757h-0.676V22.636z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M27.364,0.002l1.577,1.464l-2.478,2.478l-1.576-1.577L27.364,0.002z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M31.418,1.466l1.576-1.464l2.252,2.365L33.67,3.943L31.418,1.466z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.999999, 0, 0, 0.888921, -1061.049982, 169.212228)" style="">
|
||||||
|
<g id="group-1" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M4.392,105.741h62.834v2.253H4.392V105.741z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M0,112.498l1.014-1.126H2.14v-11.261H1.014L0,98.984h4.392v13.514H0z "/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M71.957,98.984l-1.127,1.127h-1.125v11.261h1.125l1.127,1.126h-4.504 V98.984H71.957z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-2" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M11.148,89.977h49.547v13.514H11.148V89.977z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M4.392,76.463h4.504v27.026H4.392V76.463z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M62.948,76.463h4.278v27.026h-4.278V76.463z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,102.363h5.631v-6.758h-5.631h-0.337l-0.451,0.338 l-0.338,0.339l-0.338,0.563l-0.338,0.563l-0.225,0.563l-0.112,0.563l-0.113,0.45l0.113,0.451l0.112,0.563l0.225,0.563l0.338,0.563 l0.338,0.449l0.338,0.338l0.451,0.338L15.652,102.363z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,102.363h-5.631v-6.758h5.631h0.451l0.338,0.338l0.449,0.339 l0.338,0.563l0.226,0.563l0.226,0.563l0.225,0.563v0.448v0.451l-0.225,0.563l-0.226,0.563l-0.226,0.563l-0.338,0.449l-0.449,0.338 l-0.338,0.338L56.191,102.363z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-3" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="gradient-1" gradientUnits="userSpaceOnUse" x1="341.6875" y1="-275.623" x2="346.1914" y2="-275.623" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-1)" d="M56.191,6.871h-4.504v4.504h4.504V6.871z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,11.375V6.871h-4.504v4.504H56.191z"/>
|
||||||
|
<linearGradient id="gradient-2" gradientUnits="userSpaceOnUse" x1="305.6523" y1="-275.623" x2="310.1572" y2="-275.623" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-2)" d="M20.157,6.871h-4.504v4.504h4.504V6.871z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M20.157,11.375V6.871h-4.504v4.504H20.157z"/>
|
||||||
|
<linearGradient id="gradient-3" gradientUnits="userSpaceOnUse" x1="323.6699" y1="-283.5059" x2="328.1738" y2="-283.5059" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-3)" stroke="#4C4C4C" stroke-width="0.25" d="M38.174,6.871H33.67v20.27h4.504V6.871z"/>
|
||||||
|
<path fill="#999999" d="M58.443,6.871V4.619H13.4v2.252H58.443z"/>
|
||||||
|
<linearGradient id="gradient-4" gradientUnits="userSpaceOnUse" x1="325.9219" y1="-271.0625" x2="325.9219" y2="-273.2832" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-4)" stroke="#4C4C4C" stroke-width="0.25" d="M13.4,6.871h45.043V4.619H13.4V6.871"/>
|
||||||
|
<linearGradient id="gradient-5" gradientUnits="userSpaceOnUse" x1="325.9219" y1="-362.064" x2="325.9219" y2="-368.8364" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-5)" stroke="#4C4C4C" stroke-width="0.25" d="M21.283,95.605h29.277v6.758H21.283V95.605z"/>
|
||||||
|
<linearGradient id="gradient-6" gradientUnits="userSpaceOnUse" x1="322.5439" y1="-355.3496" x2="329.3008" y2="-355.3496" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-6)" stroke="#4C4C4C" stroke-width="0.25" d="M39.301,95.605V78.715h-6.757v16.893l0.112,0.676l0.226,0.563 l0.225,0.563l0.45,0.45l0.563,0.449l0.563,0.338l0.563,0.227l0.675,0.111l0.677-0.111l0.676-0.227l0.563-0.338l0.451-0.449 l0.449-0.45l0.339-0.563l0.112-0.563L39.301,95.605z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-4" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="gradient-7" gradientUnits="userSpaceOnUse" x1="298.8965" y1="-318.8652" x2="316.9141" y2="-318.8652" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#B2B2B2"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#E5E5E5"/>
|
||||||
|
<stop offset="1" style="stop-color:#B2B2B2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-7)" d="M26.914,13.628V94.48H8.896V13.628c0,0,2.204-3.378,9.009-3.378 C25.176,10.25,26.914,13.628,26.914,13.628"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,13.628V94.48H8.896V13.628c0,0,1.972-3.378,9.043-3.378 C25.442,10.25,26.914,13.628,26.914,13.628z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-5" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="gradient-8" gradientUnits="userSpaceOnUse" x1="334.9316" y1="-318.7939" x2="352.9482" y2="-318.7939" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#B2B2B2"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#E5E5E5"/>
|
||||||
|
<stop offset="1" style="stop-color:#B2B2B2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-8)" d="M62.948,13.628V94.48H44.932V13.628c0,0,1.692-3.52,8.67-3.52 C61.661,10.108,62.948,13.628,62.948,13.628z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M62.948,13.628V94.48H44.932V13.628c0,0,1.668-3.52,8.67-3.52 C61.589,10.108,62.948,13.628,62.948,13.628z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-6" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<circle fill="#666666" stroke="#4C4C4C" stroke-width="0.25" cx="31.981" cy="24.325" r="3.941"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M22.409,27.141h27.026v33.783H22.409V27.141z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M25.787,31.645h20.27v21.396h-20.27V31.645z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M25.787,57.545h20.27v21.17h-20.27V57.545z"/>
|
||||||
|
<path fill="#7F7F7F" stroke="#4C4C4C" stroke-width="0.25" d="M37.049,60.924h6.756v7.656h-6.756V60.924z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-7" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,55.293h3.378v4.504h-3.378V55.293z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,58.671h3.378v4.505h-3.378V58.671z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,58.671h3.378v1.126h-3.378V58.671z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,55.293h3.378v1.126h-3.378V55.293z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,47.41h3.378v4.504h-3.378V47.41z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,47.41h3.378v1.126h-3.378V47.41z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M52.813,55.293h3.379v4.504h-3.379V55.293z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M52.813,55.293h3.379v1.126h-3.379V55.293z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-8" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,1.917h0.676v43.692h-0.676V1.917z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,45.608h-7.207v-0.45h7.207V45.608z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,44.032h-7.207v-0.676h7.207V44.032z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,41.78h-7.207v-0.676h7.207V41.78z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,2.367H23.085v-0.45h30.179V2.367z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M23.085,2.367h0.45v2.252h-0.45V2.367z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,22.636h-40.54v-0.675h40.54V22.636z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M55.516,22.636h0.676v6.757h-0.676V22.636z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,22.636h0.676v6.757h-0.676V22.636z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M27.364,0.002l1.577,1.464l-2.478,2.478l-1.576-1.577L27.364,0.002z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M31.418,1.466l1.576-1.464l2.252,2.365L33.67,3.943L31.418,1.466z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.999999, 0, 0, 0.888921, -1061.04986, 294.212174)" style="">
|
||||||
|
<g id="group-9" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M4.392,105.741h62.834v2.253H4.392V105.741z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M0,112.498l1.014-1.126H2.14v-11.261H1.014L0,98.984h4.392v13.514H0z "/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M71.957,98.984l-1.127,1.127h-1.125v11.261h1.125l1.127,1.126h-4.504 V98.984H71.957z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-10" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M11.148,89.977h49.547v13.514H11.148V89.977z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M4.392,76.463h4.504v27.026H4.392V76.463z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M62.948,76.463h4.278v27.026h-4.278V76.463z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,102.363h5.631v-6.758h-5.631h-0.337l-0.451,0.338 l-0.338,0.339l-0.338,0.563l-0.338,0.563l-0.225,0.563l-0.112,0.563l-0.113,0.45l0.113,0.451l0.112,0.563l0.225,0.563l0.338,0.563 l0.338,0.449l0.338,0.338l0.451,0.338L15.652,102.363z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,102.363h-5.631v-6.758h5.631h0.451l0.338,0.338l0.449,0.339 l0.338,0.563l0.226,0.563l0.226,0.563l0.225,0.563v0.448v0.451l-0.225,0.563l-0.226,0.563l-0.226,0.563l-0.338,0.449l-0.449,0.338 l-0.338,0.338L56.191,102.363z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-11" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="gradient-9" gradientUnits="userSpaceOnUse" x1="341.6875" y1="-275.623" x2="346.1914" y2="-275.623" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-9)" d="M56.191,6.871h-4.504v4.504h4.504V6.871z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,11.375V6.871h-4.504v4.504H56.191z"/>
|
||||||
|
<linearGradient id="gradient-10" gradientUnits="userSpaceOnUse" x1="305.6523" y1="-275.623" x2="310.1572" y2="-275.623" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-10)" d="M20.157,6.871h-4.504v4.504h4.504V6.871z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M20.157,11.375V6.871h-4.504v4.504H20.157z"/>
|
||||||
|
<linearGradient id="gradient-11" gradientUnits="userSpaceOnUse" x1="323.6699" y1="-283.5059" x2="328.1738" y2="-283.5059" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-11)" stroke="#4C4C4C" stroke-width="0.25" d="M38.174,6.871H33.67v20.27h4.504V6.871z"/>
|
||||||
|
<path fill="#999999" d="M58.443,6.871V4.619H13.4v2.252H58.443z"/>
|
||||||
|
<linearGradient id="gradient-12" gradientUnits="userSpaceOnUse" x1="325.9219" y1="-271.0625" x2="325.9219" y2="-273.2832" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-12)" stroke="#4C4C4C" stroke-width="0.25" d="M13.4,6.871h45.043V4.619H13.4V6.871"/>
|
||||||
|
<linearGradient id="gradient-13" gradientUnits="userSpaceOnUse" x1="325.9219" y1="-362.064" x2="325.9219" y2="-368.8364" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-13)" stroke="#4C4C4C" stroke-width="0.25" d="M21.283,95.605h29.277v6.758H21.283V95.605z"/>
|
||||||
|
<linearGradient id="gradient-14" gradientUnits="userSpaceOnUse" x1="322.5439" y1="-355.3496" x2="329.3008" y2="-355.3496" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#999999"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#CCCCCC"/>
|
||||||
|
<stop offset="1" style="stop-color:#999999"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-14)" stroke="#4C4C4C" stroke-width="0.25" d="M39.301,95.605V78.715h-6.757v16.893l0.112,0.676l0.226,0.563 l0.225,0.563l0.45,0.45l0.563,0.449l0.563,0.338l0.563,0.227l0.675,0.111l0.677-0.111l0.676-0.227l0.563-0.338l0.451-0.449 l0.449-0.45l0.339-0.563l0.112-0.563L39.301,95.605z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-12" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="gradient-15" gradientUnits="userSpaceOnUse" x1="298.8965" y1="-318.8652" x2="316.9141" y2="-318.8652" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#B2B2B2"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#E5E5E5"/>
|
||||||
|
<stop offset="1" style="stop-color:#B2B2B2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-15)" d="M26.914,13.628V94.48H8.896V13.628c0,0,2.204-3.378,9.009-3.378 C25.176,10.25,26.914,13.628,26.914,13.628"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,13.628V94.48H8.896V13.628c0,0,1.972-3.378,9.043-3.378 C25.442,10.25,26.914,13.628,26.914,13.628z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-13" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<linearGradient id="gradient-16" gradientUnits="userSpaceOnUse" x1="334.9316" y1="-318.7939" x2="352.9482" y2="-318.7939" gradientTransform="matrix(1 0 0 -1 -290 -266.5)">
|
||||||
|
<stop offset="0" style="stop-color:#B2B2B2"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#E5E5E5"/>
|
||||||
|
<stop offset="1" style="stop-color:#B2B2B2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#gradient-16)" d="M62.948,13.628V94.48H44.932V13.628c0,0,1.692-3.52,8.67-3.52 C61.661,10.108,62.948,13.628,62.948,13.628z"/>
|
||||||
|
<path fill="none" stroke="#4C4C4C" stroke-width="0.25" d="M62.948,13.628V94.48H44.932V13.628c0,0,1.668-3.52,8.67-3.52 C61.589,10.108,62.948,13.628,62.948,13.628z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-14" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<circle fill="#666666" stroke="#4C4C4C" stroke-width="0.25" cx="31.981" cy="24.325" r="3.941"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M22.409,27.141h27.026v33.783H22.409V27.141z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M25.787,31.645h20.27v21.396h-20.27V31.645z"/>
|
||||||
|
<path fill="#4C4C4C" stroke="#4C4C4C" stroke-width="0.25" d="M25.787,57.545h20.27v21.17h-20.27V57.545z"/>
|
||||||
|
<path fill="#7F7F7F" stroke="#4C4C4C" stroke-width="0.25" d="M37.049,60.924h6.756v7.656h-6.756V60.924z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-15" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,55.293h3.378v4.504h-3.378V55.293z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,58.671h3.378v4.505h-3.378V58.671z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,58.671h3.378v1.126h-3.378V58.671z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,55.293h3.378v1.126h-3.378V55.293z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,47.41h3.378v4.504h-3.378V47.41z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M26.914,47.41h3.378v1.126h-3.378V47.41z"/>
|
||||||
|
<path fill="#FFFFFF" stroke="#4C4C4C" stroke-width="0.25" d="M52.813,55.293h3.379v4.504h-3.379V55.293z"/>
|
||||||
|
<path fill="#7F0000" stroke="#4C4C4C" stroke-width="0.25" d="M52.813,55.293h3.379v1.126h-3.379V55.293z"/>
|
||||||
|
</g>
|
||||||
|
<g id="group-16" transform="matrix(1, 0, 0, 1, 1236.05109, 62.756969)">
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,1.917h0.676v43.692h-0.676V1.917z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,45.608h-7.207v-0.45h7.207V45.608z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,44.032h-7.207v-0.676h7.207V44.032z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,41.78h-7.207v-0.676h7.207V41.78z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M53.264,2.367H23.085v-0.45h30.179V2.367z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M23.085,2.367h0.45v2.252h-0.45V2.367z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M56.191,22.636h-40.54v-0.675h40.54V22.636z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M55.516,22.636h0.676v6.757h-0.676V22.636z"/>
|
||||||
|
<path fill="#666666" stroke="#4C4C4C" stroke-width="0.25" d="M15.652,22.636h0.676v6.757h-0.676V22.636z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M27.364,0.002l1.577,1.464l-2.478,2.478l-1.576-1.577L27.364,0.002z"/>
|
||||||
|
<path fill="#B2B2B2" stroke="#4C4C4C" stroke-width="0.25" d="M31.418,1.466l1.576-1.464l2.252,2.365L33.67,3.943L31.418,1.466z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 56 KiB |
251
src/assets/svg/overview-compressor.svg
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com" viewBox="0 0 950 500">
|
||||||
|
<defs>
|
||||||
|
<bx:grid x="0" y="0" width="25" height="25"/>
|
||||||
|
</defs>
|
||||||
|
<rect x="12.226" y="12.005" width="924.818" height="462.995" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"/>
|
||||||
|
<rect x="25" y="75" width="900" height="375" style="stroke: rgb(0, 0, 0); fill: rgb(255, 255, 255);"/>
|
||||||
|
<rect x="50" y="100.548" width="100.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="50" y="125" width="100.168" height="50" style="stroke: rgb(0, 0, 0); stroke-width: 0.693; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.693;" cx="125.124" cy="137.355" rx="11.269" ry="10.987"/>
|
||||||
|
<g transform="matrix(1.116151, 0, 0, 1.116686, -10.678418, 46.611243)" style="">
|
||||||
|
<path style="fill: rgb(204, 204, 204); stroke-width: 1;" d="M 166.356 47.81 L 278.348 47.81 L 278.348 114.973 L 166.356 114.973 L 166.356 47.81 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 166.356 95.477 L 278.348 67.396 L 278.348 114.973 L 166.356 114.973 L 166.356 95.477 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 166.356 98.16 L 278.348 70.258 L 278.348 114.973 L 166.356 114.973 L 166.356 98.16 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 166.356 102.453 L 278.348 74.371 L 278.348 114.973 L 166.356 114.973 L 166.356 102.453 Z"/>
|
||||||
|
<path style="fill: rgb(204, 204, 204); stroke-width: 1;" d="M 168.486 49.688 L 276.106 49.688 L 276.106 109.249 L 168.486 109.249 L 168.486 49.688 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 222.296 91.9 L 276.106 67.038 L 276.106 109.249 L 222.296 109.249 L 222.296 91.9 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 168.486 92.079 L 222.296 67.038 L 222.296 109.249 L 168.486 109.249 L 168.486 92.079 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 222.296 94.583 L 276.106 69.542 L 276.106 109.249 L 222.296 109.249 L 222.296 94.583 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 168.486 94.583 L 222.296 69.542 L 222.296 109.249 L 168.486 109.249 L 168.486 94.583 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 222.296 98.16 L 276.106 73.298 L 276.106 109.249 L 222.296 109.249 L 222.296 98.16 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 168.486 98.16 L 222.296 73.298 L 222.296 109.249 L 168.486 109.249 L 168.486 98.16 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 166.356 47.81 L 278.348 47.81 L 278.348 114.973 L 166.356 114.973 L 166.356 47.81"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 222.296 49.688 L 276.106 49.688 L 276.106 109.249 L 222.296 109.249 L 222.296 49.688"/>
|
||||||
|
<path style="fill: rgb(153, 153, 153); stroke-width: 1;" d="M 240.906 110.323 L 259.739 110.323 L 259.739 113.9 L 240.906 113.9 L 240.906 110.323 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 169.607 108.713 L 169.159 108.176 L 169.607 107.64 L 170.504 107.64 L 170.952 108.176 L 170.504 108.713 L 169.607 108.713 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 169.607 68.111 L 169.159 67.575 L 169.607 66.859 L 170.504 66.859 L 170.952 67.575 L 170.504 68.111 L 169.607 68.111 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 168.486 49.688 L 222.296 49.688 L 222.296 109.249 L 168.486 109.249 L 168.486 49.688"/>
|
||||||
|
<path style="fill: rgb(229, 229, 229); stroke-width: 1;" d="M 229.247 51.119 L 235.749 51.119 L 235.749 59.973 L 229.247 59.973 L 229.247 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 224.762 51.119 L 226.332 51.119 L 226.332 55.233 L 224.762 55.233 L 224.762 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 272.519 51.119 L 274.313 51.119 L 274.313 55.233 L 272.519 55.233 L 272.519 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(51, 51, 51); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 168.486 49.688 L 222.296 49.688 L 222.296 66.501 L 168.486 66.501 L 168.486 49.688 Z"/>
|
||||||
|
<path style="fill: rgb(229, 229, 229); stroke-width: 1;" d="M 185.526 60.778 L 192.028 60.778 L 192.028 63.103 L 185.526 63.103 L 185.526 60.778 Z"/>
|
||||||
|
<path style="fill: rgb(178, 178, 178); stroke-width: 1;" d="M 200.324 62.745 L 198.978 61.672 L 198.978 60.152 L 200.324 59.079 L 202.117 59.079 L 203.687 60.152 L 203.687 61.672 L 202.117 62.745 L 200.324 62.745 Z"/>
|
||||||
|
<path style="fill: rgb(178, 178, 178); stroke-width: 1;" d="M 207.947 62.745 L 206.602 61.672 L 206.602 60.152 L 207.947 59.079 L 209.74 59.079 L 211.086 60.152 L 211.086 61.672 L 209.74 62.745 L 207.947 62.745 Z"/>
|
||||||
|
<path style="fill: rgb(102, 102, 102); stroke-width: 1;" d="M 176.109 60.778 L 182.611 60.778 L 182.611 63.103 L 176.109 63.103 L 176.109 60.778 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 179.024 54.339 L 179.024 59.079 L 176.782 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 183.06 54.339 L 183.06 59.079 L 180.93 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 187.32 54.339 L 187.32 59.079 L 184.853 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 191.355 54.339 L 191.355 59.079 L 188.889 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 188.889 59.079 L 188.889 54.339 L 191.355 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 184.853 59.079 L 184.853 54.339 L 187.32 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 180.93 59.079 L 180.93 54.339 L 183.06 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 176.782 59.079 L 176.782 54.339 L 179.024 54.339"/>
|
||||||
|
<path style="fill: rgb(127, 127, 127); stroke-width: 1;" d="M 197.857 66.859 L 222.296 104.241 L 222.296 94.404 L 204.135 66.859 L 197.857 66.859 Z"/>
|
||||||
|
<path style="fill: rgb(102, 102, 102); stroke-width: 1;" d="M 204.135 66.859 L 222.296 94.404 L 222.296 84.566 L 210.637 66.859 L 204.135 66.859 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 210.637 66.859 L 222.296 84.566 L 222.296 74.729 L 217.139 66.859 L 210.637 66.859 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(255, 255, 255); stroke-width: 2;" d="M 222.969 50.046 L 275.434 50.046"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.13391, 0, 0, 1.234446, -9.410634, 162.99009)" style="">
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 363.181 109.151 L 363.181 110.989 L 460.721 110.989 L 469.183 107.313 L 469.183 105.958 L 460.721 109.151 L 363.181 109.151 Z"/>
|
||||||
|
<path style="fill: rgb(115, 135, 166); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 361.061 109.151 L 361.061 53.909 L 462.836 53.909 L 462.836 109.151 L 361.061 109.151 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 445.801 105.475 L 445.801 57.586 L 454.374 57.586 L 454.374 105.475 L 445.801 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 433.101 105.475 L 433.101 57.586 L 441.571 57.586 L 441.571 105.475 L 433.101 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 420.411 105.475 L 420.411 57.586 L 428.871 57.586 L 428.871 105.475 L 420.411 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 407.721 105.475 L 407.721 57.586 L 416.181 57.586 L 416.181 105.475 L 407.721 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 395.021 105.475 L 395.021 57.586 L 403.481 57.586 L 403.481 105.475 L 395.021 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 382.221 105.475 L 382.221 57.586 L 390.791 57.586 L 390.791 105.475 L 382.221 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(166, 186, 217); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 369.521 105.475 L 369.521 57.586 L 377.991 57.586 L 377.991 105.475 L 369.521 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 371.641 81.579 L 371.641 76.064 L 375.871 76.064 L 375.871 81.579 L 371.641 81.579 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 371.531 94.446 L 371.531 88.931 L 375.871 88.931 L 375.871 94.446 L 371.531 94.446 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 371.641 87.093 L 371.641 83.417 L 375.981 83.417 L 375.981 87.093 L 371.641 87.093 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 371.641 99.96 L 371.641 96.284 L 375.871 96.284 L 375.871 99.96 L 371.641 99.96 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 384.331 81.579 L 384.331 76.064 L 388.561 76.064 L 388.561 81.579 L 384.331 81.579 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 384.331 94.446 L 384.331 88.931 L 388.561 88.931 L 388.561 94.446 L 384.331 94.446 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 384.331 87.093 L 384.331 83.417 L 388.671 83.417 L 388.671 87.093 L 384.331 87.093 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 384.331 99.96 L 384.331 96.284 L 388.561 96.284 L 388.561 99.96 L 384.331 99.96 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 397.031 81.579 L 397.031 76.064 L 401.371 76.064 L 401.371 81.579 L 397.031 81.579 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 397.031 94.446 L 397.031 88.931 L 401.261 88.931 L 401.261 94.446 L 397.031 94.446 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 397.141 87.093 L 397.141 83.417 L 401.371 83.417 L 401.371 87.093 L 397.141 87.093 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 397.031 99.96 L 397.031 96.284 L 401.371 96.284 L 401.371 99.96 L 397.031 99.96 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 385.451 105.475 L 385.451 101.798 L 387.561 101.798 L 387.561 105.475 L 385.451 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 372.751 105.475 L 372.751 101.798 L 374.871 101.798 L 374.871 105.475 L 372.751 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 372.751 61.262 L 372.751 57.586 L 374.871 57.586 L 374.871 61.262 L 372.751 61.262 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 385.451 61.262 L 385.451 57.586 L 387.561 57.586 L 387.561 61.262 L 385.451 61.262 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 398.141 61.262 L 398.141 57.586 L 400.261 57.586 L 400.261 61.262 L 398.141 61.262 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 398.141 105.475 L 398.141 101.798 L 400.261 101.798 L 400.261 105.475 L 398.141 105.475 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 80.611 L 451.141 80.611 L 451.141 82.449 L 449.031 82.449 L 449.031 80.611 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 76.935 L 451.141 76.935 L 451.141 78.87 L 449.031 78.87 L 449.031 76.935 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 84.287 L 451.141 84.287 L 451.141 86.126 L 449.031 86.126 L 449.031 84.287 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 73.258 L 451.141 73.258 L 451.141 75.097 L 449.031 75.097 L 449.031 73.258 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 87.964 L 451.141 87.964 L 451.141 89.802 L 449.031 89.802 L 449.031 87.964 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 69.582 L 451.141 69.582 L 451.141 71.42 L 449.031 71.42 L 449.031 69.582 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 449.031 91.64 L 451.141 91.64 L 451.141 93.478 L 449.031 93.478 L 449.031 91.64 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 80.611 L 438.451 80.611 L 438.451 82.449 L 436.331 82.449 L 436.331 80.611 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 76.935 L 438.451 76.935 L 438.451 78.87 L 436.331 78.87 L 436.331 76.935 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 84.287 L 438.451 84.287 L 438.451 86.126 L 436.331 86.126 L 436.331 84.287 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 73.258 L 438.451 73.258 L 438.451 75.097 L 436.331 75.097 L 436.331 73.258 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 87.964 L 438.451 87.964 L 438.451 89.802 L 436.331 89.802 L 436.331 87.964 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 69.582 L 438.451 69.582 L 438.451 71.42 L 436.331 71.42 L 436.331 69.582 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 436.331 91.64 L 438.451 91.64 L 438.451 93.478 L 436.331 93.478 L 436.331 91.64 Z"/>
|
||||||
|
<path style="fill: rgb(89, 109, 140); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 462.836 53.909 L 471.299 50.233 L 471.299 105.475 L 462.836 109.151 L 462.836 53.909 Z"/>
|
||||||
|
<path style="fill: rgb(191, 211, 242); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 462.836 53.909 L 471.299 50.233 L 369.521 50.233 L 361.061 53.909 L 462.836 53.909 Z"/>
|
||||||
|
</g>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre;" x="56" y="116.982">COMPRESSOR A</text>
|
||||||
|
<rect x="50" y="150" width="100" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="56.368" y="141.716">On/Off</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="120.949" y="168.123">H</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="56.007" y="167.208">####</text>
|
||||||
|
<rect x="49.832" y="225.548" width="100.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="49.832" y="250" width="100.168" height="50" style="stroke: rgb(0, 0, 0); stroke-width: 0.693; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.693;" cx="124.956" cy="262.355" rx="11.269" ry="10.987"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="55.832" y="241.982">COMPRESSOR B</text>
|
||||||
|
<rect x="49.832" y="275" width="100" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="56.2" y="266.716">On/Off</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="120.781" y="293.123">H</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="55.839" y="292.208">####</text>
|
||||||
|
<rect x="49.832" y="350.548" width="100.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="49.832" y="375" width="100.168" height="50" style="stroke: rgb(0, 0, 0); stroke-width: 0.693; fill: rgb(244, 248, 248);"/>
|
||||||
|
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.693;" cx="124.956" cy="387.355" rx="11.269" ry="10.987"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="55.832" y="366.982">COMPRESSOR C</text>
|
||||||
|
<rect x="49.832" y="400" width="100" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="56.2" y="391.716">On/Off</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="120.781" y="418.123">H</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 15px; white-space: pre; stroke-width: 1;" x="55.839" y="417.208">####</text>
|
||||||
|
<g transform="matrix(1.116151, 0, 0, 1.116686, -10.678391, 171.611261)" style="">
|
||||||
|
<path style="fill: rgb(204, 204, 204); stroke-width: 1;" d="M 166.356 47.81 L 278.348 47.81 L 278.348 114.973 L 166.356 114.973 L 166.356 47.81 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 166.356 95.477 L 278.348 67.396 L 278.348 114.973 L 166.356 114.973 L 166.356 95.477 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 166.356 98.16 L 278.348 70.258 L 278.348 114.973 L 166.356 114.973 L 166.356 98.16 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 166.356 102.453 L 278.348 74.371 L 278.348 114.973 L 166.356 114.973 L 166.356 102.453 Z"/>
|
||||||
|
<path style="fill: rgb(204, 204, 204); stroke-width: 1;" d="M 168.486 49.688 L 276.106 49.688 L 276.106 109.249 L 168.486 109.249 L 168.486 49.688 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 222.296 91.9 L 276.106 67.038 L 276.106 109.249 L 222.296 109.249 L 222.296 91.9 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 168.486 92.079 L 222.296 67.038 L 222.296 109.249 L 168.486 109.249 L 168.486 92.079 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 222.296 94.583 L 276.106 69.542 L 276.106 109.249 L 222.296 109.249 L 222.296 94.583 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 168.486 94.583 L 222.296 69.542 L 222.296 109.249 L 168.486 109.249 L 168.486 94.583 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 222.296 98.16 L 276.106 73.298 L 276.106 109.249 L 222.296 109.249 L 222.296 98.16 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 168.486 98.16 L 222.296 73.298 L 222.296 109.249 L 168.486 109.249 L 168.486 98.16 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 166.356 47.81 L 278.348 47.81 L 278.348 114.973 L 166.356 114.973 L 166.356 47.81"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 222.296 49.688 L 276.106 49.688 L 276.106 109.249 L 222.296 109.249 L 222.296 49.688"/>
|
||||||
|
<path style="fill: rgb(153, 153, 153); stroke-width: 1;" d="M 240.906 110.323 L 259.739 110.323 L 259.739 113.9 L 240.906 113.9 L 240.906 110.323 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 169.607 108.713 L 169.159 108.176 L 169.607 107.64 L 170.504 107.64 L 170.952 108.176 L 170.504 108.713 L 169.607 108.713 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 169.607 68.111 L 169.159 67.575 L 169.607 66.859 L 170.504 66.859 L 170.952 67.575 L 170.504 68.111 L 169.607 68.111 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 168.486 49.688 L 222.296 49.688 L 222.296 109.249 L 168.486 109.249 L 168.486 49.688"/>
|
||||||
|
<path style="fill: rgb(229, 229, 229); stroke-width: 1;" d="M 229.247 51.119 L 235.749 51.119 L 235.749 59.973 L 229.247 59.973 L 229.247 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 224.762 51.119 L 226.332 51.119 L 226.332 55.233 L 224.762 55.233 L 224.762 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 272.519 51.119 L 274.313 51.119 L 274.313 55.233 L 272.519 55.233 L 272.519 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(51, 51, 51); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 168.486 49.688 L 222.296 49.688 L 222.296 66.501 L 168.486 66.501 L 168.486 49.688 Z"/>
|
||||||
|
<path style="fill: rgb(229, 229, 229); stroke-width: 1;" d="M 185.526 60.778 L 192.028 60.778 L 192.028 63.103 L 185.526 63.103 L 185.526 60.778 Z"/>
|
||||||
|
<path style="fill: rgb(178, 178, 178); stroke-width: 1;" d="M 200.324 62.745 L 198.978 61.672 L 198.978 60.152 L 200.324 59.079 L 202.117 59.079 L 203.687 60.152 L 203.687 61.672 L 202.117 62.745 L 200.324 62.745 Z"/>
|
||||||
|
<path style="fill: rgb(178, 178, 178); stroke-width: 1;" d="M 207.947 62.745 L 206.602 61.672 L 206.602 60.152 L 207.947 59.079 L 209.74 59.079 L 211.086 60.152 L 211.086 61.672 L 209.74 62.745 L 207.947 62.745 Z"/>
|
||||||
|
<path style="fill: rgb(102, 102, 102); stroke-width: 1;" d="M 176.109 60.778 L 182.611 60.778 L 182.611 63.103 L 176.109 63.103 L 176.109 60.778 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 179.024 54.339 L 179.024 59.079 L 176.782 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 183.06 54.339 L 183.06 59.079 L 180.93 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 187.32 54.339 L 187.32 59.079 L 184.853 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 191.355 54.339 L 191.355 59.079 L 188.889 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 188.889 59.079 L 188.889 54.339 L 191.355 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 184.853 59.079 L 184.853 54.339 L 187.32 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 180.93 59.079 L 180.93 54.339 L 183.06 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 176.782 59.079 L 176.782 54.339 L 179.024 54.339"/>
|
||||||
|
<path style="fill: rgb(127, 127, 127); stroke-width: 1;" d="M 197.857 66.859 L 222.296 104.241 L 222.296 94.404 L 204.135 66.859 L 197.857 66.859 Z"/>
|
||||||
|
<path style="fill: rgb(102, 102, 102); stroke-width: 1;" d="M 204.135 66.859 L 222.296 94.404 L 222.296 84.566 L 210.637 66.859 L 204.135 66.859 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 210.637 66.859 L 222.296 84.566 L 222.296 74.729 L 217.139 66.859 L 210.637 66.859 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(255, 255, 255); stroke-width: 2;" d="M 222.969 50.046 L 275.434 50.046"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.116151, 0, 0, 1.116686, -10.678387, 296.611283)" style="">
|
||||||
|
<path style="fill: rgb(204, 204, 204); stroke-width: 1;" d="M 166.356 47.81 L 278.348 47.81 L 278.348 114.973 L 166.356 114.973 L 166.356 47.81 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 166.356 95.477 L 278.348 67.396 L 278.348 114.973 L 166.356 114.973 L 166.356 95.477 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 166.356 98.16 L 278.348 70.258 L 278.348 114.973 L 166.356 114.973 L 166.356 98.16 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 166.356 102.453 L 278.348 74.371 L 278.348 114.973 L 166.356 114.973 L 166.356 102.453 Z"/>
|
||||||
|
<path style="fill: rgb(204, 204, 204); stroke-width: 1;" d="M 168.486 49.688 L 276.106 49.688 L 276.106 109.249 L 168.486 109.249 L 168.486 49.688 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 222.296 91.9 L 276.106 67.038 L 276.106 109.249 L 222.296 109.249 L 222.296 91.9 Z"/>
|
||||||
|
<path style="fill: rgb(199, 199, 199); stroke-width: 1;" d="M 168.486 92.079 L 222.296 67.038 L 222.296 109.249 L 168.486 109.249 L 168.486 92.079 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 222.296 94.583 L 276.106 69.542 L 276.106 109.249 L 222.296 109.249 L 222.296 94.583 Z"/>
|
||||||
|
<path style="fill: rgb(194, 194, 194); stroke-width: 1;" d="M 168.486 94.583 L 222.296 69.542 L 222.296 109.249 L 168.486 109.249 L 168.486 94.583 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 222.296 98.16 L 276.106 73.298 L 276.106 109.249 L 222.296 109.249 L 222.296 98.16 Z"/>
|
||||||
|
<path style="fill: rgb(189, 189, 189); stroke-width: 1;" d="M 168.486 98.16 L 222.296 73.298 L 222.296 109.249 L 168.486 109.249 L 168.486 98.16 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 166.356 47.81 L 278.348 47.81 L 278.348 114.973 L 166.356 114.973 L 166.356 47.81"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 222.296 49.688 L 276.106 49.688 L 276.106 109.249 L 222.296 109.249 L 222.296 49.688"/>
|
||||||
|
<path style="fill: rgb(153, 153, 153); stroke-width: 1;" d="M 240.906 110.323 L 259.739 110.323 L 259.739 113.9 L 240.906 113.9 L 240.906 110.323 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 169.607 108.713 L 169.159 108.176 L 169.607 107.64 L 170.504 107.64 L 170.952 108.176 L 170.504 108.713 L 169.607 108.713 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 169.607 68.111 L 169.159 67.575 L 169.607 66.859 L 170.504 66.859 L 170.952 67.575 L 170.504 68.111 L 169.607 68.111 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 168.486 49.688 L 222.296 49.688 L 222.296 109.249 L 168.486 109.249 L 168.486 49.688"/>
|
||||||
|
<path style="fill: rgb(229, 229, 229); stroke-width: 1;" d="M 229.247 51.119 L 235.749 51.119 L 235.749 59.973 L 229.247 59.973 L 229.247 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 224.762 51.119 L 226.332 51.119 L 226.332 55.233 L 224.762 55.233 L 224.762 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke-width: 1;" d="M 272.519 51.119 L 274.313 51.119 L 274.313 55.233 L 272.519 55.233 L 272.519 51.119 Z"/>
|
||||||
|
<path style="fill: rgb(51, 51, 51); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 168.486 49.688 L 222.296 49.688 L 222.296 66.501 L 168.486 66.501 L 168.486 49.688 Z"/>
|
||||||
|
<path style="fill: rgb(229, 229, 229); stroke-width: 1;" d="M 185.526 60.778 L 192.028 60.778 L 192.028 63.103 L 185.526 63.103 L 185.526 60.778 Z"/>
|
||||||
|
<path style="fill: rgb(178, 178, 178); stroke-width: 1;" d="M 200.324 62.745 L 198.978 61.672 L 198.978 60.152 L 200.324 59.079 L 202.117 59.079 L 203.687 60.152 L 203.687 61.672 L 202.117 62.745 L 200.324 62.745 Z"/>
|
||||||
|
<path style="fill: rgb(178, 178, 178); stroke-width: 1;" d="M 207.947 62.745 L 206.602 61.672 L 206.602 60.152 L 207.947 59.079 L 209.74 59.079 L 211.086 60.152 L 211.086 61.672 L 209.74 62.745 L 207.947 62.745 Z"/>
|
||||||
|
<path style="fill: rgb(102, 102, 102); stroke-width: 1;" d="M 176.109 60.778 L 182.611 60.778 L 182.611 63.103 L 176.109 63.103 L 176.109 60.778 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 179.024 54.339 L 179.024 59.079 L 176.782 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 183.06 54.339 L 183.06 59.079 L 180.93 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 187.32 54.339 L 187.32 59.079 L 184.853 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 191.355 54.339 L 191.355 59.079 L 188.889 59.079"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 188.889 59.079 L 188.889 54.339 L 191.355 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 184.853 59.079 L 184.853 54.339 L 187.32 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 180.93 59.079 L 180.93 54.339 L 183.06 54.339"/>
|
||||||
|
<path style="fill: none; stroke: rgb(127, 127, 127); stroke-width: 2;" d="M 176.782 59.079 L 176.782 54.339 L 179.024 54.339"/>
|
||||||
|
<path style="fill: rgb(127, 127, 127); stroke-width: 1;" d="M 197.857 66.859 L 222.296 104.241 L 222.296 94.404 L 204.135 66.859 L 197.857 66.859 Z"/>
|
||||||
|
<path style="fill: rgb(102, 102, 102); stroke-width: 1;" d="M 204.135 66.859 L 222.296 94.404 L 222.296 84.566 L 210.637 66.859 L 204.135 66.859 Z"/>
|
||||||
|
<path style="fill: rgb(76, 76, 76); stroke-width: 1;" d="M 210.637 66.859 L 222.296 84.566 L 222.296 74.729 L 217.139 66.859 L 210.637 66.859 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(255, 255, 255); stroke-width: 2;" d="M 222.969 50.046 L 275.434 50.046"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(0, 4, 255);" d="M 300 125 L 350 125 L 350 275 L 400 275"/>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(0, 4, 255);" d="M 300 250 L 350 250 L 350 275 L 400 275"/>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(0, 4, 255);" d="M 300 375 L 350 375 L 350 275 L 400 275"/>
|
||||||
|
<g transform="matrix(1, 0, 0, 1, 49.999999, -99.999998)" style="">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 292.93 L 717.857 292.651 L 717.857 292.442 L 717.857 292.163 L 717.857 291.954 L 717.857 291.744 L 717.857 291.535 L 717.857 291.396 L 717.914 291.186 L 717.914 291.046 L 717.972 290.907 L 718.029 290.837 L 718.086 290.698 L 718.143 290.558 L 718.201 290.488 L 718.258 290.419 L 718.372 290.349 L 718.487 290.279 L 718.602 290.209 L 718.716 290.14 L 718.888 290.14 L 719.06 290.07 L 719.231 290.07 L 719.46 290 L 719.632 290 L 719.919 290 L 720.147 290 L 720.434 290 L 720.72 290 L 721.063 290 L 721.407 290 L 721.751 290 L 722.151 290 L 770.706 290 L 771.106 290 L 771.45 290 L 771.794 290 L 772.137 290 L 772.423 290 L 772.71 290 L 772.939 290 L 773.225 290 L 773.397 290.07 L 773.626 290.07 L 773.798 290.14 L 773.969 290.14 L 774.141 290.209 L 774.256 290.279 L 774.37 290.349 L 774.485 290.419 L 774.599 290.488 L 774.657 290.558 L 774.714 290.698 L 774.771 290.837 L 774.828 290.977 L 774.886 291.117 L 774.943 291.256 L 774.943 291.465 L 775 291.674 L 775 291.884 L 775 292.093 L 775 292.303 L 775 292.581 L 775 292.86 L 775 293.209 L 775 293.488 L 775 335.558 L 775 335.907 L 775 336.256 L 775 336.604 L 775 336.884 L 775 337.163 L 775 337.442 L 775 337.72 L 775 337.93 L 774.943 338.14 L 774.943 338.349 L 774.943 338.488 L 774.886 338.628 L 774.886 338.767 L 774.828 338.907 L 774.771 339.047 L 774.714 339.117 L 774.657 339.256 L 774.599 339.326 L 774.485 339.396 L 774.427 339.465 L 774.313 339.465 L 774.198 339.535 L 774.084 339.535 L 773.969 339.604 L 773.798 339.604 L 773.683 339.604 L 773.511 339.604 L 773.339 339.674 L 773.168 339.674 L 772.939 339.674 L 772.71 339.674 L 772.481 339.674 L 721.579 339.674 L 721.235 339.674 L 720.892 339.674 L 720.663 339.674 L 720.377 339.674 L 720.09 339.604 L 719.861 339.604 L 719.69 339.604 L 719.46 339.604 L 719.289 339.535 L 719.117 339.535 L 718.945 339.465 L 718.831 339.465 L 718.659 339.396 L 718.544 339.326 L 718.43 339.256 L 718.372 339.117 L 718.258 339.047 L 718.201 338.907 L 718.143 338.767 L 718.086 338.628 L 718.029 338.488 L 717.972 338.349 L 717.914 338.14 L 717.914 337.93 L 717.914 337.72 L 717.857 337.442 L 717.857 337.163 L 717.857 336.884 L 717.857 336.604 L 717.857 336.256 L 717.857 335.907 L 717.857 335.558 L 717.857 292.93 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 732.114 339.674 L 760.743 339.674 L 760.743 342.884 L 732.114 342.884 L 732.114 339.674 Z"/>
|
||||||
|
<path style="fill: rgb(67, 67, 67); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 722.151 299.419 L 722.151 299.07 L 722.151 298.791 L 722.151 298.442 L 722.151 298.163 L 722.151 297.954 L 722.151 297.675 L 722.209 297.465 L 722.209 297.256 L 722.209 297.046 L 722.266 296.837 L 722.323 296.698 L 722.323 296.558 L 722.381 296.419 L 722.495 296.279 L 722.553 296.209 L 722.61 296.07 L 722.725 296 L 722.838 295.93 L 722.953 295.86 L 723.067 295.791 L 723.239 295.791 L 723.354 295.721 L 723.526 295.721 L 723.754 295.651 L 723.926 295.651 L 724.155 295.651 L 724.385 295.651 L 724.671 295.651 L 724.9 295.651 L 725.243 295.651 L 725.53 295.651 L 725.873 295.651 L 767.557 295.651 L 767.843 295.651 L 768.129 295.651 L 768.358 295.651 L 768.587 295.651 L 768.816 295.651 L 769.045 295.721 L 769.217 295.721 L 769.389 295.791 L 769.561 295.791 L 769.675 295.86 L 769.79 295.86 L 769.962 295.93 L 770.076 296 L 770.133 296.07 L 770.247 296.139 L 770.305 296.209 L 770.362 296.349 L 770.419 296.419 L 770.477 296.558 L 770.534 296.698 L 770.591 296.837 L 770.591 296.907 L 770.649 297.046 L 770.649 297.256 L 770.649 297.396 L 770.706 297.604 L 770.706 297.744 L 770.706 297.954 L 770.706 298.163 L 770.706 298.442 L 770.706 298.651 L 770.706 298.861 L 770.706 327.744 L 770.706 328.093 L 770.706 328.372 L 770.706 328.581 L 770.706 328.861 L 770.763 329.07 L 770.763 329.279 L 770.763 329.488 L 770.763 329.628 L 770.763 329.838 L 770.763 329.977 L 770.706 330.117 L 770.706 330.186 L 770.649 330.326 L 770.649 330.465 L 770.591 330.535 L 770.534 330.604 L 770.477 330.674 L 770.419 330.744 L 770.305 330.814 L 770.19 330.883 L 770.076 330.883 L 769.962 330.883 L 769.79 330.953 L 769.618 330.953 L 769.446 330.953 L 769.274 331.023 L 769.045 331.023 L 768.759 331.023 L 768.53 331.023 L 768.244 331.023 L 767.9 331.023 L 767.557 331.023 L 725.873 331.023 L 725.53 331.023 L 725.243 331.023 L 724.9 331.023 L 724.671 331.023 L 724.385 331.023 L 724.155 331.023 L 723.926 331.023 L 723.754 331.023 L 723.526 331.023 L 723.354 331.023 L 723.239 331.023 L 723.067 330.953 L 722.953 330.953 L 722.838 330.883 L 722.725 330.814 L 722.61 330.744 L 722.553 330.674 L 722.495 330.604 L 722.381 330.465 L 722.323 330.326 L 722.323 330.186 L 722.266 330.046 L 722.209 329.838 L 722.209 329.628 L 722.209 329.419 L 722.151 329.209 L 722.151 328.93 L 722.151 328.651 L 722.151 328.302 L 722.151 327.954 L 722.151 327.604 L 722.151 327.186 L 722.151 299.419 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 350 L 775 350 L 775 347.558 L 760.743 342.884 L 732.114 342.884 L 717.857 347.558 L 717.857 350 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 770.133 334.023 L 770.133 337.512 L 756.391 337.512 L 756.391 334.023 L 770.133 334.023 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 347.558 L 775 347.558"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 675 285.361 L 703.571 285.361 L 703.571 350 L 675 350 L 675 285.361 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 287.463 L 701.169 287.463 L 701.169 350 L 677.317 350 L 677.317 287.463 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 290.616 L 701.169 290.616 L 701.169 297.898 L 677.317 297.898 L 677.317 290.616 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 297.898 L 701.169 297.898 L 701.169 305.181 L 677.317 305.181 L 677.317 297.898 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 293.694 L 698.767 293.694 L 698.767 294.745 L 679.72 294.745 L 679.72 293.694 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 300 L 698.767 300 L 698.767 303.078 L 679.72 303.078 L 679.72 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 275 L 698.767 275 L 703.571 285.361 L 675 285.361 L 679.72 275 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 695.163 300 L 701.169 300 L 701.169 303.078 L 695.163 303.078 L 695.163 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 697.566 300 L 698.767 300 L 698.767 303.078 L 697.566 303.078 L 697.566 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 697.566 291.592 L 692.847 291.592 L 692.847 292.643 L 697.566 292.643 L 697.566 291.592 Z"/>
|
||||||
|
<circle style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="890.673" cy="114.148" r="1.051" transform="matrix(1.142857, 0, 0, 1.000013, -320.346635, 178.493448)"/>
|
||||||
|
</g>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(6, 255, 0);" d="M 525 250 L 575 250 L 575 200 L 725 200"/>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(6, 255, 0);" d="M 525 250 L 575 250 L 575 375 L 725 375"/>
|
||||||
|
<rect x="399.832" y="149.864" width="125.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="400" y="175" width="125" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="417.352" y="167.628">PLC Compressor</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="426.101" y="192.839">IP : 192.168.0.1</text>
|
||||||
|
<rect x="725" y="99.864" width="125.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="725.168" y="125" width="125" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="759.52" y="117.628">PC Station</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="751.269" y="142.839">IP : 192.168.0.2</text>
|
||||||
|
<g transform="matrix(1, 0, 0, 1, 49.999999, 74.999996)" style="">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 292.93 L 717.857 292.651 L 717.857 292.442 L 717.857 292.163 L 717.857 291.954 L 717.857 291.744 L 717.857 291.535 L 717.857 291.396 L 717.914 291.186 L 717.914 291.046 L 717.972 290.907 L 718.029 290.837 L 718.086 290.698 L 718.143 290.558 L 718.201 290.488 L 718.258 290.419 L 718.372 290.349 L 718.487 290.279 L 718.602 290.209 L 718.716 290.14 L 718.888 290.14 L 719.06 290.07 L 719.231 290.07 L 719.46 290 L 719.632 290 L 719.919 290 L 720.147 290 L 720.434 290 L 720.72 290 L 721.063 290 L 721.407 290 L 721.751 290 L 722.151 290 L 770.706 290 L 771.106 290 L 771.45 290 L 771.794 290 L 772.137 290 L 772.423 290 L 772.71 290 L 772.939 290 L 773.225 290 L 773.397 290.07 L 773.626 290.07 L 773.798 290.14 L 773.969 290.14 L 774.141 290.209 L 774.256 290.279 L 774.37 290.349 L 774.485 290.419 L 774.599 290.488 L 774.657 290.558 L 774.714 290.698 L 774.771 290.837 L 774.828 290.977 L 774.886 291.117 L 774.943 291.256 L 774.943 291.465 L 775 291.674 L 775 291.884 L 775 292.093 L 775 292.303 L 775 292.581 L 775 292.86 L 775 293.209 L 775 293.488 L 775 335.558 L 775 335.907 L 775 336.256 L 775 336.604 L 775 336.884 L 775 337.163 L 775 337.442 L 775 337.72 L 775 337.93 L 774.943 338.14 L 774.943 338.349 L 774.943 338.488 L 774.886 338.628 L 774.886 338.767 L 774.828 338.907 L 774.771 339.047 L 774.714 339.117 L 774.657 339.256 L 774.599 339.326 L 774.485 339.396 L 774.427 339.465 L 774.313 339.465 L 774.198 339.535 L 774.084 339.535 L 773.969 339.604 L 773.798 339.604 L 773.683 339.604 L 773.511 339.604 L 773.339 339.674 L 773.168 339.674 L 772.939 339.674 L 772.71 339.674 L 772.481 339.674 L 721.579 339.674 L 721.235 339.674 L 720.892 339.674 L 720.663 339.674 L 720.377 339.674 L 720.09 339.604 L 719.861 339.604 L 719.69 339.604 L 719.46 339.604 L 719.289 339.535 L 719.117 339.535 L 718.945 339.465 L 718.831 339.465 L 718.659 339.396 L 718.544 339.326 L 718.43 339.256 L 718.372 339.117 L 718.258 339.047 L 718.201 338.907 L 718.143 338.767 L 718.086 338.628 L 718.029 338.488 L 717.972 338.349 L 717.914 338.14 L 717.914 337.93 L 717.914 337.72 L 717.857 337.442 L 717.857 337.163 L 717.857 336.884 L 717.857 336.604 L 717.857 336.256 L 717.857 335.907 L 717.857 335.558 L 717.857 292.93 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 732.114 339.674 L 760.743 339.674 L 760.743 342.884 L 732.114 342.884 L 732.114 339.674 Z"/>
|
||||||
|
<path style="fill: rgb(67, 67, 67); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 722.151 299.419 L 722.151 299.07 L 722.151 298.791 L 722.151 298.442 L 722.151 298.163 L 722.151 297.954 L 722.151 297.675 L 722.209 297.465 L 722.209 297.256 L 722.209 297.046 L 722.266 296.837 L 722.323 296.698 L 722.323 296.558 L 722.381 296.419 L 722.495 296.279 L 722.553 296.209 L 722.61 296.07 L 722.725 296 L 722.838 295.93 L 722.953 295.86 L 723.067 295.791 L 723.239 295.791 L 723.354 295.721 L 723.526 295.721 L 723.754 295.651 L 723.926 295.651 L 724.155 295.651 L 724.385 295.651 L 724.671 295.651 L 724.9 295.651 L 725.243 295.651 L 725.53 295.651 L 725.873 295.651 L 767.557 295.651 L 767.843 295.651 L 768.129 295.651 L 768.358 295.651 L 768.587 295.651 L 768.816 295.651 L 769.045 295.721 L 769.217 295.721 L 769.389 295.791 L 769.561 295.791 L 769.675 295.86 L 769.79 295.86 L 769.962 295.93 L 770.076 296 L 770.133 296.07 L 770.247 296.139 L 770.305 296.209 L 770.362 296.349 L 770.419 296.419 L 770.477 296.558 L 770.534 296.698 L 770.591 296.837 L 770.591 296.907 L 770.649 297.046 L 770.649 297.256 L 770.649 297.396 L 770.706 297.604 L 770.706 297.744 L 770.706 297.954 L 770.706 298.163 L 770.706 298.442 L 770.706 298.651 L 770.706 298.861 L 770.706 327.744 L 770.706 328.093 L 770.706 328.372 L 770.706 328.581 L 770.706 328.861 L 770.763 329.07 L 770.763 329.279 L 770.763 329.488 L 770.763 329.628 L 770.763 329.838 L 770.763 329.977 L 770.706 330.117 L 770.706 330.186 L 770.649 330.326 L 770.649 330.465 L 770.591 330.535 L 770.534 330.604 L 770.477 330.674 L 770.419 330.744 L 770.305 330.814 L 770.19 330.883 L 770.076 330.883 L 769.962 330.883 L 769.79 330.953 L 769.618 330.953 L 769.446 330.953 L 769.274 331.023 L 769.045 331.023 L 768.759 331.023 L 768.53 331.023 L 768.244 331.023 L 767.9 331.023 L 767.557 331.023 L 725.873 331.023 L 725.53 331.023 L 725.243 331.023 L 724.9 331.023 L 724.671 331.023 L 724.385 331.023 L 724.155 331.023 L 723.926 331.023 L 723.754 331.023 L 723.526 331.023 L 723.354 331.023 L 723.239 331.023 L 723.067 330.953 L 722.953 330.953 L 722.838 330.883 L 722.725 330.814 L 722.61 330.744 L 722.553 330.674 L 722.495 330.604 L 722.381 330.465 L 722.323 330.326 L 722.323 330.186 L 722.266 330.046 L 722.209 329.838 L 722.209 329.628 L 722.209 329.419 L 722.151 329.209 L 722.151 328.93 L 722.151 328.651 L 722.151 328.302 L 722.151 327.954 L 722.151 327.604 L 722.151 327.186 L 722.151 299.419 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 350 L 775 350 L 775 347.558 L 760.743 342.884 L 732.114 342.884 L 717.857 347.558 L 717.857 350 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 770.133 334.023 L 770.133 337.512 L 756.391 337.512 L 756.391 334.023 L 770.133 334.023 Z"/>
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 717.857 347.558 L 775 347.558"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 675 285.361 L 703.571 285.361 L 703.571 350 L 675 350 L 675 285.361 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 287.463 L 701.169 287.463 L 701.169 350 L 677.317 350 L 677.317 287.463 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 290.616 L 701.169 290.616 L 701.169 297.898 L 677.317 297.898 L 677.317 290.616 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 677.317 297.898 L 701.169 297.898 L 701.169 305.181 L 677.317 305.181 L 677.317 297.898 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 293.694 L 698.767 293.694 L 698.767 294.745 L 679.72 294.745 L 679.72 293.694 Z"/>
|
||||||
|
<path style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 300 L 698.767 300 L 698.767 303.078 L 679.72 303.078 L 679.72 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 679.72 275 L 698.767 275 L 703.571 285.361 L 675 285.361 L 679.72 275 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 695.163 300 L 701.169 300 L 701.169 303.078 L 695.163 303.078 L 695.163 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 697.566 300 L 698.767 300 L 698.767 303.078 L 697.566 303.078 L 697.566 300 Z"/>
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 697.566 291.592 L 692.847 291.592 L 692.847 292.643 L 697.566 292.643 L 697.566 291.592 Z"/>
|
||||||
|
<circle style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="890.673" cy="114.148" r="1.051" transform="matrix(1.142857, 0, 0, 1.000013, -320.346635, 178.493448)"/>
|
||||||
|
</g>
|
||||||
|
<rect x="724.832" y="275" width="125.168" height="25.136" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.693;"/>
|
||||||
|
<rect x="725" y="300.136" width="125" height="25" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;"/>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="760.352" y="292.764">PC Server</text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; white-space: pre; stroke-width: 1;" x="751.1" y="317.975" transform="matrix(1, 0, 0, 1, -3, 0)">IP : xxx.xxx.xx.xx<tspan x="751.0999755859375" dy="1em"></tspan></text>
|
||||||
|
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 30px; white-space: pre; stroke-width: 1;" x="312.458" y="50.984">OVERVIEW COMPRESSOR</text>
|
||||||
|
<path style="fill: none; stroke-width: 1.386; stroke-dasharray: 6, 4; stroke: rgb(0, 0, 0);" d="M 625 75 L 625 114.125 L 625 264.125 L 625 450"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 45 KiB |
566
src/assets/svg/test-new.svg
Normal file
@@ -0,0 +1,566 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg viewBox="0 0 2750 1600" version="1.1" id="svg2113" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:bx="https://boxy-svg.com">
|
||||||
|
<defs id="defs1583">
|
||||||
|
<bx:grid x="0" y="0" width="62.847" height="59.308" />
|
||||||
|
</defs>
|
||||||
|
<rect width="2765.268" height="1601.316" style="fill: rgb(249, 249, 249); stroke: rgb(0, 0, 0);" id="rect1585" />
|
||||||
|
<g transform="matrix(3.756041, 0, 0, 3.411999, -325.08066, -171.136964)" style="" id="g1595">
|
||||||
|
<rect x="111.721" y="167.133" width="2.541" height="6.598" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1587" />
|
||||||
|
<rect x="111.373" y="178.442" width="2.889" height="6.598" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1589" />
|
||||||
|
<rect x="111.373" y="190.204" width="2.889" height="6.598" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1591" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
d="M 105.737 163.995 L 103.281 166.267 L 103.337 197.271 L 105.513 200 L 109.364 200 L 111.373 197.271 C 111.373 197.271 111.3 166.386 111.3 166.355 C 111.3 166.325 109.158 163.986 109.158 163.986 L 105.737 163.995 Z"
|
||||||
|
id="path1593" />
|
||||||
|
</g>
|
||||||
|
<rect x="221.266" y="4276.899" height="719.302"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(255, 230, 0); transform-box: fill-box; transform-origin: 50% 50%;"
|
||||||
|
transform="matrix(0, -1, 1, 0, 241.54927, -4187.271127)" width="1.854" id="rect1597" />
|
||||||
|
<g style="" transform="matrix(1.967086, 0, 0, 2.764776, 8.509805, 20.443167)" id="g1605">
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1;"
|
||||||
|
d="M 160.097 163.649 L 200 148.321 L 200 163.649 L 160.097 148.321 L 160.097 163.649 Z" id="path1599" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1; transform-origin: 180.051px 148.792px;"
|
||||||
|
d="M 180.051 155.918 L 180.051 141.666" id="path1601" />
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1;"
|
||||||
|
d="M 180.091 141.868 L 163.816 141.868 L 163.902 140.59 L 164.165 139.38 L 164.515 138.17 L 165.127 137.028 L 165.827 135.952 L 166.614 134.944 L 167.578 133.934 L 168.627 133.061 L 169.764 132.255 L 170.99 131.582 L 172.39 130.909 L 173.79 130.439 L 175.277 129.968 L 176.853 129.699 L 178.428 129.497 L 180.091 129.431 L 181.754 129.497 L 183.415 129.699 L 184.904 129.968 L 186.478 130.439 L 187.879 130.909 L 189.191 131.582 L 190.416 132.255 L 191.642 133.061 L 192.692 133.934 L 193.567 134.944 L 194.441 135.952 L 195.054 137.028 L 195.666 138.17 L 196.017 139.38 L 196.279 140.59 L 196.366 141.868 L 180.091 141.868 Z"
|
||||||
|
id="path1603" />
|
||||||
|
</g>
|
||||||
|
<rect x="570.504" y="312.334" width="1.617" height="134.41"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(255, 230, 0); transform-origin: 571.311px 379.538px;" id="rect1607" />
|
||||||
|
<rect x="192.153" y="622.531" width="170.94" height="200.351" style="fill: rgb(179, 179, 179); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1609" />
|
||||||
|
<rect x="363.093" y="636.243" width="11.348" height="175.002" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1611" />
|
||||||
|
<rect x="1311.745" y="222.133" width="24.158" height="66.668"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%;"
|
||||||
|
transform="matrix(0, 1, -1, 0, -916.048411, 468.548218)" id="rect1613" />
|
||||||
|
<g transform="matrix(1.967086, 0, 0, 2.255241, -84.857842, 158.144456)" id="g1627">
|
||||||
|
<rect x="-270.583" y="233.554" width="2.468" height="4.532" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 0, 0)" id="rect1615" />
|
||||||
|
<rect x="-271.043" y="248.421" width="2.928" height="4.29" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 0, 0)" id="rect1617" />
|
||||||
|
<rect x="207.97" y="-262.356" width="2.928" height="4.551" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 479.012726, 525.711979)" id="rect1619" />
|
||||||
|
<path style="fill: rgb(5, 121, 40); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%;"
|
||||||
|
d="M 274.287 273.087 L 271.043 270.353 L 271.117 233.051 L 273.991 229.767 L 279.077 229.767 L 281.731 233.051 C 281.731 233.051 281.634 270.21 281.634 270.248 C 281.634 270.284 278.805 273.098 278.805 273.098 L 274.287 273.087 Z"
|
||||||
|
transform="matrix(-1, 0, 0, -1, 0.000001, 0.000002)" id="path1621" />
|
||||||
|
<rect x="270.583" y="-241.384" width="2.468" height="4.451" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 541.166016, 482.768005)" id="rect1623" />
|
||||||
|
<rect x="-270.583" y="255.823" width="2.468" height="4.658" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 0, 0)" id="rect1625" />
|
||||||
|
</g>
|
||||||
|
<rect x="1814.407" y="219.287" width="36.797" height="68.004"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1832.81px 253.29px;"
|
||||||
|
transform="matrix(0, 1, -1, 0, -1263.114954, 471.648948)" id="rect1629" />
|
||||||
|
<g transform="matrix(-1.967086, 0, 0, 2.255241, 1061.308724, 158.144456)" style="" id="g1643">
|
||||||
|
<rect x="-270.583" y="233.554" width="2.468" height="4.532" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 0, 0)" id="rect1631" />
|
||||||
|
<rect x="-271.043" y="248.421" width="2.928" height="4.29" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 0, 0)" id="rect1633" />
|
||||||
|
<rect x="207.97" y="-262.356" width="2.928" height="4.551" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 479.012726, 525.711979)" id="rect1635" />
|
||||||
|
<path style="fill: rgb(5, 121, 40); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%;"
|
||||||
|
d="M 274.287 273.087 L 271.043 270.353 L 271.117 233.051 L 273.991 229.767 L 279.077 229.767 L 281.731 233.051 C 281.731 233.051 281.634 270.21 281.634 270.248 C 281.634 270.284 278.805 273.098 278.805 273.098 L 274.287 273.087 Z"
|
||||||
|
transform="matrix(-1, 0, 0, -1, 0.000001, 0.000002)" id="path1637" />
|
||||||
|
<rect x="270.583" y="-241.384" width="2.468" height="4.451" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 541.166016, 482.768005)" id="rect1639" />
|
||||||
|
<rect x="-270.583" y="255.823" width="2.468" height="4.658" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 0, 0)" id="rect1641" />
|
||||||
|
</g>
|
||||||
|
<rect x="603.693" y="580.559" width="11.348" height="284.474" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1645" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%;"
|
||||||
|
d="M 507.186 740.424 L 550.252 704.131 L 722.03 703.268 L 765.747 740.642 L 507.186 740.424 Z" id="path1647"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, -0.000028, 0.000006)" />
|
||||||
|
<rect x="657.992" y="580.559" width="34.149" height="284.474" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1649" />
|
||||||
|
<rect x="671.676" y="142.765" width="12.37" height="42.847"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 677.862px 164.189px;"
|
||||||
|
transform="matrix(0, 1, -1, 0, -41.394006, 438.822517)" id="rect1651" />
|
||||||
|
<rect x="671.676" y="142.765" width="12.37" height="42.849"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 677.862px 164.191px;"
|
||||||
|
transform="matrix(0, 1, -1, 0, -41.395654, 676.71634)" id="rect1653" />
|
||||||
|
<rect x="671.676" y="142.759" width="12.37" height="42.847"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 677.862px 164.183px;"
|
||||||
|
transform="matrix(0, 1, -1, 0, -41.393823, 469.979671)" id="rect1655" />
|
||||||
|
<rect x="671.676" y="142.763" width="12.37" height="42.849"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 677.862px 164.189px;"
|
||||||
|
transform="matrix(0, 1, -1, 0, -41.396143, 640.873796)" id="rect1657" />
|
||||||
|
<rect x="692.141" y="609.194" width="11.348" height="225.524" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1659" />
|
||||||
|
<rect x="703.489" y="627.978" width="78.52" height="190.076" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1661" />
|
||||||
|
<rect x="734.874" y="627.978" width="11.348" height="190.076" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1663" />
|
||||||
|
<rect x="782.009" y="580.559" width="84.791" height="284.474" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1665" />
|
||||||
|
<rect x="912.958" y="653.978" width="34.351" height="142.639" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1667" />
|
||||||
|
<rect x="387.88" y="481.524" width="3.04" height="170.655" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(0.998593, -0.053021, 0, 1.001409, 560.055851, 178.939058)" id="rect1669" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%;"
|
||||||
|
d="M 726.028 743.604 L 808.32 701.986 L 970.629 701.986 L 1052.174 743.604 L 726.028 743.604 Z" id="path1671"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, -0.00006, -0.000073)" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 966.433px 728.04px;"
|
||||||
|
d="M 851.71 741.93 L 909.604 714.15 L 1023.79 714.15 L 1081.159 741.93 L 851.71 741.93 Z" id="path1673"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, 0.00001, 0.000013)" />
|
||||||
|
<rect x="780.851" y="481.524" width="6.12" height="170.655" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(0.998593, -0.053021, 0, 1.001409, 202.605928, 199.545717)" id="rect1675" />
|
||||||
|
<rect x="989.142" y="656.234" width="44.023" height="142.639" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1677" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1040.14px 723.043px;"
|
||||||
|
d="M 839.576 716.183 L 884.529 729.902 L 1200.198 729.902 L 1240.694 716.272 L 839.576 716.183 Z" id="path1679"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, -0.000087, -0.000004)" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
d="M 1048.001 548.108 L 1048.304 897.975 L 1207.093 897.975 L 1218.978 875.774 L 1219.181 569.45 L 1207.954 548.801 L 1048.001 548.108 Z"
|
||||||
|
id="path1681" />
|
||||||
|
<rect x="1219.18" y="568.827" width="27.547" height="308.201" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1683" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1250.61px 722.928px;"
|
||||||
|
d="M 1073.93 726.31 L 1113.529 719.546 L 1391.606 719.546 L 1427.277 726.267 L 1073.93 726.31 Z" id="path1685"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, 0.000091, -0.000014)" />
|
||||||
|
<rect x="1254.482" y="596.467" width="44.499" height="254.908" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1687" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1475.41px 727.407px;"
|
||||||
|
d="M 1256.467 722.549 L 1278.178 732.178 L 1672.628 732.254 L 1694.355 722.901 L 1256.467 722.549 Z" id="path1689"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, 0.000073, 0.000005)" />
|
||||||
|
<rect x="1298.981" y="568.827" width="44.499" height="308.201" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1691" />
|
||||||
|
<rect x="1343.481" y="596.824" width="44.499" height="254.908" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1693" />
|
||||||
|
<rect x="1387.98" y="596.824" width="21.872" height="254.908" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1695" />
|
||||||
|
<rect x="1409.852" y="596.824" width="44.499" height="254.908" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1697" />
|
||||||
|
<rect x="1454.351" y="520.549" width="15.503" height="409.931" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1699" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1486.54px 729.878px;"
|
||||||
|
d="M 1285.741 725.021 L 1305.656 734.65 L 1667.405 734.725 L 1687.333 725.373 L 1285.741 725.021 Z" id="path1701"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, -0.000073, 0.000112)" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1497.66px 730.53px;"
|
||||||
|
d="M 1312.27 725.673 L 1330.659 735.302 L 1664.657 735.377 L 1683.056 726.025 L 1312.27 725.673 Z" id="path1703"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, 0.000015, -0.000034)" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1508.79px 733.956px;"
|
||||||
|
d="M 1344.755 729.099 L 1361.021 738.727 L 1656.546 738.803 L 1672.82 729.45 L 1344.755 729.099 Z" id="path1705"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, -0.000137, 0.000068)" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1519.91px 734.718px;"
|
||||||
|
d="M 1368.907 729.861 L 1383.883 739.49 L 1655.938 739.565 L 1670.927 730.213 L 1368.907 729.861 Z" id="path1707"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, -0.000023, -0.000055)" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1531.04px 734.75px;"
|
||||||
|
d="M 1388.217 729.893 L 1402.375 739.521 L 1659.692 739.597 L 1673.869 730.244 L 1388.217 729.893 Z" id="path1709"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, 0.000068, -0.000003)" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-origin: 1542.17px 732.158px;"
|
||||||
|
d="M 1413.447 727.301 L 1426.21 736.93 L 1658.11 737.005 L 1670.886 727.653 L 1413.447 727.301 Z" id="path1711"
|
||||||
|
transform="matrix(0, 0.872229, -1.146488, 0, -0.000157, 0.000035)" />
|
||||||
|
<rect x="1547.735" y="619.886" width="16.773" height="224.545" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1713" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
d="M 1565.938 502.426 C 1565.938 502.426 1562.4376824661815 892.9090808121027 1566.214 945.552 C 1567.0379405282195 957.0379606625416 1565.9094864891053 963.619303438939 1569.212 966.433 C 1571.519042488712 968.3985687994298 1577.3044475621036 968.2986812007803 1579.505 966.259 C 1582.6326889892484 963.3599605027395 1580.896216996098 956.9246936747566 1581.431 945.552 C 1583.9015586930873 893.0131066227198 1585.3790228328503 546.5920914640369 1581.978 501.716 C 1581.3657807394668 493.6378397282225 1582.3293586890359 489.67728117870035 1579.793 487.501 C 1577.6385435938726 485.6524038423454 1571.744195211586 485.6529690725045 1569.416 487.501 C 1566.5866798051243 489.74680447459815 1565.938 502.426 1565.938 502.426 C 1565.9379999999999 502.42599999999993 1565.938 502.426 1565.938 502.426 C 1565.938 502.426 1565.9380000000003 502.4260000000001 1565.938 502.426"
|
||||||
|
id="path1715"
|
||||||
|
bx:d="M 1565.938 502.426 R 1566.214 945.552 R 1569.212 966.433 R 1579.505 966.259 R 1581.431 945.552 R 1581.978 501.716 R 1579.793 487.501 R 1569.416 487.501 R 1565.938 502.426 R 1565.938 502.426 Z 1@8154597a" />
|
||||||
|
<path style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%;"
|
||||||
|
d="M 1549.808 751.776 L 1677.314 751.035 C 1677.314 751.035 1677.314 726.268 1677.314 725.991 C 1677.314 725.714 1663.065 700.76 1663.065 700.76 L 1564.013 701.59 L 1549.107 726.268 L 1549.808 751.776 Z"
|
||||||
|
id="path1717" transform="matrix(0, 0.872229, -1.146488, 0, 0.000076, -0.000036)" />
|
||||||
|
<rect x="1632.876" y="655.365" width="9.58" height="142.788" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1719" />
|
||||||
|
<rect x="1660.461" y="596.755" width="5.046" height="48.404" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1721" transform="matrix(1, 0, 0, 1, -18.004511, 103.828065)" />
|
||||||
|
<rect x="1647.502" y="676.325" width="8.445" height="101.391" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1723" />
|
||||||
|
<rect x="1673.951" y="577.366" width="4.198" height="91.421" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1725" transform="matrix(1, 0, 0, 1, -18.004511, 103.828065)" />
|
||||||
|
<rect x="1660.144" y="684.807" width="85.36" height="79.267" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1727" />
|
||||||
|
<rect x="1795.977" y="604.057" width="14.617" height="26.594" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -19.971598, 106.083306)" id="rect1729" />
|
||||||
|
<rect x="1749.438" y="661.186" width="24.897" height="132.31" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1731" />
|
||||||
|
<rect x="1757.53" y="559.76" width="10.489" height="12.88" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -19.971598, 106.083306)" id="rect1733" />
|
||||||
|
<rect x="1763.434" y="611.146" width="10.487" height="12.88" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -25.872856, 106.083306)" id="rect1735" />
|
||||||
|
<rect x="1763.432" y="661.737" width="10.487" height="12.88" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -25.872856, 106.083306)" id="rect1737" />
|
||||||
|
<rect x="1365.579" y="186.044" width="1.963" height="36.402" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, 380.50336, 492.679508)" id="rect1739" />
|
||||||
|
<rect x="1689.88" y="573.855" width="1.395" height="35.82" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, 56.772358, 158.144456)" id="rect1741" />
|
||||||
|
<rect x="1797.053" y="559.491" width="10.489" height="12.88" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -21.938683, 106.083306)" id="rect1743" />
|
||||||
|
<rect x="1800.989" y="610.877" width="10.487" height="12.88" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -25.872856, 106.083306)" id="rect1745" />
|
||||||
|
<rect x="1793.121" y="661.469" width="10.487" height="12.88" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -18.004511, 106.083306)" id="rect1747" />
|
||||||
|
<rect x="1791.424" y="672.34" width="7.064" height="236.318" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1749" />
|
||||||
|
<rect x="1798.487" y="655.365" width="131.944" height="264.986"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" id="rect1751" />
|
||||||
|
<rect x="1803.02" y="661.186" width="122.717" height="253.381" style="fill: rgb(246, 246, 246); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1753" />
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.560739, 848.629096, 547.548161)" id="g1771">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1755" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1757" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1759" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1761" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1763" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1765" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1767" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1769" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.522625, 848.629096, 533.266483)" id="g1789">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1773" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1775" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1777" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1779" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1781" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1783" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1785" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1787" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.576848, 848.629096, 569.471135)" id="g1807">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1791" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1793" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1795" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1797" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1799" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1801" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1803" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1805" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.580201, 848.629096, 593.583928)" id="g1825">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1809" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1811" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1813" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1815" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1817" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1819" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1821" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1823" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.450487, 848.629096, 651.557073)" id="g1843">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1827" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1829" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1831" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1833" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1835" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1837" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1839" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1841" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.459332, 848.629096, 669.753301)" id="g1861">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1845" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1847" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1849" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1851" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1853" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1855" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1857" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1859" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.522625, 848.629096, 675.022405)" id="g1879">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1863" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1865" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1867" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1869" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1871" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1873" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1875" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1877" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.522625, 848.629096, 698.643987)" id="g1897">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1881" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1883" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1885" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1887" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1889" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1891" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1893" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1895" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.522625, 848.629096, 722.26563)" id="g1915">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1899" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1901" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1903" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1905" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1907" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1909" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1911" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1913" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.522625, 848.629096, 745.887152)" id="g1933">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1917" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1919" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1921" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1923" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1925" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1927" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1929" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1931" />
|
||||||
|
</g>
|
||||||
|
<g style="" transform="matrix(1.018862, 0, 0, 0.39464, 848.629096, 800.136481)" id="g1951">
|
||||||
|
<path style="fill: rgb(230, 230, 230); stroke: rgb(76, 76, 76); stroke-width: 2;"
|
||||||
|
d="M 989.672 289.959 L 989.672 244.761 L 1009.612 244.761 L 1009.612 289.959 L 989.672 289.959 Z" id="path1935" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 250.366 L 1009.612 250.366"
|
||||||
|
id="path1937" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 256.03 L 1009.612 256.03"
|
||||||
|
id="path1939" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 261.635 L 1009.612 261.635"
|
||||||
|
id="path1941" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 267.3 L 1009.612 267.3"
|
||||||
|
id="path1943" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 272.965 L 1009.612 272.965"
|
||||||
|
id="path1945" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 278.629 L 1009.612 278.629"
|
||||||
|
id="path1947" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 989.672 284.234 L 1009.612 284.234"
|
||||||
|
id="path1949" />
|
||||||
|
</g>
|
||||||
|
<rect x="1816.162" y="699.155" width="19.366" height="49.631" style="fill: rgb(242, 189, 189); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1953" />
|
||||||
|
<rect x="1888.417" y="699.155" width="19.366" height="49.631" style="fill: rgb(242, 189, 189); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1955" />
|
||||||
|
<rect x="1785.603" y="716.96" width="126.859" height="12.88" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1957" />
|
||||||
|
<rect x="1930.432" y="670.354" width="7.064" height="236.318" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1959" />
|
||||||
|
<rect x="-1907.783" y="834.718" width="19.366" height="49.631" style="fill: rgb(242, 189, 189); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 0, 0)" id="rect1961" />
|
||||||
|
<rect x="-1853.532" y="730.89" width="19.366" height="49.631" style="fill: rgb(242, 189, 189); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, -18.004396, 103.828089)" id="rect1963" />
|
||||||
|
<rect x="1937.985" y="839.138" width="100.302" height="40.159" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1965" />
|
||||||
|
<rect x="1937.985" y="846.571" width="24.897" height="24.625" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1967" />
|
||||||
|
<rect x="-1962.883" y="852.523" width="152.247" height="12.88" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 0, 0)" id="rect1969" />
|
||||||
|
<rect x="1988.046" y="823.358" width="18.559" height="74.071" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1971" />
|
||||||
|
<rect x="987.703" y="358.288" width="5.256" height="7.21" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, 994.049751, 467.679142)" id="rect1973" />
|
||||||
|
<rect x="2070.573" y="748.65" width="5.254" height="7.21" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -88.819611, 106.083306)" id="rect1975" />
|
||||||
|
<rect x="866.734" y="341.306" width="5.254" height="7.21" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, 1115.019173, 541.748722)" id="rect1977" />
|
||||||
|
<rect x="2038.287" y="839.138" width="38.832" height="40.159" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1979" />
|
||||||
|
<rect x="2077.119" y="665.843" width="20.987" height="357.686" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1981" />
|
||||||
|
<rect x="987.703" y="358.288" width="5.256" height="7.21" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, 1018.901741, 467.679142)" id="rect1983" />
|
||||||
|
<rect x="2136.735" y="748.65" width="5.254" height="7.21" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, -130.128419, 106.083306)" id="rect1985" />
|
||||||
|
<rect x="866.734" y="341.306" width="5.254" height="7.21" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(1, 0, 0, 1, 1139.873544, 541.748722)" id="rect1987" />
|
||||||
|
<rect x="2098.104" y="637.371" width="391.487" height="411.061"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" id="rect1989" />
|
||||||
|
<rect x="2509.225" y="839.136" width="38.832" height="40.159" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1991" />
|
||||||
|
<rect x="180.154" y="635.837" width="11.348" height="175.002" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1993" />
|
||||||
|
<rect x="2488.238" y="665.843" width="20.987" height="357.686" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect1995" />
|
||||||
|
<ellipse style="fill: rgb(16, 255, 40); stroke: rgb(0, 0, 0);" cx="2568.167" cy="859.043" rx="26.011" ry="30.315"
|
||||||
|
id="ellipse1997" />
|
||||||
|
<rect x="2750.419" y="-736.219" width="38.832" height="40.159" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);"
|
||||||
|
transform="matrix(-1, 0, 0, 1, 5482.833562, 1576.26583)" id="rect1999" />
|
||||||
|
<rect x="2612.182" y="751.661" width="61.845" height="7.808" style="fill: rgb(16, 255, 40); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2001" transform="matrix(1, 0, 0, 1, -18.004511, 103.828065)" />
|
||||||
|
<ellipse style="fill: rgb(16, 255, 40); stroke: rgb(0, 0, 0);" cx="2675.44" cy="859.952" rx="26.011" ry="30.315"
|
||||||
|
id="ellipse2003" />
|
||||||
|
<text
|
||||||
|
style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-weight: bold; font-size: 14px;"
|
||||||
|
x="108.414" y="257.057" id="text2017"
|
||||||
|
transform="matrix(1.967086, 0, 0, 2.255241, -18.004511, 103.828065)">STARTER</text>
|
||||||
|
<text
|
||||||
|
style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-weight: bold; font-size: 14px;"
|
||||||
|
x="1086.976" y="257.057" id="text2019"
|
||||||
|
transform="matrix(1.967086, 0, 0, 2.255241, -18.004511, 103.828065)">GENERATOR</text>
|
||||||
|
<g style="" transform="matrix(1.967086, 0, 0, 2.764776, 508.468418, 20.443167)" id="start">
|
||||||
|
<path style="stroke: rgb(76, 76, 76); stroke-width: 1; fill: rgb(114, 182, 33);"
|
||||||
|
d="M 160.097 163.649 L 200 148.321 L 200 163.649 L 160.097 148.321 L 160.097 163.649 Z" id="path2021" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1; transform-origin: 180.051px 148.792px;"
|
||||||
|
d="M 180.051 155.918 L 180.051 141.666" id="path2023" />
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1;"
|
||||||
|
d="M 180.091 141.868 L 163.816 141.868 L 163.902 140.59 L 164.165 139.38 L 164.515 138.17 L 165.127 137.028 L 165.827 135.952 L 166.614 134.944 L 167.578 133.934 L 168.627 133.061 L 169.764 132.255 L 170.99 131.582 L 172.39 130.909 L 173.79 130.439 L 175.277 129.968 L 176.853 129.699 L 178.428 129.497 L 180.091 129.431 L 181.754 129.497 L 183.415 129.699 L 184.904 129.968 L 186.478 130.439 L 187.879 130.909 L 189.191 131.582 L 190.416 132.255 L 191.642 133.061 L 192.692 133.934 L 193.567 134.944 L 194.441 135.952 L 195.054 137.028 L 195.666 138.17 L 196.017 139.38 L 196.279 140.59 L 196.366 141.868 L 180.091 141.868 Z"
|
||||||
|
id="path2025" />
|
||||||
|
</g>
|
||||||
|
<rect x="221.413" y="1002.801" width="1.854" height="168.654"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(255, 230, 0); transform-origin: 222.34px 1087.13px;"
|
||||||
|
transform="matrix(0, -1, 1, 0, 763.873214, -637.849971)" id="rect2029" />
|
||||||
|
<rect x="374.641" y="101.049" width="0.825" height="198.326"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(255, 230, 0); transform-origin: 375.053px 200.208px;"
|
||||||
|
transform="matrix(0, 1, -1, 0, 294.61756, 111.712623)" id="rect2031" />
|
||||||
|
<g transform="matrix(1.967086, 0, 0, 2.255241, -488.110508, 103.828065)" id="g2039">
|
||||||
|
<path style="fill: rgb(192, 192, 192); stroke: rgb(76, 76, 76); stroke-width: 1;"
|
||||||
|
d="M 567.451 100 L 600 85.62 L 600 100 L 567.451 85.62 L 567.451 100 Z" id="path2033" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1;" d="M 583.725 92.747 L 583.725 77.736"
|
||||||
|
id="path2035" />
|
||||||
|
<path style="fill: rgb(192, 192, 192); stroke: rgb(76, 76, 76); stroke-width: 1;"
|
||||||
|
d="M 570.306 68.59 L 597.145 68.59 L 597.145 77.736 L 570.306 77.736 L 570.306 68.59 Z" id="path2037" />
|
||||||
|
</g>
|
||||||
|
<rect x="767.213" y="177.924" width="1.617" height="134.41"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(255, 230, 0); transform-origin: 768.019px 245.128px;" id="rect2041" />
|
||||||
|
<rect x="1070.54" y="448.35" height="97.503"
|
||||||
|
style="fill: rgb(216, 216, 216); stroke: rgb(255, 230, 0); transform-origin: 1071.58px 497.101px;" width="2.089"
|
||||||
|
id="rect2043" />
|
||||||
|
<line style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" x1="724.999" y1="710.607" x2="416.261" y2="944.343"
|
||||||
|
id="line2045" />
|
||||||
|
<rect x="276.022" y="952.397" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2047" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="173.639"
|
||||||
|
y="385.601" id="val_pt008a" transform="matrix(1.967086, 0, 0, 2.255241, -60.004517, 120.828065)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="158.322"
|
||||||
|
y="487.153" id="text2051" transform="matrix(1.967086, 0, 0, 2.255241, -52.444386, 79.824853)">01-PT008-A</text>
|
||||||
|
<rect x="279.22" y="1190.531" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2053" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="171.889"
|
||||||
|
y="502.901" id="val_zt002" transform="matrix(1.967086, 0, 0, 2.255241, -54.735848, 94.476006)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="159.616"
|
||||||
|
y="369.076" id="text2057" transform="matrix(1.967086, 0, 0, 2.255241, -44.004513, 105.828065)">01-ZT002</text>
|
||||||
|
<rect x="522.842" y="951.928" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2059" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="277.994"
|
||||||
|
y="384.55" id="text2061" transform="matrix(1.967086, 0, 0, 2.255241, -19.693392, 121.658036)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="261.191"
|
||||||
|
y="368.446" id="text2063" transform="matrix(1.967086, 0, 0, 2.255241, -3.615977, 109.752755)">01-TE014-A</text>
|
||||||
|
<rect x="524.88" y="1071.852" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2065" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="276.954"
|
||||||
|
y="443.949" id="text2067" transform="matrix(1.967086, 0, 0, 2.255241, -13.004511, 107.828065)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="260.151"
|
||||||
|
y="427.845" id="text2069" transform="matrix(1.967086, 0, 0, 2.255241, -5.004511, 98.828065)">01-TE014-B</text>
|
||||||
|
<rect x="761.776" y="952.548" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2071" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="380.973"
|
||||||
|
y="384.641" id="text2073" transform="matrix(1.967086, 0, 0, 2.255241, 18.995489, 121.828065)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="364.17"
|
||||||
|
y="368.537" id="text2075" transform="matrix(1.967086, 0, 0, 2.255241, 37.810299, 111.378892)">01-TE015-A</text>
|
||||||
|
<rect x="761.73" y="1070.507" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2077" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="379.933"
|
||||||
|
y="444.04" id="text2079" transform="matrix(1.967086, 0, 0, 2.255241, 20.995489, 106.828065)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="363.13"
|
||||||
|
y="427.936" id="text2081" transform="matrix(1.967086, 0, 0, 2.255241, 43.995491, 98.828065)">01-TE015-B</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="263.902"
|
||||||
|
y="487.153" id="text2083" transform="matrix(1.967086, 0, 0, 2.255241, -13.004511, 83.828065)">01-PT008-B</text>
|
||||||
|
<rect x="523.636" y="1188.88" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2085" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="277.469"
|
||||||
|
y="502.901" id="text2087" transform="matrix(1.967086, 0, 0, 2.255241, -16.004511, 92.828065)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="366.542"
|
||||||
|
y="487.153" id="text2089" transform="matrix(1.967086, 0, 0, 2.255241, 37.144352, 81.927483)">01-PT008-C</text>
|
||||||
|
<rect x="763.687" y="1188.982" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2091" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="380.109"
|
||||||
|
y="502.901" id="text2093" transform="matrix(1.967086, 0, 0, 2.255241, 19.144352, 91.927483)">###.##</text>
|
||||||
|
<rect x="1065.115" y="713.167" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2095" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="564.157"
|
||||||
|
y="282.044" id="text2097" transform="matrix(1.967086, 0, 0, 2.255241, -36.004513, 115.828065)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="554.354"
|
||||||
|
y="265.94" id="text2099" transform="matrix(1.967086, 0, 0, 2.255241, -32.004513, 95.828065)">01-KT001</text>
|
||||||
|
<rect x="1519.433" y="431.199" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2101" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="786.983"
|
||||||
|
y="157.016" id="text2103" transform="matrix(1.967086, 0, 0, 2.255241, -22.004511, 114.828065)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="770.18"
|
||||||
|
y="140.912" id="text2105" transform="matrix(1.967086, 0, 0, 2.255241, -2.004511, 91.828065)">01-PDT007</text>
|
||||||
|
<rect x="1517.328" y="300.54" width="131" height="51" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0);"
|
||||||
|
id="rect2107" />
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 16px;" x="785.777"
|
||||||
|
y="104.795" id="text2109" transform="matrix(1.967086, 0, 0, 2.255241, -22.004511, 101.828065)">###.##</text>
|
||||||
|
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 17px;" x="774.974"
|
||||||
|
y="88.691" id="text2111" transform="matrix(1.967086, 0, 0, 2.255241, -12.112564, 88.3145)">01-TE019</text>
|
||||||
|
<g style="" transform="matrix(1.967086, 0, 0, 2.764776, 509.086395, 21.011166)" id="stop">
|
||||||
|
<path style="stroke: rgb(76, 76, 76); stroke-width: 1; fill: rgb(224, 38, 38);"
|
||||||
|
d="M 160.097 163.649 L 200 148.321 L 200 163.649 L 160.097 148.321 L 160.097 163.649 Z" id="path-1" />
|
||||||
|
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1; transform-origin: 180.051px 148.792px;"
|
||||||
|
d="M 180.051 155.918 L 180.051 141.666" id="path-2" />
|
||||||
|
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1;"
|
||||||
|
d="M 180.091 141.868 L 163.816 141.868 L 163.902 140.59 L 164.165 139.38 L 164.515 138.17 L 165.127 137.028 L 165.827 135.952 L 166.614 134.944 L 167.578 133.934 L 168.627 133.061 L 169.764 132.255 L 170.99 131.582 L 172.39 130.909 L 173.79 130.439 L 175.277 129.968 L 176.853 129.699 L 178.428 129.497 L 180.091 129.431 L 181.754 129.497 L 183.415 129.699 L 184.904 129.968 L 186.478 130.439 L 187.879 130.909 L 189.191 131.582 L 190.416 132.255 L 191.642 133.061 L 192.692 133.934 L 193.567 134.944 L 194.441 135.952 L 195.054 137.028 L 195.666 138.17 L 196.017 139.38 L 196.279 140.59 L 196.366 141.868 L 180.091 141.868 Z"
|
||||||
|
id="path-3" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 411 KiB |
@@ -1,126 +1,174 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
|
|
||||||
async function ApiRequest(
|
const baseURL = import.meta.env.VITE_API_SERVER;
|
||||||
urlParams = { method: 'GET', params: {}, url: '', prefix: '/', token: true }
|
|
||||||
) {
|
|
||||||
const baseURLDef = `${import.meta.env.VITE_API_SERVER}`;
|
|
||||||
const instance = axios.create({
|
|
||||||
baseURL: urlParams.url ?? baseURLDef,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isFormData = urlParams.params instanceof FormData;
|
const instance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// axios khusus refresh
|
||||||
|
const refreshApi = axios.create({
|
||||||
|
baseURL,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
async (error) => {
|
||||||
|
const originalRequest = error.config;
|
||||||
|
|
||||||
|
console.error('🚨 Response Error Interceptor:', {
|
||||||
|
status: error.response?.status,
|
||||||
|
url: originalRequest.url,
|
||||||
|
message: error.response?.data?.message,
|
||||||
|
hasRetried: originalRequest._retry,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||||
|
originalRequest._retry = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// console.log('🔄 Refresh token dipanggil...');
|
||||||
|
const refreshRes = await refreshApi.post('/auth/refresh-token');
|
||||||
|
|
||||||
|
const newAccessToken = refreshRes.data.data.accessToken;
|
||||||
|
localStorage.setItem('token', newAccessToken);
|
||||||
|
// console.log('✅ Token refreshed successfully');
|
||||||
|
|
||||||
|
// update token di header
|
||||||
|
instance.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
|
||||||
|
originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
|
||||||
|
|
||||||
|
// console.log('🔁 Retrying original request...');
|
||||||
|
return instance(originalRequest);
|
||||||
|
} catch (refreshError) {
|
||||||
|
console.error(
|
||||||
|
'❌ Refresh token gagal:',
|
||||||
|
refreshError.response?.data || refreshError.message
|
||||||
|
);
|
||||||
|
localStorage.clear();
|
||||||
|
window.location.href = '/signin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function ApiRequest({ method = 'GET', params = {}, prefix = '/', token = true } = {}) {
|
||||||
|
const isFormData = params instanceof FormData;
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
method: urlParams.method,
|
method,
|
||||||
url: urlParams.prefix ?? '/',
|
url: prefix,
|
||||||
data: urlParams.params,
|
data: params,
|
||||||
// yang lama
|
|
||||||
// headers: {
|
|
||||||
// 'Content-Type': 'application/json',
|
|
||||||
// 'Accept-Language': 'en_US',
|
|
||||||
// },
|
|
||||||
|
|
||||||
// yang baru
|
|
||||||
headers: {
|
headers: {
|
||||||
'Accept-Language': 'en_US',
|
'Accept-Language': 'en_US',
|
||||||
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (urlParams.params === 'doc') {
|
|
||||||
request.responseType = 'arraybuffer';
|
|
||||||
request.headers['Content-Type'] = 'blob';
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(request);
|
|
||||||
|
|
||||||
// console.log('prefix', urlParams.prefix);
|
|
||||||
|
|
||||||
const tokenRedirect = sessionStorage.getItem('token_redirect');
|
const tokenRedirect = sessionStorage.getItem('token_redirect');
|
||||||
|
|
||||||
let stringToken = '';
|
let rawToken = '';
|
||||||
|
|
||||||
if (tokenRedirect !== null) {
|
if (tokenRedirect !== null) {
|
||||||
stringToken = tokenRedirect;
|
rawToken = tokenRedirect;
|
||||||
// console.log(`sessionStorage: ${tokenRedirect}`);
|
// console.log(`sessionStorage: ${tokenRedirect}`);
|
||||||
} else {
|
} else {
|
||||||
stringToken = localStorage.getItem('token');
|
rawToken = localStorage.getItem('token');
|
||||||
// console.log(`localStorage: ${stringToken}`);
|
// console.log(`localStorage: ${rawToken}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlParams.prefix !== 'auth/login') {
|
if (token && rawToken) {
|
||||||
const tokenWithQuotes = stringToken;
|
const cleanToken = rawToken.replace(/"/g, '');
|
||||||
const token = tokenWithQuotes.replace(/"/g, '');
|
request.headers['Authorization'] = `Bearer ${cleanToken}`;
|
||||||
const AUTH_TOKEN = `Bearer ${token}`;
|
// console.log('🔐 Sending request with token:', cleanToken.substring(0, 20) + '...');
|
||||||
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
|
} else {
|
||||||
} else if (urlParams.token == true) {
|
console.warn('⚠️ No token found in localStorage');
|
||||||
const tokenWithQuotes = stringToken;
|
|
||||||
const token = tokenWithQuotes.replace(/"/g, '');
|
|
||||||
const AUTH_TOKEN = `Bearer ${token}`;
|
|
||||||
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await instance(request)
|
// console.log('📤 API Request:', { method, url: prefix, hasToken: !!rawToken });
|
||||||
.then(function (response) {
|
|
||||||
const responseCustom = response;
|
|
||||||
responseCustom.error = false;
|
|
||||||
return responseCustom;
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
console.log('error', error.toJSON());
|
|
||||||
|
|
||||||
const errorData = error.toJSON();
|
try {
|
||||||
|
const response = await instance(request);
|
||||||
const respError = error.response ?? {};
|
// console.log('✅ API Response:', {
|
||||||
cekError(
|
// url: prefix,
|
||||||
errorData.status,
|
// status: response.status,
|
||||||
error?.response?.data?.message ?? errorData?.message ?? 'Something Wrong'
|
// statusCode: response.data?.statusCode,
|
||||||
);
|
// });
|
||||||
respError.error = true;
|
return { ...response, error: false };
|
||||||
return respError;
|
} catch (error) {
|
||||||
|
const status = error?.response?.status || 500;
|
||||||
|
const message = error?.response?.data?.message || error.message || 'Something Wrong';
|
||||||
|
console.error('❌ API Error:', {
|
||||||
|
url: prefix,
|
||||||
|
status,
|
||||||
|
message,
|
||||||
|
fullError: error?.response?.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (status !== 401) {
|
||||||
|
await cekError(status, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...error.response, error: true };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cekError(props, message = '') {
|
async function cekError(status, message = '') {
|
||||||
console.log('status code', props);
|
if (status === 403) {
|
||||||
if (props === 401) {
|
await Swal.fire({
|
||||||
Swal.fire({
|
|
||||||
icon: 'warning',
|
icon: 'warning',
|
||||||
title: 'Peringatan',
|
title: 'Forbidden',
|
||||||
text: `${message}, Silahkan login`,
|
text: message,
|
||||||
}).then((result) => {
|
});
|
||||||
if (result.isConfirmed) {
|
} else if (status >= 500) {
|
||||||
localStorage.clear();
|
await Swal.fire({
|
||||||
location.replace('/signin');
|
icon: 'error',
|
||||||
} else if (result.isDenied) {
|
title: 'Server Error',
|
||||||
Swal.fire('Changes are not saved', '', 'info');
|
text: message,
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Swal.fire({
|
await Swal.fire({
|
||||||
icon: 'warning',
|
icon: 'warning',
|
||||||
title: 'Peringatan',
|
title: 'Peringatan',
|
||||||
text: message,
|
text: message,
|
||||||
}).then((result) => {});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SendRequest = async (queryParams) => {
|
const SendRequest = async (queryParams) => {
|
||||||
try {
|
try {
|
||||||
const response = await ApiRequest(queryParams);
|
const response = await ApiRequest(queryParams);
|
||||||
return response || [];
|
|
||||||
} catch (error) {
|
// If ApiRequest returned error flag, return error structure
|
||||||
console.log('error', error);
|
if (response.error) {
|
||||||
if (error.response) {
|
const errorMsg = response.data?.message || response.statusText || 'Request failed';
|
||||||
console.error('Error Status:', error.response.status); // Status error, misal: 401
|
|
||||||
console.error('Error Data:', error.response.data); // Detail pesan error
|
// Return consistent error structure instead of empty array
|
||||||
console.error('Error Pesan:', error.response.data.message); //Pesan error
|
return {
|
||||||
} else {
|
statusCode: response.status || 500,
|
||||||
console.error('Error:', error.message);
|
message: errorMsg,
|
||||||
|
data: null,
|
||||||
|
error: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Swal.fire({ icon: 'error', text: error });
|
|
||||||
// return error;
|
return response || { statusCode: 200, data: [], message: 'Success' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ SendRequest catch error:', error);
|
||||||
|
|
||||||
|
// Don't show Swal here, let the calling code handle it
|
||||||
|
// This allows better error handling in each API call
|
||||||
|
return {
|
||||||
|
statusCode: 500,
|
||||||
|
message: error.message || 'Something went wrong',
|
||||||
|
data: null,
|
||||||
|
error: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
112
src/components/Global/CardList.jsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card, Button, Row, Col, Typography, Space, Tag } from 'antd';
|
||||||
|
import { EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const CardList = ({
|
||||||
|
data,
|
||||||
|
column,
|
||||||
|
header,
|
||||||
|
showPreviewModal,
|
||||||
|
showEditModal,
|
||||||
|
showDeleteDialog,
|
||||||
|
cardColor,
|
||||||
|
fieldColor,
|
||||||
|
}) => {
|
||||||
|
const getCardStyle = (color) => {
|
||||||
|
const colorStyle = color ?? '#F3EDEA'; // Orange color
|
||||||
|
return {
|
||||||
|
border: `2px solid ${colorStyle}`,
|
||||||
|
borderRadius: '8px',
|
||||||
|
textAlign: 'center', // Center text
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTitleStyle = (color) => {
|
||||||
|
const backgroundColor = color ?? '#FCF2ED';
|
||||||
|
return {
|
||||||
|
backgroundColor,
|
||||||
|
color: '#fff',
|
||||||
|
padding: '2px 8px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
display: 'inline-block', // ganti inline-block → block
|
||||||
|
width: 'fit-content', // biar lebarnya tetap menyesuaikan teks
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={[16, 16]} style={{ marginTop: '16px', justifyContent: 'left' }}>
|
||||||
|
{data.map((item) => (
|
||||||
|
<Col xs={24} sm={24} md={12} lg={6} key={item.device_id}>
|
||||||
|
<Card
|
||||||
|
title={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between', // kiri & kanan
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={getTitleStyle(fieldColor ? item[fieldColor] : cardColor)}
|
||||||
|
>
|
||||||
|
{item[header]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
style={getCardStyle(fieldColor ? item[fieldColor] : cardColor)}
|
||||||
|
actions={[
|
||||||
|
showPreviewModal && (
|
||||||
|
<EyeOutlined
|
||||||
|
style={{ color: '#1890ff' }}
|
||||||
|
key="preview"
|
||||||
|
onClick={() => showPreviewModal(item)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
showEditModal && (
|
||||||
|
<EditOutlined
|
||||||
|
style={{ color: '#faad14' }}
|
||||||
|
key="edit"
|
||||||
|
onClick={() => showEditModal(item)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
showDeleteDialog && (
|
||||||
|
<DeleteOutlined
|
||||||
|
style={{ color: '#ff1818' }}
|
||||||
|
key="delete"
|
||||||
|
onClick={() => showDeleteDialog(item)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
].filter(Boolean)} // <== Hapus elemen yang undefined
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'left' }}>
|
||||||
|
{column.map((itemCard, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
{!itemCard.hidden &&
|
||||||
|
itemCard.title !== 'No' &&
|
||||||
|
itemCard.title !== 'Action' && (
|
||||||
|
<p style={{ margin: '8px 0' }}>
|
||||||
|
<Text strong>{itemCard.title}:</Text>{' '}
|
||||||
|
{itemCard.render
|
||||||
|
? itemCard.render(
|
||||||
|
item[itemCard.dataIndex],
|
||||||
|
item,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
: item[itemCard.dataIndex] ||
|
||||||
|
item[itemCard.key] ||
|
||||||
|
'-'}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardList;
|
||||||
48
src/components/Global/DateRealTime.jsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
const JamRealtimeAntd = () => {
|
||||||
|
const [waktu, setWaktu] = useState(new Date());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setWaktu(new Date());
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Format custom manual untuk konsistensi
|
||||||
|
const formatWaktuLengkap = (date) => {
|
||||||
|
const hari = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
|
||||||
|
const bulan = [
|
||||||
|
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
|
||||||
|
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
|
||||||
|
];
|
||||||
|
|
||||||
|
const namaHari = hari[date.getDay()];
|
||||||
|
const tanggal = date.getDate().toString().padStart(2, '0');
|
||||||
|
const namaBulan = bulan[date.getMonth()];
|
||||||
|
const tahun = date.getFullYear();
|
||||||
|
|
||||||
|
const jam = date.getHours().toString().padStart(2, '0');
|
||||||
|
const menit = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
const detik = date.getSeconds().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
return `${namaHari}, ${tanggal} ${namaBulan} ${tahun} ${jam}:${menit}:${detik}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text style={{
|
||||||
|
fontSize: '25px',
|
||||||
|
// fontWeight: 'bold',
|
||||||
|
color: '#1BAA56'
|
||||||
|
}}>
|
||||||
|
{formatWaktuLengkap(waktu)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JamRealtimeAntd;
|
||||||
@@ -1,19 +1,27 @@
|
|||||||
// mqttService.js
|
// mqttService.js
|
||||||
import mqtt from 'mqtt';
|
import mqtt from 'mqtt';
|
||||||
|
|
||||||
const mqttUrl = 'ws://36.66.16.49:9001';
|
const mqttUrl = `${import.meta.env.VITE_MQTT_SERVER ?? 'ws://localhost:1884'}`;
|
||||||
const topics = ['SYPIU_GGCP', 'SYPIU_GGCP/list-permit/changed','SYPIU_GGCP/list-user/changed'];
|
const topics = [
|
||||||
|
'PIU_COD/AIR_DRYER/OVERVIEW',
|
||||||
|
'PIU_COD/AIR_DRYER/AIR_DRYER_A',
|
||||||
|
'PIU_COD/AIR_DRYER/AIR_DRYER_B',
|
||||||
|
'PIU_COD/AIR_DRYER/AIR_DRYER_C',
|
||||||
|
'PIU_COD/COMPRESSOR/OVERVIEW',
|
||||||
|
'PIU_COD/COMPRESSOR/COMPRESSOR_A',
|
||||||
|
'PIU_COD/COMPRESSOR/COMPRESSOR_B',
|
||||||
|
'PIU_COD/COMPRESSOR/COMPRESSOR_C'
|
||||||
|
];
|
||||||
const options = {
|
const options = {
|
||||||
keepalive: 30,
|
keepalive: 30,
|
||||||
clientId: 'react_mqtt_' + Math.random().toString(16).substr(2, 8),
|
clientId: 'react_mqtt_' + Math.random().toString(16).substr(2, 8),
|
||||||
protocolId: 'MQTT',
|
protocolId: 'MQTT',
|
||||||
protocolVersion: 4,
|
protocolVersion: 4,
|
||||||
clean: true,
|
clean: true,
|
||||||
reconnectPeriod: 1000,
|
reconnectPeriod: 1000,
|
||||||
connectTimeout: 30 * 1000,
|
connectTimeout: 30 * 1000,
|
||||||
username: 'ide', // jika ada
|
username: `${import.meta.env.VITE_MQTT_USERNAME ?? ''}`, // jika ada
|
||||||
password: 'aremania', // jika ada
|
password: `${import.meta.env.VITE_MQTT_PASSWORD ?? ''}`, // jika ada
|
||||||
};
|
};
|
||||||
|
|
||||||
const client = mqtt.connect(mqttUrl, options);
|
const client = mqtt.connect(mqttUrl, options);
|
||||||
@@ -22,37 +30,37 @@ const client = mqtt.connect(mqttUrl, options);
|
|||||||
let isConnected = false;
|
let isConnected = false;
|
||||||
|
|
||||||
client.on('connect', () => {
|
client.on('connect', () => {
|
||||||
console.log('MQTT Connected');
|
console.log('MQTT Connected');
|
||||||
isConnected = true;
|
isConnected = true;
|
||||||
|
|
||||||
// Subscribe default topic
|
// Subscribe default topic
|
||||||
client.subscribe(topics, (err) => {
|
client.subscribe(topics, (err) => {
|
||||||
if (err) console.error('Subscribe error:', err);
|
if (err) console.error('Subscribe error:', err);
|
||||||
else console.log(`Subscribed to topics: ${topics.join(', ')}`);
|
else console.log(`Subscribed to topics: ${topics.join(', ')}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('error', (err) => {
|
client.on('error', (err) => {
|
||||||
console.error('Connection error: ', err);
|
console.error('Connection error: ', err);
|
||||||
client.end();
|
client.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('close', () => {
|
client.on('close', () => {
|
||||||
console.log('MQTT Disconnected');
|
console.log('MQTT Disconnected');
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish message to MQTT
|
* Publish message to MQTT
|
||||||
* @param {string} topic
|
* @param {string} topic
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
*/
|
*/
|
||||||
const publishMessage = (topic, message) => {
|
const publishMessage = (topic, message) => {
|
||||||
if (client && isConnected && message.trim() !== '') {
|
if (client && isConnected && message.trim() !== '') {
|
||||||
client.publish(topic, message);
|
client.publish(topic, message);
|
||||||
} else {
|
} else {
|
||||||
console.warn('MQTT not connected or message empty');
|
console.warn('MQTT not connected or message empty');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,13 +68,34 @@ const publishMessage = (topic, message) => {
|
|||||||
* @param {function} callback - Function(topic, message)
|
* @param {function} callback - Function(topic, message)
|
||||||
*/
|
*/
|
||||||
const listenMessage = (callback) => {
|
const listenMessage = (callback) => {
|
||||||
client.on('message', (topic, message) => {
|
client.on('message', (topic, message) => {
|
||||||
callback(topic, message.toString());
|
callback(topic, message.toString());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
const setValSvg = (listenTopic, svg) => {
|
||||||
publishMessage,
|
client.on('message', (topic, message) => {
|
||||||
listenMessage,
|
// console.log(topic ,' = ', listenTopic);
|
||||||
client,
|
if (topic === listenTopic) {
|
||||||
|
const objChanel = JSON.parse(message);
|
||||||
|
|
||||||
|
Object.entries(objChanel).forEach(([key, value]) => {
|
||||||
|
// console.log(key, value);
|
||||||
|
const el = svg.getElementById(key);
|
||||||
|
if (el) {
|
||||||
|
if (value === true) {
|
||||||
|
el.style.display = ''; // sembunyikan
|
||||||
|
} else if (value === false) {
|
||||||
|
el.style.display = 'none';
|
||||||
|
} else if (!isNaN(value)) {
|
||||||
|
el.textContent = Number(value ?? 0.0).toFixed(2);
|
||||||
|
} else {
|
||||||
|
el.textContent = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { publishMessage, listenMessage, setValSvg };
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import axios from 'axios';
|
|||||||
|
|
||||||
const RegistrationRequest = async ({ method, prefix, params, headers = {} }) => {
|
const RegistrationRequest = async ({ method, prefix, params, headers = {} }) => {
|
||||||
const baseURL = `${import.meta.env.VITE_API_SERVER}`;
|
const baseURL = `${import.meta.env.VITE_API_SERVER}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method: method,
|
method: method,
|
||||||
@@ -22,4 +22,4 @@ const RegistrationRequest = async ({ method, prefix, params, headers = {} }) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RegistrationRequest;
|
export default RegistrationRequest;
|
||||||
|
|||||||
@@ -1,64 +1,10 @@
|
|||||||
import React, { memo, useState, useEffect, useRef } from 'react';
|
import React, { memo, useState, useEffect, useRef } from 'react';
|
||||||
import { Table, Pagination, Row, Col, Card, Grid, Button, Typography, Tag } from 'antd';
|
import { Table, Pagination, Row, Col, Card, Grid, Button, Typography, Tag, Segmented } from 'antd';
|
||||||
import {
|
import { MacCommandOutlined, TableOutlined } from '@ant-design/icons';
|
||||||
PlusOutlined,
|
import CardList from './CardList';
|
||||||
FilterOutlined,
|
|
||||||
EditOutlined,
|
|
||||||
DeleteOutlined,
|
|
||||||
EyeOutlined,
|
|
||||||
SearchOutlined,
|
|
||||||
FilePdfOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { setFilterData } from './DataFilter';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const defCard = {
|
|
||||||
r1: {
|
|
||||||
style: { fontWeight: 'bold', fontSize: 13 },
|
|
||||||
type: 'primary',
|
|
||||||
color: '',
|
|
||||||
text: 'Cold Work Permit',
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
r2: {
|
|
||||||
style: { marginLeft: 8, fontSize: 13 },
|
|
||||||
type: 'primary',
|
|
||||||
color: 'success',
|
|
||||||
text: 'Pengajuan',
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
r3: {
|
|
||||||
style: { fontSize: 12 },
|
|
||||||
type: 'secondary',
|
|
||||||
color: '',
|
|
||||||
text: 'No. IVR/20250203/XXV/III',
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
r4: {
|
|
||||||
style: { fontSize: 12 },
|
|
||||||
type: 'primary',
|
|
||||||
color: '',
|
|
||||||
text: '3 Feb 2025',
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
r5: {
|
|
||||||
style: { fontSize: 12 },
|
|
||||||
type: 'primary',
|
|
||||||
color: '',
|
|
||||||
text: 'Lokasi Gudang Robang',
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
r6: {
|
|
||||||
style: { fontSize: 12 },
|
|
||||||
type: 'primary',
|
|
||||||
color: '',
|
|
||||||
text: 'maka tambahkan user tersebut dalam user_partner dengan partner baru yang ditambahkan diatas',
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
action: (e) => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const TableList = memo(function TableList({
|
const TableList = memo(function TableList({
|
||||||
getData,
|
getData,
|
||||||
queryParams,
|
queryParams,
|
||||||
@@ -66,61 +12,130 @@ const TableList = memo(function TableList({
|
|||||||
triger,
|
triger,
|
||||||
mobile,
|
mobile,
|
||||||
rowSelection = null,
|
rowSelection = null,
|
||||||
|
header = 'name',
|
||||||
|
showPreviewModal,
|
||||||
|
showEditModal,
|
||||||
|
showDeleteDialog,
|
||||||
|
cardColor,
|
||||||
|
fieldColor,
|
||||||
|
firstLoad = true,
|
||||||
|
columnDynamic = false,
|
||||||
|
cardComponent, // New prop for custom card component
|
||||||
|
onStockUpdate, // Prop to pass to card component
|
||||||
|
onGetData, // Callback to execute when data is received
|
||||||
}) {
|
}) {
|
||||||
const [gridLoading, setGridLoading] = useState(false);
|
const [gridLoading, setGridLoading] = useState(false);
|
||||||
|
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
const [pagingResponse, setPagingResponse] = useState({
|
|
||||||
totalData: '',
|
|
||||||
perPage: '',
|
|
||||||
totalPage: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
current: 1,
|
current_page: 1,
|
||||||
limit: 10,
|
current_limit: 10,
|
||||||
total: 0,
|
total_limit: 0,
|
||||||
|
total_page: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [columnsDynamic, setColumnsDynamic] = useState(columns);
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useState('table');
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
|
const [renderCount, setRenderCount] = useState(firstLoad ? 1 : 0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
filter(1, 10);
|
if (renderCount < 1) {
|
||||||
|
setRenderCount(renderCount + 1);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
filter(1, pagination.current_limit);
|
||||||
|
}
|
||||||
}, [triger]);
|
}, [triger]);
|
||||||
|
|
||||||
const filter = async (currentPage, pageSize) => {
|
const filter = async (currentPage, pageSize) => {
|
||||||
setGridLoading(true);
|
setGridLoading(true);
|
||||||
|
|
||||||
const paging = {
|
const paging = {
|
||||||
page: currentPage,
|
page: Number(currentPage),
|
||||||
limit: pageSize,
|
limit: Number(pageSize),
|
||||||
};
|
};
|
||||||
|
|
||||||
const param = new URLSearchParams({ ...paging, ...queryParams });
|
const param = new URLSearchParams({ ...paging, ...queryParams });
|
||||||
|
|
||||||
const resData = await getData(param);
|
const resData = await getData(param);
|
||||||
|
|
||||||
|
if (columnDynamic && resData) {
|
||||||
|
const columnsApi = resData[columnDynamic] ?? '';
|
||||||
|
|
||||||
|
// Pisahkan string menjadi array kolom
|
||||||
|
const colArray = columnsApi.split(',').map((c) => c.trim());
|
||||||
|
|
||||||
|
// Kolom default datetime di awal
|
||||||
|
const defaultColumns = [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Datetime',
|
||||||
|
dataIndex: 'datetime',
|
||||||
|
key: 'datetime',
|
||||||
|
width: '15%',
|
||||||
|
// render: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Buat kolom numerik dengan format 4 angka di belakang koma
|
||||||
|
const numericColumns = colArray.map((colName) => ({
|
||||||
|
title: colName,
|
||||||
|
dataIndex: colName,
|
||||||
|
key: colName,
|
||||||
|
align: 'right',
|
||||||
|
width: 'auto',
|
||||||
|
render: (value) => {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value.toFixed(4);
|
||||||
|
}
|
||||||
|
return value ?? '-';
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Gabungkan default + API columns
|
||||||
|
setColumnsDynamic([...defaultColumns, ...numericColumns]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchedData = resData?.data ?? [];
|
||||||
|
|
||||||
|
// Panggil callback jika disediakan
|
||||||
|
if (onGetData && typeof onGetData === 'function') {
|
||||||
|
onGetData(fetchedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(fetchedData);
|
||||||
|
|
||||||
|
const pagingData = resData?.paging;
|
||||||
|
|
||||||
|
if (pagingData) {
|
||||||
|
setPagination((prev) => ({
|
||||||
|
...prev,
|
||||||
|
current_page: pagingData.current_page || 1,
|
||||||
|
current_limit: pagingData.current_limit || 10,
|
||||||
|
total_limit: pagingData.total_limit || 0,
|
||||||
|
total_page: pagingData.total_page || 1,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
setGridLoading(false);
|
||||||
|
|
||||||
if (resData) {
|
if (resData) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setGridLoading(false);
|
setGridLoading(false);
|
||||||
}, 900);
|
}, 900);
|
||||||
}
|
} else {
|
||||||
|
setGridLoading(false);
|
||||||
setData(resData.data.data ?? []);
|
return;
|
||||||
setFilterData(resData.data.data ?? []);
|
|
||||||
|
|
||||||
if (resData.status == 200) {
|
|
||||||
setPagingResponse({
|
|
||||||
totalData: resData.data.total,
|
|
||||||
perPage: resData.data.paging.page_total,
|
|
||||||
totalPage: resData.data.paging.limit,
|
|
||||||
});
|
|
||||||
|
|
||||||
setPagination((prev) => ({
|
|
||||||
...prev,
|
|
||||||
current: resData.data.paging.page,
|
|
||||||
limit: resData.data.paging.limit,
|
|
||||||
total: resData.data.paging.total,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,7 +143,7 @@ const TableList = memo(function TableList({
|
|||||||
setPagination((prev) => ({
|
setPagination((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
current: page,
|
current: page,
|
||||||
pageSize,
|
limit: pageSize,
|
||||||
}));
|
}));
|
||||||
filter(page, pageSize);
|
filter(page, pageSize);
|
||||||
};
|
};
|
||||||
@@ -137,145 +152,66 @@ const TableList = memo(function TableList({
|
|||||||
|
|
||||||
const isMobile = !screens.md; // kalau kurang dari md (768px) dianggap mobile
|
const isMobile = !screens.md; // kalau kurang dari md (768px) dianggap mobile
|
||||||
|
|
||||||
|
// Use the custom card component if provided, otherwise default to CardList
|
||||||
|
const CardViewComponent = cardComponent || CardList;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{isMobile && mobile ? (
|
<Segmented
|
||||||
<Row gutter={24}>
|
options={[
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
{ value: 'table', icon: <TableOutlined /> },
|
||||||
{data.map((item) => (
|
{ value: 'card', icon: <MacCommandOutlined /> },
|
||||||
<Card
|
]}
|
||||||
key={item.id}
|
value={viewMode}
|
||||||
title={
|
onChange={setViewMode}
|
||||||
(mobile.r1 || mobile.r2) && (
|
/>
|
||||||
<div
|
{(isMobile && mobile) || viewMode === 'card' ? (
|
||||||
style={{
|
<CardViewComponent
|
||||||
display: 'flex',
|
cardColor={cardColor}
|
||||||
justifyContent: 'space-between',
|
fieldColor={fieldColor}
|
||||||
alignItems: 'center',
|
data={data}
|
||||||
}}
|
column={columnsDynamic}
|
||||||
>
|
header={header}
|
||||||
{mobile.r1 && (
|
showPreviewModal={showPreviewModal}
|
||||||
<span style={mobile.r1.style ?? {}}>
|
showEditModal={showEditModal}
|
||||||
{item[mobile.r1.name] ?? mobile.r1.text ?? ''}
|
showDeleteDialog={showDeleteDialog}
|
||||||
</span>
|
onStockUpdate={onStockUpdate}
|
||||||
)}
|
/>
|
||||||
{mobile.r2 && (
|
|
||||||
<Tag
|
|
||||||
color={mobile.r2.color ?? ''}
|
|
||||||
style={mobile.r2.style ?? {}}
|
|
||||||
>
|
|
||||||
{item[mobile.r2.name] ?? mobile.r2.text ?? ''}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
||||||
{mobile.r3 && mobile.r4 && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
type={mobile.r3 ? mobile.r3.type ?? 'primary' : ''}
|
|
||||||
style={mobile.r3 ? mobile.r3.style ?? {} : {}}
|
|
||||||
>
|
|
||||||
{item[mobile.r3 ? mobile.r3.name : ''] ?? ''}
|
|
||||||
</Text>
|
|
||||||
<Text style={mobile.r4 ? mobile.r4.style ?? {} : {}}>
|
|
||||||
{item[mobile.r4 ? mobile.r4.name : ''] ?? ''}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{mobile.r5 && (
|
|
||||||
<Text
|
|
||||||
type={mobile.r5 ? mobile.r5.type ?? 'secondary' : ''}
|
|
||||||
style={mobile.r5 ? mobile.r5.style ?? {} : {}}
|
|
||||||
>
|
|
||||||
{item[mobile.r5 ? mobile.r5.name : ''] ?? ''}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{mobile.r6 && (
|
|
||||||
<div>
|
|
||||||
<Text
|
|
||||||
type={mobile.r6 ? mobile.r6.type ?? 'primary' : ''}
|
|
||||||
style={mobile.r6 ? mobile.r6.style ?? {} : {}}
|
|
||||||
>
|
|
||||||
{item[mobile.r6 ? mobile.r6.name : ''] ?? ''}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: 16,
|
|
||||||
borderTop: '1px solid #f0f0f0',
|
|
||||||
paddingTop: 8,
|
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
shape="round"
|
|
||||||
icon={<EyeOutlined />}
|
|
||||||
onClick={(e) => {
|
|
||||||
mobile.action(item);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Detail
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Row>
|
|
||||||
) : (
|
) : (
|
||||||
<Row gutter={24}>
|
<Row gutter={24} style={{ marginTop: '16px' }}>
|
||||||
{/* TABLE */}
|
|
||||||
<Table
|
<Table
|
||||||
rowSelection={rowSelection || null}
|
rowSelection={rowSelection || null}
|
||||||
columns={columns}
|
columns={columnsDynamic}
|
||||||
dataSource={data.map((item, index) => ({ ...item, key: index }))}
|
dataSource={data.map((item, index) => ({ ...item, key: index }))}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
loading={gridLoading}
|
loading={gridLoading}
|
||||||
scroll={{
|
scroll={{ y: 520 }}
|
||||||
y: 520,
|
size="small"
|
||||||
x: 1300,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* PAGINATION */}
|
|
||||||
<Col xs={24} style={{ marginTop: '16px' }}>
|
|
||||||
<Row justify="space-between" align="middle">
|
|
||||||
<Col>
|
|
||||||
<div>
|
|
||||||
Menampilkan {pagingResponse.totalData} Data dari{' '}
|
|
||||||
{pagingResponse.perPage} Halaman
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
<Col>
|
|
||||||
<Pagination
|
|
||||||
showSizeChanger
|
|
||||||
onChange={handlePaginationChange}
|
|
||||||
onShowSizeChange={handlePaginationChange}
|
|
||||||
current={pagination.current}
|
|
||||||
pageSize={pagination.pageSize}
|
|
||||||
total={pagination.total}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
{/* PAGINATION */}
|
||||||
|
<Row justify="space-between" align="middle">
|
||||||
|
<Col>
|
||||||
|
<div>
|
||||||
|
Menampilkan {pagination.current_limit} data halaman{' '}
|
||||||
|
{pagination.current_page} dari total {pagination.total_limit} data
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Pagination
|
||||||
|
showSizeChanger
|
||||||
|
onChange={handlePaginationChange}
|
||||||
|
onShowSizeChange={handlePaginationChange}
|
||||||
|
current={pagination.current_page}
|
||||||
|
pageSize={pagination.current_limit}
|
||||||
|
total={pagination.total_limit}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default TableList;
|
export default TableList;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const NotifOk = ({ icon, title, message }) => {
|
|||||||
icon: icon,
|
icon: icon,
|
||||||
title: title,
|
title: title,
|
||||||
text: message,
|
text: message,
|
||||||
|
html: message.replace(/\n/g, '<br/>'),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
42
src/context/AuthContext.jsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { createContext, useContext, useState, useEffect } from "react";
|
||||||
|
import { SendRequest } from "../utils/api";
|
||||||
|
|
||||||
|
const AuthContext = createContext();
|
||||||
|
|
||||||
|
export const AuthProvider = ({ children }) => {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
// fetch user info saat mount
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUser = async () => {
|
||||||
|
try {
|
||||||
|
const res = await SendRequest({ prefix: "/auth/me", method: "GET" });
|
||||||
|
setUser(res);
|
||||||
|
} catch (err) {
|
||||||
|
setUser(null);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchUser();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const login = (accessToken) => {
|
||||||
|
localStorage.setItem("token", accessToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
localStorage.clear();
|
||||||
|
setUser(null);
|
||||||
|
window.location.href = "/signin";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ user, setUser, login, logout, loading }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAuth = () => useContext(AuthContext);
|
||||||
@@ -18,4 +18,67 @@
|
|||||||
html body {
|
html body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom green Sidebar Menu Styles */
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-item-selected {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-item-selected::after {
|
||||||
|
border-right-color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-item:hover,
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-submenu-title:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-submenu-selected > .ant-menu-submenu-title {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-green-menu.ant-menu-dark.ant-menu-inline .ant-menu-sub {
|
||||||
|
background: rgba(0, 0, 0, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-item,
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-submenu-title {
|
||||||
|
color: rgba(255, 255, 255, 0.9) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-item-active,
|
||||||
|
.custom-green-menu.ant-menu-dark .ant-menu-submenu-active > .ant-menu-submenu-title {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*start styling for scrollbar menu */
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: linear-gradient(180deg, #1BAA56 0%, rgb(5, 75, 34) 100%);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: linear-gradient(180deg, #2bc56d 0%, rgb(8, 94, 43) 100%);
|
||||||
|
}
|
||||||
|
.custom-menu-scrollbar {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #1BAA56 transparent;
|
||||||
|
}
|
||||||
|
/* Hilangkan panah atas/bawah dengan important */
|
||||||
|
.custom-menu-scrollbar::-webkit-scrollbar-button {
|
||||||
|
display: none !important;
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
}
|
||||||
|
/*end styling for scrollbar menu */
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Layout, theme, Space, Typography, Breadcrumb, Button } from 'antd';
|
import { Layout, Typography, Breadcrumb, Button, theme } from 'antd';
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
import { UserOutlined } from '@ant-design/icons';
|
||||||
import handleLogOut from '../Utils/Auth/Logout';
|
import handleLogOut from '../Utils/Auth/Logout';
|
||||||
import { useBreadcrumb } from './LayoutBreadcrumb';
|
import { useBreadcrumb } from './LayoutBreadcrumb';
|
||||||
import { decryptData } from '../components/Global/Formatter';
|
import { decryptData } from '../components/Global/Formatter';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import DateRealTime from '../components/Global/DateRealTime';
|
||||||
|
|
||||||
const { Link, Text } = Typography;
|
const { Link, Text } = Typography;
|
||||||
const { Header } = Layout;
|
const { Header } = Layout;
|
||||||
@@ -12,18 +13,45 @@ const { Header } = Layout;
|
|||||||
const LayoutHeader = () => {
|
const LayoutHeader = () => {
|
||||||
const { breadcrumbItems } = useBreadcrumb();
|
const { breadcrumbItems } = useBreadcrumb();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
|
||||||
token: { colorBgContainer, colorBorder, colorText },
|
|
||||||
} = theme.useToken();
|
|
||||||
|
|
||||||
// Ambil data user dari localStorage dan dekripsi
|
// Ambil token warna dari theme Ant Design
|
||||||
|
const { token } = theme.useToken() || {};
|
||||||
|
const colorBgContainer = token?.colorBgContainer || '#fff';
|
||||||
|
const colorBorder = token?.colorBorder || '#d9d9d9';
|
||||||
|
const colorText = token?.colorText || '#1BAA56';
|
||||||
|
|
||||||
|
// Ambil data user dari localStorage
|
||||||
|
let userData = null;
|
||||||
|
|
||||||
const sessionData = localStorage.getItem('session');
|
const sessionData = localStorage.getItem('session');
|
||||||
const userData = sessionData ? decryptData(sessionData) : null;
|
if (sessionData) {
|
||||||
// console.log(userData);
|
userData = decryptData(sessionData);
|
||||||
|
} else {
|
||||||
|
const userRaw = localStorage.getItem('user');
|
||||||
|
if (userRaw) {
|
||||||
|
try {
|
||||||
|
// bungkus biar konsisten { user: {...} }
|
||||||
|
userData = { user: JSON.parse(userRaw) };
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Gagal parse user dari localStorage:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const roleName = userData?.user?.approval || userData?.user?.partner_name || 'Guest';
|
// console.log('User data di header:', userData?.user);
|
||||||
|
|
||||||
const userName = userData?.user?.name || userData?.user?.username || 'User';
|
// Role handling
|
||||||
|
let roleName = userData?.user?.role_name || 'Guest';
|
||||||
|
const userName = userData?.user?.name || userData?.user?.username || userData?.user?.user_name || 'User';
|
||||||
|
|
||||||
|
// Override jika Super Admin
|
||||||
|
if (
|
||||||
|
userData?.user?.is_sa === true ||
|
||||||
|
userData?.user?.is_sa === 'true' ||
|
||||||
|
userData?.user?.is_sa === 1
|
||||||
|
) {
|
||||||
|
roleName = 'Super Admin';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -35,12 +63,13 @@ const LayoutHeader = () => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
rowGap: 10,
|
rowGap: 10,
|
||||||
paddingTop:15,
|
paddingTop: 15,
|
||||||
paddingBottom: 20,
|
paddingBottom: 20,
|
||||||
paddingLeft: 24,
|
paddingLeft: 24,
|
||||||
paddingRight: 24,
|
paddingRight: 24,
|
||||||
minHeight: 100,
|
// minHeight: 100,
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
|
boxShadow: '5px 0 10px rgba(0, 0, 0, 0.4)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -53,16 +82,39 @@ const LayoutHeader = () => {
|
|||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: colorText,
|
color: '#1BAA56',
|
||||||
fontSize: 16,
|
fontSize: 26,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Login AS {roleName}
|
{/* Login AS {roleName} */}
|
||||||
|
CALL OF DUTY
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <Text
|
||||||
|
style={{
|
||||||
|
color: '#000000',
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
> */}
|
||||||
|
{/* Login AS {roleName} */}
|
||||||
|
{/* Kamis, 04 November 2025 16:35:00 */}
|
||||||
|
{/* </Text> */}
|
||||||
|
<DateRealTime/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -83,13 +135,12 @@ const LayoutHeader = () => {
|
|||||||
>
|
>
|
||||||
<UserOutlined style={{ fontSize: 16, color: colorText }} />
|
<UserOutlined style={{ fontSize: 16, color: colorText }} />
|
||||||
<Text style={{ marginLeft: 8, color: colorText }} strong>
|
<Text style={{ marginLeft: 8, color: colorText }} strong>
|
||||||
{userName}
|
{userName} @ {roleName}
|
||||||
</Text>
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Link
|
<Link
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleLogOut();
|
handleLogOut(navigate);
|
||||||
navigate('/signin');
|
|
||||||
}}
|
}}
|
||||||
aria-label="Log out from the application"
|
aria-label="Log out from the application"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ const LayoutLogo = () => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: '#001529',
|
|
||||||
padding: '1rem',
|
padding: '1rem',
|
||||||
borderRadius: '1rem',
|
borderRadius: '1rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: 'radial-gradient(circle at center, rgba(255,255,255,0.95), rgba(220,220,220,0.5))',
|
background:
|
||||||
|
'radial-gradient(circle at center, rgba(255,255,255,0.95), rgba(220,220,220,0.5))',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
width: 160,
|
width: 160,
|
||||||
height: 160,
|
height: 160,
|
||||||
@@ -26,7 +26,7 @@ const LayoutLogo = () => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
boxShadow: '0 6px 20px rgba(0, 0, 0, 0.2)',
|
boxShadow: '0 6px 20px rgba(0, 0, 0, 0.2)',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
overflow: 'hidden'
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Ring sebelum logo utama */}
|
{/* Ring sebelum logo utama */}
|
||||||
@@ -37,7 +37,7 @@ const LayoutLogo = () => {
|
|||||||
width: 160,
|
width: 160,
|
||||||
height: 160,
|
height: 160,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
zIndex: 1
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -66,4 +66,4 @@ const LayoutLogo = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LayoutLogo;
|
export default LayoutLogo;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { Menu, Typography, Image } from 'antd';
|
import { Menu, Typography, Image } from 'antd';
|
||||||
import { getSessionData } from '../components/Global/Formatter';
|
import { getSessionData } from '../components/Global/Formatter';
|
||||||
import { HomeOutlined,
|
import {
|
||||||
|
HomeOutlined,
|
||||||
DatabaseOutlined,
|
DatabaseOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
@@ -12,7 +13,26 @@ import { HomeOutlined,
|
|||||||
HistoryOutlined,
|
HistoryOutlined,
|
||||||
DollarOutlined,
|
DollarOutlined,
|
||||||
RollbackOutlined,
|
RollbackOutlined,
|
||||||
ProductOutlined
|
ProductOutlined,
|
||||||
|
TagOutlined,
|
||||||
|
AppstoreOutlined,
|
||||||
|
MobileOutlined,
|
||||||
|
WarningOutlined,
|
||||||
|
LineChartOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
|
BellOutlined,
|
||||||
|
AlertOutlined,
|
||||||
|
SafetyOutlined,
|
||||||
|
TeamOutlined,
|
||||||
|
ClockCircleOutlined,
|
||||||
|
CalendarOutlined,
|
||||||
|
DesktopOutlined,
|
||||||
|
NodeExpandOutlined,
|
||||||
|
GroupOutlined,
|
||||||
|
SlidersOutlined,
|
||||||
|
SnippetsOutlined,
|
||||||
|
ContactsOutlined,
|
||||||
|
ToolOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@@ -20,30 +40,316 @@ const { Text } = Typography;
|
|||||||
const allItems = [
|
const allItems = [
|
||||||
{
|
{
|
||||||
key: 'home',
|
key: 'home',
|
||||||
icon: <HomeOutlined style={{fontSize:'19px'}} />,
|
icon: <HomeOutlined style={{ fontSize: '19px' }} />,
|
||||||
label: <Link to="/dashboard/home" className='fontMenus'>Home</Link>,
|
label: (
|
||||||
|
<Link to="/dashboard/home" className="fontMenus">
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'master',
|
key: 'dashboard-svg',
|
||||||
icon: <DatabaseOutlined style={{fontSize:'19px'}} />,
|
icon: <GroupOutlined style={{ fontSize: '19px' }} />,
|
||||||
label: 'Master',
|
label: 'Dashboard',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: 'master-device',
|
key: 'dashboard-svg-compressor',
|
||||||
icon: <DatabaseOutlined style={{fontSize:'19px'}} />,
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
label: <Link to="/master/device">Device</Link>,
|
label: 'Compressor',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-compressor-overview',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/dashboard-svg/overview-compressor">Overview</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-compressor-compressor-a',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/dashboard-svg/compressor-a">Compressor A</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-compressor-compressor-b',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/dashboard-svg/compressor-b">Compressor B</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-compressor-compressor-c',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/dashboard-svg/compressor-c">Compressor C</Link>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-airdryer',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: 'Air Dryer',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-airdryer-overview',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/dashboard-svg/overview-airdryer">Overview</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-airdryer-airdryer-a',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/dashboard-svg/airdryer-a">Air Dryer A</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-airdryer-airdryer-b',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/dashboard-svg/airdryer-b">Air Dryer B</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dashboard-svg-airdryer-airdryer-c',
|
||||||
|
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/dashboard-svg/airdryer-c">Air Dryer C</Link>,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'master',
|
||||||
|
icon: <DatabaseOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: 'Master',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'master-plant-sub-section',
|
||||||
|
icon: <ProductOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/master/plant-sub-section">Plant Sub Section</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'master-brand-device',
|
||||||
|
icon: <AntDesignOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/master/brand-device">Brand Device</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'master-device',
|
||||||
|
icon: <MobileOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/master/device">Device</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'master-unit',
|
||||||
|
icon: <AppstoreOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/master/unit">Unit</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'master-tag',
|
||||||
|
icon: <TagOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/master/tag">Tag</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'master-status',
|
||||||
|
icon: <SafetyOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/master/status">Status</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'master-sparepart',
|
||||||
|
icon: <ToolOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/master/sparepart">Sparepart</Link>,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// key: 'master-shift',
|
||||||
|
// icon: <ClockCircleOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
// label: <Link to="/master/shift">Shift</Link>,
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'report',
|
||||||
|
icon: <SnippetsOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: 'Report',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'report-trending',
|
||||||
|
icon: <LineChartOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/report/trending">Trending</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'report-report',
|
||||||
|
icon: <FileTextOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/report/report">Report</Link>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
icon: <HistoryOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: 'History',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'history-alarm',
|
||||||
|
icon: <AlertOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/history/alarm">Alarm</Link>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'history-event',
|
||||||
|
icon: <SlidersOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: <Link to="/history/event">Event</Link>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'contact',
|
||||||
|
icon: <ContactsOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: (
|
||||||
|
<Link to="/contact" className="fontMenus">
|
||||||
|
Contact
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'notification',
|
||||||
|
icon: <BellOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: (
|
||||||
|
<Link to="/notification" className="fontMenus">
|
||||||
|
Notification
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'role',
|
||||||
|
icon: <SafetyOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: (
|
||||||
|
<Link to="/role" className="fontMenus">
|
||||||
|
Role
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'user',
|
||||||
|
icon: <UserOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
label: (
|
||||||
|
<Link to="/user" className="fontMenus">
|
||||||
|
User
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// key: 'jadwal-shift',
|
||||||
|
// icon: <CalendarOutlined style={{ fontSize: '19px' }} />,
|
||||||
|
// label: (
|
||||||
|
// <Link to="/jadwal-shift" className="fontMenus">
|
||||||
|
// Jadwal Shift
|
||||||
|
// </Link>
|
||||||
|
// ),
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
const LayoutMenu = () => {
|
const LayoutMenu = () => {
|
||||||
|
const location = useLocation();
|
||||||
const [stateOpenKeys, setStateOpenKeys] = useState(['home']);
|
const [stateOpenKeys, setStateOpenKeys] = useState(['home']);
|
||||||
|
const [selectedKeys, setSelectedKeys] = useState(['home']);
|
||||||
|
|
||||||
const getLevelKeys = items1 => {
|
// Function to get menu key from current path
|
||||||
|
const getMenuKeyFromPath = (pathname) => {
|
||||||
|
// Remove leading slash and split path
|
||||||
|
const pathParts = pathname.replace(/^\//, '').split('/');
|
||||||
|
|
||||||
|
// Handle different route patterns
|
||||||
|
if (pathname === '/dashboard/home') return 'home';
|
||||||
|
if (pathname === '/user') return 'user';
|
||||||
|
if (pathname === '/role') return 'role';
|
||||||
|
if (pathname === '/notification') return 'notification';
|
||||||
|
if (pathname === '/jadwal-shift') return 'jadwal-shift';
|
||||||
|
if (pathname === '/contact') return 'contact';
|
||||||
|
|
||||||
|
// Handle master routes
|
||||||
|
if (pathname.startsWith('/master/')) {
|
||||||
|
const subPath = pathParts[1];
|
||||||
|
// Convert kebab-case to the actual menu keys
|
||||||
|
const masterKeyMap = {
|
||||||
|
'plant-sub-section': 'master-plant-sub-section',
|
||||||
|
'brand-device': 'master-brand-device',
|
||||||
|
device: 'master-device',
|
||||||
|
unit: 'master-unit',
|
||||||
|
tag: 'master-tag',
|
||||||
|
status: 'master-status',
|
||||||
|
sparepart: 'master-sparepart',
|
||||||
|
shift: 'master-shift',
|
||||||
|
};
|
||||||
|
return masterKeyMap[subPath] || `master-${subPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle dashboard svg routes
|
||||||
|
if (pathname.startsWith('/dashboard-svg/')) {
|
||||||
|
const subPath = pathParts[1];
|
||||||
|
// Map specific routes to their menu keys
|
||||||
|
if (subPath === 'overview-compressor') return 'dashboard-svg-compressor-overview';
|
||||||
|
if (subPath === 'compressor-a') return 'dashboard-svg-compressor-compressor-a';
|
||||||
|
if (subPath === 'compressor-b') return 'dashboard-svg-compressor-compressor-b';
|
||||||
|
if (subPath === 'compressor-c') return 'dashboard-svg-compressor-compressor-c';
|
||||||
|
if (subPath === 'overview-airdryer') return 'dashboard-svg-airdryer-overview';
|
||||||
|
if (subPath === 'airdryer-a') return 'dashboard-svg-airdryer-airdryer-a';
|
||||||
|
if (subPath === 'airdryer-b') return 'dashboard-svg-airdryer-airdryer-b';
|
||||||
|
if (subPath === 'airdryer-c') return 'dashboard-svg-airdryer-airdryer-c';
|
||||||
|
|
||||||
|
return `dashboard-svg-${subPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle report routes
|
||||||
|
if (pathname.startsWith('/report/')) {
|
||||||
|
const subPath = pathParts[1];
|
||||||
|
const reportKeyMap = {
|
||||||
|
trending: 'report-trending',
|
||||||
|
report: 'report-report',
|
||||||
|
};
|
||||||
|
return reportKeyMap[subPath] || `report-${subPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle history routes
|
||||||
|
if (pathname.startsWith('/history/')) {
|
||||||
|
const subPath = pathParts[1];
|
||||||
|
const historyKeyMap = {
|
||||||
|
alarm: 'history-alarm',
|
||||||
|
event: 'history-event',
|
||||||
|
};
|
||||||
|
return historyKeyMap[subPath] || `history-${subPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'home'; // default
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to get parent keys from menu key
|
||||||
|
const getParentKeys = (key) => {
|
||||||
|
const parentKeys = [];
|
||||||
|
|
||||||
|
if (key.startsWith('dashboard-svg-compressor-')) {
|
||||||
|
parentKeys.push('dashboard-svg', 'dashboard-svg-compressor');
|
||||||
|
} else if (key.startsWith('dashboard-svg-airdryer-')) {
|
||||||
|
parentKeys.push('dashboard-svg', 'dashboard-svg-airdryer');
|
||||||
|
} else if (key.startsWith('dashboard-svg-')) {
|
||||||
|
parentKeys.push('dashboard-svg');
|
||||||
|
} else if (key.startsWith('master-')) {
|
||||||
|
parentKeys.push('master');
|
||||||
|
} else if (key.startsWith('report-')) {
|
||||||
|
parentKeys.push('report');
|
||||||
|
} else if (key.startsWith('history-')) {
|
||||||
|
parentKeys.push('history');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update selected and open keys when route changes
|
||||||
|
useEffect(() => {
|
||||||
|
const currentKey = getMenuKeyFromPath(location.pathname);
|
||||||
|
setSelectedKeys([currentKey]);
|
||||||
|
|
||||||
|
const parentKeys = getParentKeys(currentKey);
|
||||||
|
|
||||||
|
// Always keep the parent menus open when a child is selected
|
||||||
|
if (parentKeys.length > 0) {
|
||||||
|
setStateOpenKeys(parentKeys);
|
||||||
|
} else {
|
||||||
|
setStateOpenKeys([]);
|
||||||
|
}
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
const getLevelKeys = (items1) => {
|
||||||
const key = {};
|
const key = {};
|
||||||
const func = (items2, level = 1) => {
|
const func = (items2, level = 1) => {
|
||||||
items2.forEach(item => {
|
items2.forEach((item) => {
|
||||||
if (item.key) {
|
if (item.key) {
|
||||||
key[item.key] = level;
|
key[item.key] = level;
|
||||||
}
|
}
|
||||||
@@ -58,53 +364,66 @@ const LayoutMenu = () => {
|
|||||||
|
|
||||||
const levelKeys = getLevelKeys(allItems);
|
const levelKeys = getLevelKeys(allItems);
|
||||||
|
|
||||||
const onOpenChange = openKeys => {
|
const onOpenChange = (openKeys) => {
|
||||||
const currentOpenKey = openKeys.find(key => stateOpenKeys.indexOf(key) === -1);
|
const currentOpenKey = openKeys.find((key) => stateOpenKeys.indexOf(key) === -1);
|
||||||
|
|
||||||
|
// If clicking on a menu that was previously closed
|
||||||
if (currentOpenKey !== undefined) {
|
if (currentOpenKey !== undefined) {
|
||||||
const repeatIndex = openKeys.filter(key => key !== currentOpenKey).findIndex(key => levelKeys[key] === levelKeys[currentOpenKey]);
|
const repeatIndex = openKeys
|
||||||
|
.filter((key) => key !== currentOpenKey)
|
||||||
|
.findIndex((key) => levelKeys[key] === levelKeys[currentOpenKey]);
|
||||||
|
|
||||||
setStateOpenKeys(
|
setStateOpenKeys(
|
||||||
openKeys.filter((_, index) => index !== repeatIndex).filter(key => levelKeys[key] <= levelKeys[currentOpenKey]),
|
openKeys
|
||||||
|
.filter((_, index) => index !== repeatIndex)
|
||||||
|
.filter((key) => levelKeys[key] <= levelKeys[currentOpenKey])
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setStateOpenKeys(openKeys);
|
// If clicking on a menu that was previously open, close only that menu
|
||||||
|
// but keep other parent menus open if they have active children
|
||||||
|
const currentKey = getMenuKeyFromPath(location.pathname);
|
||||||
|
const necessaryParentKeys = getParentKeys(currentKey);
|
||||||
|
|
||||||
|
// Filter out only the menus that are necessary to keep open
|
||||||
|
const filteredOpenKeys = openKeys.filter((key) => necessaryParentKeys.includes(key));
|
||||||
|
|
||||||
|
setStateOpenKeys(filteredOpenKeys);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const session = getSessionData();
|
const session = getSessionData();
|
||||||
const isAdmin = session?.user?.user_id;
|
const isAdmin = session?.user?.user_id;
|
||||||
|
|
||||||
const karyawan = ()=>{
|
const karyawan = () => {
|
||||||
return allItems.filter(
|
return allItems
|
||||||
item => item.key !== 'setting'
|
.filter((item) => item.key !== 'setting')
|
||||||
// tambahkan menu jika terdapat menu yang di sembunyikan dari user karyawan
|
.map((item) => {
|
||||||
// && item.key !== 'master'
|
if (item.key === 'master') {
|
||||||
// && item.key !== 'master'
|
return {
|
||||||
).map(item=>{
|
...item,
|
||||||
if(item.key === 'master'){
|
};
|
||||||
return{
|
|
||||||
...item,
|
|
||||||
// buka command dibawah jika terdapat sub menu yang di sembunyikan
|
|
||||||
// children: item.children.filter(
|
|
||||||
// child => child.key !== 'master-product'
|
|
||||||
// tambahkan menu jika terdapat menu yang di sembunyikan dari user karyawan
|
|
||||||
// && child.key !== 'master-service'
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
}
|
return item;
|
||||||
return item;
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const items = isAdmin === 1 ? allItems : karyawan();
|
const items = isAdmin === 1 ? allItems : karyawan();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
theme="dark"
|
|
||||||
mode="inline"
|
mode="inline"
|
||||||
items={items}
|
items={items}
|
||||||
defaultSelectedKeys={['home']}
|
selectedKeys={selectedKeys}
|
||||||
openKeys={stateOpenKeys}
|
openKeys={stateOpenKeys}
|
||||||
onOpenChange={onOpenChange}
|
onOpenChange={onOpenChange}
|
||||||
|
style={{
|
||||||
|
background: 'transparent',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
theme="dark"
|
||||||
|
className="custom-green-menu"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default LayoutMenu;
|
export default LayoutMenu;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import LayoutLogo from './LayoutLogo';
|
import LayoutLogo from './LayoutLogo';
|
||||||
import LayoutMenu from './LayoutMenu';
|
import LayoutMenu from './LayoutMenu';
|
||||||
@@ -6,7 +6,8 @@ import LayoutMenu from './LayoutMenu';
|
|||||||
const { Sider } = Layout;
|
const { Sider } = Layout;
|
||||||
const LayoutSidebar = () => {
|
const LayoutSidebar = () => {
|
||||||
return (
|
return (
|
||||||
<Sider width={300}
|
<Sider
|
||||||
|
width={255}
|
||||||
breakpoint="lg"
|
breakpoint="lg"
|
||||||
collapsedWidth="0"
|
collapsedWidth="0"
|
||||||
onBreakpoint={(broken) => {
|
onBreakpoint={(broken) => {
|
||||||
@@ -15,11 +16,40 @@ const LayoutSidebar = () => {
|
|||||||
onCollapse={(collapsed, type) => {
|
onCollapse={(collapsed, type) => {
|
||||||
// console.log(collapsed, type);
|
// console.log(collapsed, type);
|
||||||
}}
|
}}
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(180deg, #1BAA56 0%,rgb(5, 75, 34) 100%)',
|
||||||
|
// overflow: 'auto',
|
||||||
|
height: '100vh',
|
||||||
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
borderTopRightRadius: '30px',
|
||||||
|
borderBottomRightRadius: '30px',
|
||||||
|
boxShadow: '5px 0 10px rgba(0, 0, 0, 0.4)',
|
||||||
|
zIndex: 9999
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<LayoutLogo />
|
<div style={{
|
||||||
<LayoutMenu />
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100vh',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
{/* Logo section - fixed height */}
|
||||||
|
<div style={{flexShrink: 0,minHeight: '64px'}}>
|
||||||
|
<LayoutLogo />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Menu section - scrollable */}
|
||||||
|
<div style={{flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column'}}>
|
||||||
|
<div className="custom-menu-scrollbar" style={{flex: 1, overflowY: 'auto', overflowX: 'hidden', backgroundColor: 'transparent'}}>
|
||||||
|
<LayoutMenu />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Sider>
|
</Sider>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default LayoutSidebar
|
export default LayoutSidebar;
|
||||||
|
|||||||
@@ -1,33 +1,38 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Layout, theme } from 'antd';
|
import { Layout, theme, Grid } from 'antd';
|
||||||
import LayoutFooter from './LayoutFooter';
|
import LayoutFooter from './LayoutFooter';
|
||||||
import LayoutHeader from './LayoutHeader';
|
import LayoutHeader from './LayoutHeader';
|
||||||
import LayoutSidebar from './LayoutSidebar';
|
import LayoutSidebar from './LayoutSidebar';
|
||||||
|
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
const MainLayout = ({ children }) => {
|
const MainLayout = ({ children }) => {
|
||||||
const {
|
const {
|
||||||
token: { colorBgContainer, borderRadiusLG },
|
token: { colorBgContainer, borderRadiusLG },
|
||||||
} = theme.useToken();
|
} = theme.useToken();
|
||||||
|
|
||||||
return (
|
const screens = useBreakpoint();
|
||||||
<Layout style={{ height: '100vh' }}>
|
const isDesktop = screens.lg;
|
||||||
<LayoutSidebar />
|
|
||||||
<Layout
|
|
||||||
style={{
|
|
||||||
overflow: 'auto',
|
|
||||||
|
|
||||||
}}>
|
return (
|
||||||
<LayoutHeader />
|
<Layout style={{ height: '100vh' }}>
|
||||||
<Content
|
<LayoutSidebar />
|
||||||
style={{
|
<Layout
|
||||||
margin: '24px 16px 0',
|
style={{
|
||||||
flex: '1 0 auto',
|
marginLeft: isDesktop ? '250px' : '0',
|
||||||
}}
|
overflow: 'auto',
|
||||||
>
|
}}
|
||||||
{/* <div
|
>
|
||||||
|
<LayoutHeader />
|
||||||
|
<Content
|
||||||
|
style={{
|
||||||
|
margin: '0 10px',
|
||||||
|
flex: '1 0 auto',
|
||||||
|
padding: '8px 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <div
|
||||||
style={{
|
style={{
|
||||||
padding: 24,
|
padding: 24,
|
||||||
minHeight: '100%',
|
minHeight: '100%',
|
||||||
@@ -35,12 +40,12 @@ const MainLayout = ({ children }) => {
|
|||||||
borderRadius: borderRadiusLG,
|
borderRadius: borderRadiusLG,
|
||||||
}}
|
}}
|
||||||
> */}
|
> */}
|
||||||
{children}
|
{children}
|
||||||
{/* </div> */}
|
{/* </div> */}
|
||||||
</Content>
|
</Content>
|
||||||
{/* <LayoutFooter /> */}
|
{/* <LayoutFooter /> */}
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default MainLayout;
|
export default MainLayout;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react'
|
|||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
import './App.css'
|
||||||
import { BreadcrumbProvider } from './layout/LayoutBreadcrumb.jsx';
|
import { BreadcrumbProvider } from './layout/LayoutBreadcrumb.jsx';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
|
|||||||
@@ -1,461 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import {
|
|
||||||
Flex,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
Form,
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Space,
|
|
||||||
Upload,
|
|
||||||
Divider,
|
|
||||||
Tooltip,
|
|
||||||
message,
|
|
||||||
Select,
|
|
||||||
} from 'antd';
|
|
||||||
import {
|
|
||||||
UploadOutlined,
|
|
||||||
UserOutlined,
|
|
||||||
IdcardOutlined,
|
|
||||||
PhoneOutlined,
|
|
||||||
LockOutlined,
|
|
||||||
InfoCircleOutlined,
|
|
||||||
MailOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
const { Item } = Form;
|
|
||||||
const { Option } = Select;
|
|
||||||
import sypiu_ggcp from 'assets/sypiu_ggcp.jpg';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { register, uploadFile, checkUsername } from '../../api/auth';
|
|
||||||
import { NotifAlert } from '../../components/Global/ToastNotif';
|
|
||||||
|
|
||||||
const Registration = () => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [fileListKontrak, setFileListKontrak] = useState([]);
|
|
||||||
const [fileListHsse, setFileListHsse] = useState([]);
|
|
||||||
const [fileListIcon, setFileListIcon] = useState([]);
|
|
||||||
|
|
||||||
// Daftar jenis vendor
|
|
||||||
const vendorTypes = [
|
|
||||||
{ vendor_type: 1, vendor_type_name: 'One-Time' },
|
|
||||||
{ vendor_type: 2, vendor_type_name: 'Rutin' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const onFinish = async (values) => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
if (!fileListKontrak.length || !fileListHsse.length) {
|
|
||||||
message.error('Harap unggah Lampiran Kontrak Kerja dan HSSE Plan!');
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('path_kontrak', fileListKontrak[0].originFileObj);
|
|
||||||
formData.append('path_hse_plant', fileListHsse[0].originFileObj);
|
|
||||||
if (fileListIcon.length) {
|
|
||||||
formData.append('path_icon', fileListIcon[0].originFileObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadResponse = await uploadFile(formData);
|
|
||||||
|
|
||||||
if (!uploadResponse.data?.pathKontrak && !uploadResponse.data?.pathHsePlant) {
|
|
||||||
message.error(uploadResponse.message || 'Gagal mengunggah file.');
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams({ username: values.username });
|
|
||||||
const usernameCheck = await checkUsername(params);
|
|
||||||
if (usernameCheck.data.data && usernameCheck.data.data.available === false) {
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'error',
|
|
||||||
title: 'Gagal',
|
|
||||||
message: usernameCheck.data.message || 'Terjadi kesalahan, silakan coba lagi',
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerData = {
|
|
||||||
nama_perusahaan: values.namaPerusahaan,
|
|
||||||
no_kontak_wo: values.noKontakWo,
|
|
||||||
path_kontrak: uploadResponse.data.pathKontrak || '',
|
|
||||||
durasi: values.durasiPekerjaan,
|
|
||||||
nilai_csms: values.nilaiCsms.toString(),
|
|
||||||
vendor_type: values.jenisVendor, // Tambahkan jenis vendor ke registerData
|
|
||||||
path_hse_plant: uploadResponse.data.pathHsePlant || '',
|
|
||||||
nama_leader: values.penanggungJawab,
|
|
||||||
no_identitas: values.noIdentitas,
|
|
||||||
no_hp: values.noHandphone,
|
|
||||||
email_register: values.username,
|
|
||||||
password_register: values.password,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await register(registerData);
|
|
||||||
|
|
||||||
if (response.data?.id_register) {
|
|
||||||
message.success('Data berhasil disimpan!');
|
|
||||||
|
|
||||||
try {
|
|
||||||
form.resetFields();
|
|
||||||
setFileListKontrak([]);
|
|
||||||
setFileListHsse([]);
|
|
||||||
setFileListIcon([]);
|
|
||||||
|
|
||||||
navigate('/registration-submitted');
|
|
||||||
} catch (postSuccessError) {
|
|
||||||
message.warning(
|
|
||||||
'Registrasi berhasil, tetapi ada masalah setelahnya. Silakan ke halaman login secara manual.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message.error(response.message || 'Pendaftaran gagal, silakan coba lagi.');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saat registrasi:', error);
|
|
||||||
NotifAlert({
|
|
||||||
icon: 'error',
|
|
||||||
title: 'Gagal',
|
|
||||||
message: error.message || 'Terjadi kesalahan, silakan coba lagi',
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCancel = () => {
|
|
||||||
form.resetFields();
|
|
||||||
setFileListKontrak([]);
|
|
||||||
setFileListHsse([]);
|
|
||||||
setFileListIcon([]);
|
|
||||||
navigate('/signin');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeKontrak = ({ fileList }) => {
|
|
||||||
setFileListKontrak(fileList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeHsse = ({ fileList }) => {
|
|
||||||
setFileListHsse(fileList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeIcon = ({ fileList }) => {
|
|
||||||
setFileListIcon(fileList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const beforeUpload = (file, fieldname) => {
|
|
||||||
const isValidType = [
|
|
||||||
'image/jpeg',
|
|
||||||
'image/jpg',
|
|
||||||
'image/png',
|
|
||||||
fieldname !== 'path_icon' ? 'application/pdf' : null,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.includes(file.type);
|
|
||||||
const isNotEmpty = file.size > 0;
|
|
||||||
const isSizeValid = file.size / 1024 / 1024 < 10;
|
|
||||||
|
|
||||||
if (!isValidType) {
|
|
||||||
message.error(
|
|
||||||
`Hanya file ${
|
|
||||||
fieldname === 'path_icon' ? 'JPG/PNG' : 'PDF/JPG/PNG'
|
|
||||||
} yang diperbolehkan!`
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!isNotEmpty) {
|
|
||||||
message.error('File tidak boleh kosong!');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!isSizeValid) {
|
|
||||||
message.error('Ukuran file maksimal 10MB!');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
align="center"
|
|
||||||
justify="center"
|
|
||||||
style={{
|
|
||||||
minHeight: '100vh',
|
|
||||||
backgroundImage: `url(${sypiu_ggcp})`,
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
padding: '20px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Card
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: 800,
|
|
||||||
background: 'rgba(255, 255, 255, 0.9)',
|
|
||||||
backdropFilter: 'blur(10px)',
|
|
||||||
borderRadius: '12px',
|
|
||||||
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)',
|
|
||||||
padding: '24px',
|
|
||||||
}}
|
|
||||||
title={
|
|
||||||
<Flex align="center" justify="space-between">
|
|
||||||
<h2 style={{ margin: 0, color: '#1a3c34' }}>Formulir Pendaftaran</h2>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
icon={<InfoCircleOutlined />}
|
|
||||||
onClick={() => navigate('/signin')}
|
|
||||||
>
|
|
||||||
Kembali
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
onFinish={onFinish}
|
|
||||||
layout="horizontal"
|
|
||||||
labelCol={{ span: 8 }}
|
|
||||||
wrapperCol={{ span: 16 }}
|
|
||||||
labelAlign="left"
|
|
||||||
style={{ maxWidth: 800 }}
|
|
||||||
>
|
|
||||||
{/* Informasi Perusahaan */}
|
|
||||||
<Divider
|
|
||||||
orientation="left"
|
|
||||||
orientationMargin={0}
|
|
||||||
style={{
|
|
||||||
color: '#23A55A',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginLeft: 0,
|
|
||||||
paddingLeft: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Informasi Perusahaan
|
|
||||||
</Divider>
|
|
||||||
<Item
|
|
||||||
label="Nama Perusahaan"
|
|
||||||
name="namaPerusahaan"
|
|
||||||
rules={[{ required: true, message: 'Masukkan Nama Perusahaan!' }]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
prefix={<UserOutlined />}
|
|
||||||
placeholder="Masukkan Nama Perusahaan"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="Durasi Pekerjaan (Hari)"
|
|
||||||
name="durasiPekerjaan"
|
|
||||||
rules={[{ required: true, message: 'Masukkan Durasi Pekerjaan!' }]}
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
min={1}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder="Masukkan Durasi Pekerjaan"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="No Kontrak Kerja / Agreement"
|
|
||||||
name="noKontakWo"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: 'Masukkan No Kontrak Kerja / Agreement!' },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
}}
|
|
||||||
placeholder="Masukkan No Kontrak Kerja / Agreement"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="Lampiran Kontrak Kerja"
|
|
||||||
name="lampiranKontrak"
|
|
||||||
rules={[{ required: true, message: 'Unggah Lampiran Kontrak Kerja!' }]}
|
|
||||||
>
|
|
||||||
<Upload
|
|
||||||
beforeUpload={(file) => beforeUpload(file, 'path_kontrak')}
|
|
||||||
fileList={fileListKontrak}
|
|
||||||
onChange={handleChangeKontrak}
|
|
||||||
maxCount={1}
|
|
||||||
>
|
|
||||||
<Button icon={<UploadOutlined />} size="large">
|
|
||||||
Unggah PDF/JPG
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="HSSE Plan"
|
|
||||||
name="hssePlan"
|
|
||||||
rules={[{ required: true, message: 'Unggah HSSE Plan!' }]}
|
|
||||||
>
|
|
||||||
<Upload
|
|
||||||
beforeUpload={(file) => beforeUpload(file, 'path_hse_plant')}
|
|
||||||
fileList={fileListHsse}
|
|
||||||
onChange={handleChangeHsse}
|
|
||||||
maxCount={1}
|
|
||||||
>
|
|
||||||
<Button icon={<UploadOutlined />} size="large">
|
|
||||||
Unggah PDF/JPG
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="Nilai CSMS"
|
|
||||||
name="nilaiCsms"
|
|
||||||
rules={[{ required: true, message: 'Masukkan Nilai CSMS!' }]}
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder="Masukkan Nilai CSMS"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="Jenis Vendor"
|
|
||||||
name="jenisVendor"
|
|
||||||
rules={[{ required: true, message: 'Pilih Jenis Vendor!' }]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder="Pilih Jenis Vendor"
|
|
||||||
size="large"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
{vendorTypes.map((vendor) => (
|
|
||||||
<Option key={vendor.vendor_type} value={vendor.vendor_type}>
|
|
||||||
{vendor.vendor_type_name}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Item>
|
|
||||||
|
|
||||||
{/* Informasi Penanggung Jawab */}
|
|
||||||
<Divider
|
|
||||||
orientation="left"
|
|
||||||
orientationMargin={0}
|
|
||||||
style={{
|
|
||||||
color: '#23A55A',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginLeft: 0,
|
|
||||||
paddingLeft: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Informasi Penanggung Jawab
|
|
||||||
</Divider>
|
|
||||||
<Item
|
|
||||||
label="Nama Penanggung Jawab"
|
|
||||||
name="penanggungJawab"
|
|
||||||
rules={[{ required: true, message: 'Masukkan Nama Penanggung Jawab!' }]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
prefix={<UserOutlined />}
|
|
||||||
placeholder="Masukkan Nama Penanggung Jawab"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="No Handphone"
|
|
||||||
name="noHandphone"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: 'Masukkan No Handphone!' },
|
|
||||||
{
|
|
||||||
pattern: /^(\+62|0)[0-9]{9,12}$/,
|
|
||||||
message:
|
|
||||||
'Format nomor telepon tidak valid! (Contoh: +62.... atau 0....)',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
prefix={<PhoneOutlined />}
|
|
||||||
placeholder="Masukkan No Handphone (+62)"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="No Identitas"
|
|
||||||
name="noIdentitas"
|
|
||||||
rules={[{ required: true, message: 'Masukkan No Identitas!' }]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
prefix={<IdcardOutlined />}
|
|
||||||
placeholder="Masukkan No Identitas"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
|
|
||||||
{/* Akun Pengguna */}
|
|
||||||
<Divider
|
|
||||||
orientation="left"
|
|
||||||
orientationMargin={0}
|
|
||||||
style={{
|
|
||||||
color: '#23A55A',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginLeft: 0,
|
|
||||||
paddingLeft: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Akun Pengguna (digunakan sebagai user login SYPIU)
|
|
||||||
</Divider>
|
|
||||||
<Item
|
|
||||||
label="Email"
|
|
||||||
name="username"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: 'Masukkan Email!' },
|
|
||||||
{ type: 'email', message: 'Format email tidak valid!' },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
prefix={<MailOutlined />}
|
|
||||||
placeholder="Masukkan Email"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
<Item
|
|
||||||
label="Password"
|
|
||||||
name="password"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: 'Masukkan Password!' },
|
|
||||||
{ min: 6, message: 'Password minimal 6 karakter!' },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input.Password
|
|
||||||
prefix={<LockOutlined />}
|
|
||||||
placeholder="Masukkan Password"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</Item>
|
|
||||||
|
|
||||||
{/* Tombol */}
|
|
||||||
<Item wrapperCol={{ offset: 8, span: 16 }}>
|
|
||||||
<Space style={{ marginTop: '24px', width: '100%' }}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
size="large"
|
|
||||||
loading={loading}
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#23A55A',
|
|
||||||
borderColor: '#23A55A',
|
|
||||||
width: 120,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onCancel} size="large" style={{ width: 120 }}>
|
|
||||||
Batal
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Item>
|
|
||||||
</Form>
|
|
||||||
</Card>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Registration;
|
|
||||||
@@ -1,186 +1,180 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Flex, Input, Form, Button, Card, Space, Image } from 'antd';
|
import { Flex, Input, Form, Button, Card, Space, Image } from 'antd';
|
||||||
import React from 'react';
|
|
||||||
import handleSignIn from '../../Utils/Auth/SignIn';
|
|
||||||
import sypiu_ggcp from 'assets/sypiu_ggcp.jpg';
|
|
||||||
import logo from 'assets/freepik/LOGOPIU.png';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { NotifAlert } from '../../components/Global/ToastNotif';
|
import { NotifAlert } from '../../components/Global/ToastNotif';
|
||||||
import { decryptData } from '../../components/Global/Formatter';
|
import { SendRequest } from '../../components/Global/ApiRequest';
|
||||||
|
import bg_cod from 'assets/bg-cod-1.jpg';
|
||||||
|
import logo from 'assets/freepik/LOGOPIU.png';
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
const [captchaSvg, setCaptchaSvg] = React.useState('');
|
const [form] = Form.useForm();
|
||||||
const [userInput, setUserInput] = React.useState('');
|
const [captchaSvg, setCaptchaSvg] = useState('');
|
||||||
const [message, setMessage] = React.useState('');
|
const [captchaText, setCaptchaText] = useState('');
|
||||||
const [captchaText, setcaptchaText] = React.useState('');
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// let url = `${import.meta.env.VITE_API_SERVER}/users`;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
const defaultSignIn = {
|
||||||
|
identifier: 'superadmin@cod.com',
|
||||||
|
password: '@Superadmin123',
|
||||||
|
captcha: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveToSignUp = () => {
|
||||||
|
navigate('/signup');
|
||||||
|
};
|
||||||
|
|
||||||
|
// ambil captcha
|
||||||
|
const fetchCaptcha = async () => {
|
||||||
|
try {
|
||||||
|
const res = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: 'auth/generate-captcha',
|
||||||
|
token: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
setCaptchaSvg(res.data?.data?.svg || '');
|
||||||
|
setCaptchaText(res.data?.data?.text || '');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching captcha:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
fetchCaptcha();
|
fetchCaptcha();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Fetch the CAPTCHA SVG from the backend
|
const handleOnSubmit = async (values) => {
|
||||||
const fetchCaptcha = async () => {
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// let url = `${import.meta.env.VITE_API_SERVER}/operation`
|
const res = await SendRequest({
|
||||||
// const response = await fetch('http://localhost:9528/generate-captcha');
|
method: 'post',
|
||||||
const response = await fetch(
|
prefix: 'auth/login',
|
||||||
`${import.meta.env.VITE_API_SERVER}/auth/generate-captcha`,
|
params: {
|
||||||
{
|
identifier: values.identifier,
|
||||||
credentials: 'include', // Wajib untuk mengirim cookie
|
password: values.password,
|
||||||
}
|
captcha: values.captcha,
|
||||||
);
|
captchaText: captchaText,
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
// Ambil header
|
const user = res?.data?.data?.user || res?.user;
|
||||||
const captchaToken = response.headers.get('X-Captcha-Token');
|
const accessToken = res?.data?.data?.accessToken || res?.tokens?.accessToken;
|
||||||
|
|
||||||
// console.log('Captcha Token:', decryptData(captchaToken));
|
if (user && accessToken) {
|
||||||
|
localStorage.setItem('token', accessToken);
|
||||||
|
localStorage.setItem('user', JSON.stringify(user));
|
||||||
|
|
||||||
setcaptchaText(decryptData(captchaToken));
|
NotifAlert({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Login Berhasil',
|
||||||
|
message: res?.message || 'Selamat datang!',
|
||||||
|
});
|
||||||
|
|
||||||
const data = await response.text();
|
navigate('/dashboard/home');
|
||||||
setCaptchaSvg(data);
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error('Error fetching CAPTCHA:', error);
|
// hanya handle invalid captcha disini
|
||||||
}
|
if (err?.response?.data?.message?.toLowerCase().includes('captcha')) {
|
||||||
};
|
NotifAlert({
|
||||||
|
icon: 'warning',
|
||||||
const handleOnSubmit = async (e) => {
|
title: 'Peringatan',
|
||||||
// console.log('Received values of form: ', e);
|
message: 'Invalid captcha',
|
||||||
// e.preventDefault();
|
});
|
||||||
|
fetchCaptcha();
|
||||||
try {
|
|
||||||
// const response = await fetch(`${import.meta.env.VITE_API_SERVER}/auth/verify-captcha`, {
|
|
||||||
// method: 'POST',
|
|
||||||
// headers: { 'Content-Type': 'application/json' },
|
|
||||||
// credentials: 'include', // WAJIB: Agar cookie CAPTCHA dikirim
|
|
||||||
// body: JSON.stringify({ captcha: userInput }),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const data = await response.json();
|
|
||||||
// console.log(data);
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
success: captchaText === userInput ? true : false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
setMessage('CAPTCHA verified successfully!');
|
|
||||||
await handleSignIn(e);
|
|
||||||
} else {
|
} else {
|
||||||
setMessage('CAPTCHA verification failed. Try again.');
|
|
||||||
fetchCaptcha(); // Refresh CAPTCHA on failure
|
|
||||||
NotifAlert({
|
NotifAlert({
|
||||||
icon: 'error',
|
icon: 'error',
|
||||||
title: 'Gagal',
|
title: 'Login Gagal',
|
||||||
message: data.message || 'CAPTCHA verification failed. Try again.',
|
message: err?.message || 'Terjadi kesalahan',
|
||||||
});
|
});
|
||||||
|
fetchCaptcha();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} finally {
|
||||||
console.error('Error verifying CAPTCHA:', error);
|
setLoading(false);
|
||||||
setMessage('An error occurred. Please try again.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setUserInput(''); // Clear the input field
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveToRegistration = (e) => {
|
|
||||||
// e.preventDefault();
|
|
||||||
// navigate("/signup")
|
|
||||||
navigate('/registration');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex
|
||||||
<Flex
|
align="center"
|
||||||
align="center"
|
justify="center"
|
||||||
justify="center"
|
style={{
|
||||||
// vertical
|
height: '100vh',
|
||||||
style={{
|
backgroundImage: `url(${bg_cod})`,
|
||||||
height: '100vh',
|
backgroundSize: 'cover',
|
||||||
// marginTop: '10vh',
|
backgroundPosition: 'center',
|
||||||
// backgroundImage: `url('https://via.placeholder.com/300')`,
|
}}
|
||||||
backgroundImage: `url(${sypiu_ggcp})`,
|
>
|
||||||
backgroundSize: 'cover',
|
<Card style={{ boxShadow: '0px 4px 8px rgba(0,0,0,0.1)' }}>
|
||||||
backgroundPosition: 'center',
|
<Flex align="center" justify="center">
|
||||||
}}
|
<Image src={logo} height={150} width={220} preview={false} alt="logo" />
|
||||||
>
|
</Flex>
|
||||||
<Card
|
<br />
|
||||||
style={{
|
<Form
|
||||||
boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)',
|
form={form}
|
||||||
}}
|
layout="vertical"
|
||||||
|
style={{ width: '250px' }}
|
||||||
|
onFinish={handleOnSubmit}
|
||||||
|
initialValues={defaultSignIn}
|
||||||
>
|
>
|
||||||
<Flex align="center" justify="center">
|
<Form.Item
|
||||||
<Image
|
label="Email / Username"
|
||||||
// src="/src/assets/freepik/LOGOPIU.png"
|
name="identifier"
|
||||||
src={logo}
|
rules={[
|
||||||
height={150}
|
{
|
||||||
width={220}
|
required: true,
|
||||||
preview={false}
|
message: 'Email / Username tidak boleh kosong',
|
||||||
alt="signin"
|
},
|
||||||
/>
|
]}
|
||||||
</Flex>
|
|
||||||
<br />
|
|
||||||
<Form onFinish={handleOnSubmit} layout="vertical" style={{ width: '250px' }}>
|
|
||||||
<Form.Item
|
|
||||||
label="Username"
|
|
||||||
name="username"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
// type: "email",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input placeholder="username" size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label="Password"
|
|
||||||
name="password"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: 'Please input your password!',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input.Password placeholder="password" size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
<div
|
|
||||||
style={{ marginLeft: 45 }}
|
|
||||||
dangerouslySetInnerHTML={{ __html: captchaSvg }}
|
|
||||||
/>
|
|
||||||
{/* {message} */}
|
|
||||||
<Input
|
|
||||||
style={{ marginTop: 10, marginBottom: 15 }}
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter CAPTCHA text"
|
|
||||||
size="large"
|
|
||||||
value={userInput}
|
|
||||||
onChange={(e) => setUserInput(e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Form.Item>
|
|
||||||
<Space direction="vertical" style={{ width: '100%' }}>
|
|
||||||
<Button type="primary" htmlType="submit" style={{ width: '100%' }}>
|
|
||||||
Sign In
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
onClick={() => moveToRegistration()}
|
|
||||||
>
|
>
|
||||||
Registration
|
<Input placeholder="Email / Username" size="large" />
|
||||||
</Button>
|
</Form.Item>
|
||||||
</Card>
|
|
||||||
</Flex>
|
<Form.Item
|
||||||
</>
|
label="Password"
|
||||||
|
name="password"
|
||||||
|
rules={[{ required: true, message: 'Password tidak boleh kosong' }]}
|
||||||
|
>
|
||||||
|
<Input.Password placeholder="Password" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{ textAlign: 'center' }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: captchaSvg }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="CAPTCHA"
|
||||||
|
name="captcha"
|
||||||
|
rules={[{ required: true, message: 'Silahkan masukkan CAPTCHA' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Masukkan CAPTCHA" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={loading}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
onClick={() => moveToSignUp()}
|
||||||
|
>
|
||||||
|
Registration
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,158 +1,201 @@
|
|||||||
import { Flex, Input, Form, Button, Card, Space, Image } from 'antd'
|
import React, { useState } from 'react';
|
||||||
import React from 'react'
|
import { Flex, Input, Form, Button, Card, Space, Image, Row, Col } from 'antd';
|
||||||
// import handleSignIn from '../../Utils/Auth/SignIn';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import sypiu_ggcp from 'assets/sypiu_ggcp.jpg';
|
import bg_cod from 'assets/bg_cod.jpg';
|
||||||
import {useNavigate} from "react-router-dom";
|
import logo from 'assets/freepik/LOGOPIU.png';
|
||||||
|
import { register } from '../../api/auth';
|
||||||
|
import { NotifOk, NotifAlert } from '../../components/Global/ToastNotif';
|
||||||
|
|
||||||
const SignUp = () => {
|
const SignUp = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [captchaSvg, setCaptchaSvg] = React.useState('');
|
const [isRegistered, setIsRegistered] = useState(false);
|
||||||
const [userInput, setUserInput] = React.useState('');
|
|
||||||
const [message, setMessage] = React.useState('');
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const moveToSignin = () => {
|
||||||
// let url = `${import.meta.env.VITE_API_SERVER}/users`;
|
navigate('/signin');
|
||||||
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
const handleSignUp = async (values) => {
|
||||||
fetchCaptcha();
|
const { user_fullname, user_name, user_email, user_phone, user_password, confirmPassword } =
|
||||||
}, []);
|
values;
|
||||||
|
|
||||||
// Fetch the CAPTCHA SVG from the backend
|
// Validasi confirm password
|
||||||
const fetchCaptcha = async () => {
|
if (user_password !== confirmPassword) {
|
||||||
try {
|
NotifAlert({
|
||||||
const response = await fetch('http://localhost:9528/generate-captcha');
|
icon: 'error',
|
||||||
const data = await response.text();
|
title: 'Password Tidak Sama',
|
||||||
setCaptchaSvg(data);
|
message: 'Password dan confirm password harus sama',
|
||||||
} catch (error) {
|
});
|
||||||
console.error('Error fetching CAPTCHA:', error);
|
form.resetFields(['password', 'confirmPassword']);
|
||||||
}
|
return;
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnSubmt = async (e) => {
|
|
||||||
// console.log('Received values of form: ', e);
|
|
||||||
// await handleSignIn(e);
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('http://localhost:5000/verify-captcha', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ userInput }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.success) {
|
|
||||||
setMessage('CAPTCHA verified successfully!');
|
|
||||||
} else {
|
|
||||||
setMessage('CAPTCHA verification failed. Try again.');
|
|
||||||
fetchCaptcha(); // Refresh CAPTCHA on failure
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Error verifying CAPTCHA:', error);
|
|
||||||
setMessage('An error occurred. Please try again.');
|
|
||||||
}
|
|
||||||
|
|
||||||
setUserInput(''); // Clear the input field
|
// Validasi nomor telepon Indonesia
|
||||||
}
|
const phoneRegex = /^(?:\+62|62|0)8[1-9][0-9]{6,11}$/;
|
||||||
|
if (!phoneRegex.test(user_phone)) {
|
||||||
const moveToSignin = (e) => {
|
NotifAlert({
|
||||||
// e.preventDefault();
|
icon: 'error',
|
||||||
navigate("/signin")
|
title: 'Format Telepon Salah',
|
||||||
}
|
message: 'Nomor telepon tidak valid (harus nomor Indonesia)',
|
||||||
|
});
|
||||||
|
form.resetFields(['user_phone']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
// Validasi password kompleks
|
||||||
<>
|
const passwordErrors = [];
|
||||||
|
if (user_password.length < 8) passwordErrors.push('Minimal 8 karakter');
|
||||||
|
if (!/[A-Z]/.test(user_password)) passwordErrors.push('Harus ada huruf kapital');
|
||||||
|
if (!/[0-9]/.test(user_password)) passwordErrors.push('Harus ada angka');
|
||||||
|
if (!/[!@#$%^&*(),.?":{}|<>]/.test(user_password))
|
||||||
|
passwordErrors.push('Harus ada karakter spesial');
|
||||||
|
if (passwordErrors.length) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Password Tidak Valid',
|
||||||
|
message: passwordErrors.join(', '),
|
||||||
|
});
|
||||||
|
form.resetFields(['user_password', 'confirmPassword']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
<Flex
|
setLoading(true);
|
||||||
align='center'
|
try {
|
||||||
justify='center'
|
const res = await register({
|
||||||
// vertical
|
user_fullname,
|
||||||
style={{
|
user_name,
|
||||||
height: '100vh',
|
user_email,
|
||||||
// marginTop: '10vh',
|
user_phone,
|
||||||
// backgroundImage: `url('https://via.placeholder.com/300')`,
|
user_password,
|
||||||
backgroundImage: `url(${sypiu_ggcp})`,
|
});
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundPosition: 'center',
|
NotifOk({
|
||||||
}}
|
icon: 'success',
|
||||||
>
|
title: 'Registrasi Berhasil',
|
||||||
<Card
|
message: res?.data?.message || 'Berhasil menambahkan user.',
|
||||||
style={{
|
});
|
||||||
boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.1)',
|
|
||||||
}}
|
form.resetFields();
|
||||||
|
setIsRegistered(true);
|
||||||
|
// navigate('/signin');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Register error:', err);
|
||||||
|
const errorMessage = err?.response?.data?.message || err.message || 'Terjadi kesalahan';
|
||||||
|
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Registrasi Gagal',
|
||||||
|
message: errorMessage || 'Terjadi kesalahan',
|
||||||
|
});
|
||||||
|
if (errorMessage.toLowerCase().includes('already')) {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
justify="center"
|
||||||
|
style={{
|
||||||
|
minHeight: '100vh',
|
||||||
|
backgroundImage: `url(${bg_cod})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
padding: '20px',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Flex align='center' justify='center'>
|
<Card
|
||||||
<Image src='/vite.svg' height={150} width={150} preview={false} alt='signin' />
|
style={{
|
||||||
</Flex>
|
width: '100%',
|
||||||
<br />
|
maxWidth: 450,
|
||||||
<Form
|
borderRadius: '12px',
|
||||||
onFinish={handleOnSubmt}
|
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)',
|
||||||
layout='vertical'
|
padding: '10px',
|
||||||
style={{ width: '250px' }}
|
textAlign: 'center',
|
||||||
>
|
}}
|
||||||
|
|
||||||
<Form.Item label="Email" name="email"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
type: 'email'
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Input placeholder='email' size='large' />
|
<Image src={logo} height={150} width={220} preview={false} alt="logo" />
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="Password" name="password"
|
<h2 style={{ marginBottom: '20px', color: '#1a3c34' }}>Registration</h2>
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: 'Please input your password!'
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input.Password placeholder='password' size='large' />
|
|
||||||
</Form.Item>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: captchaSvg }} />
|
|
||||||
|
|
||||||
<Form.Item label="Captcha" name="captcha"
|
<Form form={form} onFinish={handleSignUp} layout="vertical">
|
||||||
rules={[
|
<Row gutter={16}>
|
||||||
{
|
<Col span={12}>
|
||||||
required: true,
|
<Form.Item
|
||||||
type: 'text'
|
label="Full Name"
|
||||||
},
|
name="user_fullname"
|
||||||
]}
|
rules={[{ required: true }]}
|
||||||
>
|
>
|
||||||
<Input placeholder='Enter CAPTCHA text' size='large' />
|
<Input placeholder="Full Name" size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item label="Name" name="user_name" rules={[{ required: true }]}>
|
||||||
|
<Input placeholder="Name" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
{/* <input
|
<Row gutter={16}>
|
||||||
type="text"
|
<Col span={12}>
|
||||||
placeholder="Enter CAPTCHA text"
|
<Form.Item
|
||||||
value={userInput}
|
label="Email"
|
||||||
onChange={(e) => setUserInput(e.target.value)}
|
name="user_email"
|
||||||
/> */}
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
type: 'email',
|
||||||
|
message: 'Please input a valid email!',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Email" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item label="Phone" name="user_phone" rules={[{ required: true }]}>
|
||||||
|
<Input placeholder="Phone" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item label="Password" name="user_password" rules={[{ required: true }]}>
|
||||||
<Space direction='vertical' style={{ width: '100%' }}>
|
<Input.Password placeholder="Password" size="large" />
|
||||||
<Button type="primary" htmlType="submit" style={{ width: '100%' }}>
|
</Form.Item>
|
||||||
Registrasi
|
|
||||||
|
<Form.Item
|
||||||
|
label="Confirm Password"
|
||||||
|
name="confirmPassword"
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<Input.Password placeholder="Confirm Password" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={loading}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
Sign Up
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<Button type="primary" style={{ width: '100%' }} onClick={moveToSignin}>
|
||||||
|
Sign In
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Card>
|
||||||
</Form.Item>
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
</Form>
|
export default SignUp;
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
onClick={() => moveToSignin()}
|
|
||||||
>
|
|
||||||
Sign In
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SignUp
|
|
||||||
|
|||||||
49
src/pages/blank/RedirectWa.jsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { verifyRedirect } from '../../api/auth';
|
||||||
|
import { encryptData } from '../../components/Global/Formatter';
|
||||||
|
import NotFound from './NotFound';
|
||||||
|
import Waiting from './Waiting';
|
||||||
|
import NotificationDetailTab from '../notificationDetail/IndexNotificationDetail';
|
||||||
|
|
||||||
|
export default function RedirectWa() {
|
||||||
|
const [idData, setIdData] = useState(0);
|
||||||
|
const [ready, setReady] = useState(0);
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
// URLSearchParams untuk ambil query
|
||||||
|
const queryParams = new URLSearchParams(location.search);
|
||||||
|
const token = queryParams.get('token');
|
||||||
|
|
||||||
|
const handleInitForm = async (encodedToken) => {
|
||||||
|
const originalToken = decodeURIComponent(encodedToken);
|
||||||
|
// console.log(originalToken);
|
||||||
|
|
||||||
|
const response = await verifyRedirect({
|
||||||
|
tokenRedirect: originalToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('tes', response);
|
||||||
|
|
||||||
|
const tokenResult = JSON.stringify(response.data?.data?.accessToken);
|
||||||
|
|
||||||
|
sessionStorage.setItem('token_redirect', tokenResult);
|
||||||
|
response.data.auth = true;
|
||||||
|
sessionStorage.setItem('session', encryptData(response?.data));
|
||||||
|
|
||||||
|
setIdData(response.data.data.idData);
|
||||||
|
|
||||||
|
setReady(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleInitForm(token);
|
||||||
|
}, [idData]);
|
||||||
|
|
||||||
|
if (ready == 0) return <Waiting />;
|
||||||
|
|
||||||
|
if (idData === 0) return <NotFound />;
|
||||||
|
|
||||||
|
return <NotificationDetailTab id={idData} />;
|
||||||
|
}
|
||||||
72
src/pages/contact/IndexContact.jsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import ListContact from './component/ListContact';
|
||||||
|
import DetailContact from './component/DetailContact';
|
||||||
|
import { useBreadcrumb } from '../../layout/LayoutBreadcrumb';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const IndexContact = memo(function IndexContact() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
|
||||||
|
const [actionMode, setActionMode] = useState('list');
|
||||||
|
const [selectedData, setSelectedData] = useState(null);
|
||||||
|
const [readOnly, setReadOnly] = useState(false);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [contactType, setContactType] = useState('operator');
|
||||||
|
|
||||||
|
const setMode = (param) => {
|
||||||
|
setShowModal(param !== 'list');
|
||||||
|
setReadOnly(param === 'preview');
|
||||||
|
setActionMode(param);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContactSaved = (contactData, actionMode) => {
|
||||||
|
setLastSavedContact({ contactData, actionMode });
|
||||||
|
|
||||||
|
// Clear after processing
|
||||||
|
setTimeout(() => setLastSavedContact(null), 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [lastSavedContact, setLastSavedContact] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
setBreadcrumbItems([
|
||||||
|
{ title: <Text strong style={{ fontSize: '14px' }}>• Contact</Text> },
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
navigate('/signin');
|
||||||
|
}
|
||||||
|
}, [navigate, setBreadcrumbItems]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ListContact
|
||||||
|
actionMode={actionMode}
|
||||||
|
setActionMode={setMode}
|
||||||
|
selectedData={selectedData}
|
||||||
|
setSelectedData={setSelectedData}
|
||||||
|
readOnly={readOnly}
|
||||||
|
lastSavedContact={lastSavedContact}
|
||||||
|
setContactType={setContactType}
|
||||||
|
/>
|
||||||
|
<DetailContact
|
||||||
|
setActionMode={setMode}
|
||||||
|
selectedData={selectedData}
|
||||||
|
setSelectedData={setSelectedData}
|
||||||
|
readOnly={readOnly}
|
||||||
|
showModal={showModal}
|
||||||
|
actionMode={actionMode}
|
||||||
|
onContactSaved={handleContactSaved}
|
||||||
|
contactType={contactType}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IndexContact;
|
||||||
272
src/pages/contact/component/DetailContact.jsx
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
import React, { memo, useEffect, useState } from 'react';
|
||||||
|
import { Modal, Input, Button, Switch, ConfigProvider, Typography, Divider, Select } from 'antd';
|
||||||
|
import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
|
||||||
|
import { validateRun } from '../../../Utils/validate';
|
||||||
|
import { createContact, updateContact } from '../../../api/contact';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const DetailContact = memo(function DetailContact(props) {
|
||||||
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
|
||||||
|
const defaultData = {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
is_active: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState(defaultData);
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
|
||||||
|
// Validasi untuk field phone - hanya angka yang diperbolehkan
|
||||||
|
if (name === 'phone') {
|
||||||
|
const cleanedValue = value.replace(/[^0-9+\-\s()]/g, '');
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: cleanedValue,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleStatusToggle = (checked) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
is_active: checked,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setConfirmLoading(true);
|
||||||
|
|
||||||
|
// Validation rules
|
||||||
|
const validationRules = [
|
||||||
|
{ field: 'name', label: 'Contact Name', required: true },
|
||||||
|
{ field: 'phone', label: 'Phone', required: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
validateRun(formData, validationRules, (errorMessages) => {
|
||||||
|
NotifOk({ icon: 'warning', title: 'Peringatan', message: errorMessages });
|
||||||
|
setConfirmLoading(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Custom validation untuk name - minimal 3 karakter
|
||||||
|
if (formData.name && formData.name.length < 3) {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Peringatan',
|
||||||
|
message: 'Nama contact minimal 3 karakter',
|
||||||
|
});
|
||||||
|
setConfirmLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validation untuk phone - Indonesian phone format
|
||||||
|
const phoneRegex = /^(?:\+62|0)8\d{7,10}$/;
|
||||||
|
if (formData.phone && !phoneRegex.test(formData.phone.replace(/[\s\-\(\)]/g, ''))) {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Peringatan',
|
||||||
|
message: 'Nomor telepon harus format Indonesia (+628XXXXXXXXX atau 08XXXXXXXXX)',
|
||||||
|
});
|
||||||
|
setConfirmLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const contactData = {
|
||||||
|
contact_name: formData.name,
|
||||||
|
contact_phone: formData.phone.replace(/[\s\-\(\)]/g, ''), // Clean phone number
|
||||||
|
is_active: formData.is_active,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (props.actionMode === 'edit') {
|
||||||
|
response = await updateContact(
|
||||||
|
props.selectedData.contact_id || props.selectedData.id,
|
||||||
|
contactData
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
response = await createContact(contactData);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: `Data Contact "${formData.name}" berhasil ${
|
||||||
|
props.actionMode === 'add' ? 'ditambahkan' : 'diperbarui'
|
||||||
|
}.`,
|
||||||
|
});
|
||||||
|
|
||||||
|
props.onContactSaved?.(response.data, props.actionMode);
|
||||||
|
handleCancel();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Save failed:', error);
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: error.response?.data?.message || 'Terjadi kesalahan saat menyimpan data.',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setConfirmLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
props.setActionMode('list');
|
||||||
|
props.setSelectedData(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.showModal) {
|
||||||
|
if (props.actionMode === 'edit' && props.selectedData) {
|
||||||
|
setFormData({
|
||||||
|
name: props.selectedData.contact_name || props.selectedData.name,
|
||||||
|
phone: props.selectedData.contact_phone || props.selectedData.phone,
|
||||||
|
is_active:
|
||||||
|
props.selectedData.is_active || props.selectedData.status === 'active',
|
||||||
|
});
|
||||||
|
} else if (props.actionMode === 'add') {
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
is_active: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [props.showModal, props.actionMode, props.selectedData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={`${
|
||||||
|
props.actionMode === 'add'
|
||||||
|
? 'Tambah'
|
||||||
|
: props.actionMode === 'edit'
|
||||||
|
? 'Edit'
|
||||||
|
: 'Detail'
|
||||||
|
} Kontak`}
|
||||||
|
open={props.showModal}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={[
|
||||||
|
<React.Fragment key="modal-footer">
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Button: {
|
||||||
|
defaultBg: 'white',
|
||||||
|
defaultColor: '#23A55A',
|
||||||
|
defaultBorderColor: '#23A55A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button onClick={handleCancel}>{props.readOnly ? 'Tutup' : 'Batal'}</Button>
|
||||||
|
</ConfigProvider>
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Button: {
|
||||||
|
defaultBg: '#23a55a',
|
||||||
|
defaultColor: '#FFFFFF',
|
||||||
|
defaultBorderColor: '#23a55a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!props.readOnly && (
|
||||||
|
<Button loading={confirmLoading} onClick={handleSave}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ConfigProvider>
|
||||||
|
</React.Fragment>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div style={{ padding: '8px 0' }}>
|
||||||
|
{/* Status field only show in add mode*/}
|
||||||
|
{props.actionMode === 'add' && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Text strong>Status</Text>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ display: 'flex', alignItems: 'center', marginTop: '8px' }}
|
||||||
|
>
|
||||||
|
<div style={{ marginRight: '8px' }}>
|
||||||
|
<Switch
|
||||||
|
disabled={props.readOnly}
|
||||||
|
style={{
|
||||||
|
backgroundColor: formData.is_active
|
||||||
|
? '#23A55A'
|
||||||
|
: '#bfbfbf',
|
||||||
|
}}
|
||||||
|
checked={formData.is_active}
|
||||||
|
onChange={handleStatusToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text>{formData.is_active ? 'Active' : 'Inactive'}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Divider style={{ margin: '12px 0' }} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Name</Text>
|
||||||
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
|
<Input
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Enter Name"
|
||||||
|
readOnly={props.readOnly}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Phone</Text>
|
||||||
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
|
<Input
|
||||||
|
name="phone"
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Enter Phone Number"
|
||||||
|
readOnly={props.readOnly}
|
||||||
|
maxLength={15}
|
||||||
|
style={{ color: formData.is_active ? '#000000' : '#ff4d4f' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Contact Type */}
|
||||||
|
{/* <div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Contact Type</Text>
|
||||||
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
|
<Select
|
||||||
|
value={formData.contact_type || undefined}
|
||||||
|
onChange={handleContactTypeChange}
|
||||||
|
placeholder="Select Contact Type"
|
||||||
|
disabled={props.readOnly}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
<Select.Option value="operator">Operator</Select.Option>
|
||||||
|
<Select.Option value="gudang">Gudang</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default DetailContact;
|
||||||
487
src/pages/contact/component/ListContact.jsx
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { Button, Row, Col, Input, Tabs, Space, ConfigProvider, Card, Tag, Switch } from 'antd';
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
PhoneOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { NotifAlert, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
||||||
|
import { getAllContact, deleteContact, updateContact } from '../../../api/contact';
|
||||||
|
|
||||||
|
const ContactCard = memo(function ContactCard({
|
||||||
|
contact,
|
||||||
|
showEditModal,
|
||||||
|
showDeleteModal,
|
||||||
|
onStatusToggle,
|
||||||
|
}) {
|
||||||
|
const handleStatusToggle = async (checked) => {
|
||||||
|
try {
|
||||||
|
const updatedContact = {
|
||||||
|
contact_name: contact.contact_name || contact.name,
|
||||||
|
contact_phone: contact.contact_phone || contact.phone,
|
||||||
|
is_active: checked,
|
||||||
|
contact_type: contact.contact_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
await updateContact(contact.contact_id || contact.id, updatedContact);
|
||||||
|
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: `Status "${contact.contact_name || contact.name}" berhasil diperbarui.`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Refresh contacts list
|
||||||
|
onStatusToggle && onStatusToggle();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating contact status:', error);
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Gagal memperbarui status kontak',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6}>
|
||||||
|
<div
|
||||||
|
className="contact-card"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||||
|
height: '100%',
|
||||||
|
padding: '16px',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
border: '1px solid #e8e8e8',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Type Badge - Top Left */}
|
||||||
|
{/* <div style={{ position: 'absolute', top: 0, left: 0, zIndex: 1 }}>
|
||||||
|
<Tag
|
||||||
|
color={
|
||||||
|
contact.contact_type === 'operator'
|
||||||
|
? 'blue'
|
||||||
|
: contact.contact_type === 'gudang'
|
||||||
|
? 'orange'
|
||||||
|
: 'default'
|
||||||
|
}
|
||||||
|
style={{ fontSize: '11px' }}
|
||||||
|
>
|
||||||
|
{contact.contact_type === 'operator' ? 'Operator' : contact.contact_type === 'gudang' ? 'Gudang' : 'Unknown'}
|
||||||
|
</Tag>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
{/* Status Slider - Top Right */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
padding: '4px 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||||
|
<Switch
|
||||||
|
checked={contact.status === 'active'}
|
||||||
|
onChange={handleStatusToggle}
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
contact.status === 'active' ? '#52c41a' : '#d9d9d9',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: contact.status === 'active' ? '#52c41a' : '#ff4d4f',
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{contact.status === 'active' ? 'Active' : 'Inactive'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
flex: 1,
|
||||||
|
paddingTop: '28px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="avatar"
|
||||||
|
style={{
|
||||||
|
width: 55,
|
||||||
|
height: 55,
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor:
|
||||||
|
contact.status === 'active' ? '#52c41a' : '#ff4d4f',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserOutlined style={{ color: 'white', fontSize: '25px' }} />
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '16px',
|
||||||
|
marginBottom: '4px',
|
||||||
|
color: '#262626',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{contact.contact_name || contact.name}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: '14px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PhoneOutlined style={{ marginRight: 6, color: '#1890ff' }} />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: contact.status === 'active' ? '#262626' : '#262626',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{contact.contact_phone || contact.phone}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Edit and Delete Buttons - Bottom Right */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
gap: '8px',
|
||||||
|
marginTop: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#fff7e6',
|
||||||
|
borderColor: '#faad14',
|
||||||
|
color: '#faad14',
|
||||||
|
padding: '2px 6px',
|
||||||
|
fontSize: '11px',
|
||||||
|
height: '24px',
|
||||||
|
}}
|
||||||
|
icon={
|
||||||
|
<EditOutlined style={{ color: '#faad14', fontSize: '11px' }} />
|
||||||
|
}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showEditModal(contact);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit info
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#fff1f0',
|
||||||
|
borderColor: 'red',
|
||||||
|
padding: '2px 6px',
|
||||||
|
fontSize: '11px',
|
||||||
|
height: '24px',
|
||||||
|
}}
|
||||||
|
icon={<DeleteOutlined style={{ fontSize: '11px' }} />}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
showDeleteModal(contact);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListContact = memo(function ListContact(props) {
|
||||||
|
const [activeTab, setActiveTab] = useState('all');
|
||||||
|
const [filteredContacts, setFilteredContacts] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Default filter object matching plantSection pattern
|
||||||
|
const defaultFilter = { criteria: '' };
|
||||||
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||||
|
|
||||||
|
// Fetch contacts from API
|
||||||
|
const fetchContacts = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// Build search parameters matching database pattern
|
||||||
|
const searchParams = { ...formDataFilter };
|
||||||
|
|
||||||
|
// Add specific filters if not "all"
|
||||||
|
if (activeTab !== 'all') {
|
||||||
|
if (activeTab === 'operator') {
|
||||||
|
searchParams.code = 'operator';
|
||||||
|
} else if (activeTab === 'gudang') {
|
||||||
|
searchParams.code = 'gudang';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
Object.entries(searchParams).forEach(([key, value]) => {
|
||||||
|
if (value !== '' && value !== null && value !== undefined) {
|
||||||
|
queryParams.append(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await getAllContact(queryParams);
|
||||||
|
setFilteredContacts(response.data || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching contacts:', error);
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Gagal memuat data kontak',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch contacts on component mount
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
navigate('/signin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchContacts();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Refetch when filters change
|
||||||
|
useEffect(() => {
|
||||||
|
fetchContacts();
|
||||||
|
}, [formDataFilter, activeTab]);
|
||||||
|
|
||||||
|
// Listen for saved contact data
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.lastSavedContact) {
|
||||||
|
fetchContacts();
|
||||||
|
}
|
||||||
|
}, [props.lastSavedContact]);
|
||||||
|
|
||||||
|
const getFilteredContacts = () => {
|
||||||
|
return filteredContacts;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showEditModal = (param) => {
|
||||||
|
props.setSelectedData(param);
|
||||||
|
props.setActionMode('edit');
|
||||||
|
};
|
||||||
|
|
||||||
|
const showAddModal = () => {
|
||||||
|
props.setSelectedData(null);
|
||||||
|
props.setActionMode('add');
|
||||||
|
|
||||||
|
props.setContactType?.(activeTab);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDeleteModal = (contact) => {
|
||||||
|
NotifConfirmDialog({
|
||||||
|
icon: 'question',
|
||||||
|
title: 'Konfirmasi Hapus',
|
||||||
|
message: `Kontak "${contact.contact_name || contact.name}" akan dihapus?`,
|
||||||
|
onConfirm: () => handleDelete(contact),
|
||||||
|
onCancel: () => props.setSelectedData(null),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (contact) => {
|
||||||
|
try {
|
||||||
|
await deleteContact(contact.contact_id || contact.id);
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: `Kontak "${contact.contact_name || contact.name}" berhasil dihapus.`,
|
||||||
|
});
|
||||||
|
// Refetch contacts after deletion
|
||||||
|
fetchContacts();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting contact:', error);
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Gagal menghapus kontak',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Card>
|
||||||
|
<Row>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Row justify="space-between" align="middle" gutter={[8, 8]}>
|
||||||
|
<Col xs={24} sm={24} md={12} lg={12}>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Search by name..."
|
||||||
|
value={formDataFilter.criteria}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setFormDataFilter({ criteria: value });
|
||||||
|
if (value === '') {
|
||||||
|
setFormDataFilter(defaultFilter);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSearch={(value) => setFormDataFilter({ criteria: value })}
|
||||||
|
allowClear={{
|
||||||
|
clearIcon: (
|
||||||
|
<span onClick={() => setFormDataFilter(defaultFilter)}>
|
||||||
|
✕
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
enterButton={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Space wrap size="small">
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Button: {
|
||||||
|
defaultBg: 'white',
|
||||||
|
defaultColor: '#23A55A',
|
||||||
|
defaultBorderColor: '#23A55A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => showAddModal()}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
Add Contact
|
||||||
|
</Button>
|
||||||
|
</ConfigProvider>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} style={{ marginTop: '16px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Tabs */}
|
||||||
|
{/* <Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={setActiveTab}
|
||||||
|
size="large"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'all',
|
||||||
|
label: 'All',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'operator',
|
||||||
|
label: 'Operator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'gudang',
|
||||||
|
label: 'Gudang',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{getFilteredContacts().length === 0 ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||||
|
<span style={{ color: '#8c8c8c' }}>
|
||||||
|
{loading ? 'Loading contacts...' : 'No contacts found'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
{getFilteredContacts().map((contact) => (
|
||||||
|
<ContactCard
|
||||||
|
key={contact.contact_id || contact.id}
|
||||||
|
contact={{
|
||||||
|
...contact,
|
||||||
|
id: contact.contact_id || contact.id,
|
||||||
|
name: contact.contact_name || contact.name,
|
||||||
|
phone: contact.contact_phone || contact.phone,
|
||||||
|
status: contact.is_active ? 'active' : 'inactive',
|
||||||
|
}}
|
||||||
|
showEditModal={showEditModal}
|
||||||
|
showDeleteModal={showDeleteModal}
|
||||||
|
onStatusToggle={fetchContacts}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ListContact;
|
||||||
38
src/pages/history/alarm/IndexHistoryAlarm.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import ListHistoryAlarm from './component/ListHistoryAlarm';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const IndexHistoryAlarm = memo(function IndexHistoryAlarm() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
const [selectedData, setSelectedData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
setBreadcrumbItems([
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<Text strong style={{ fontSize: '14px' }}>
|
||||||
|
• History Event
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
navigate('/signin');
|
||||||
|
}
|
||||||
|
}, [navigate, setBreadcrumbItems]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ListHistoryAlarm selectedData={selectedData} setSelectedData={setSelectedData} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IndexHistoryAlarm;
|
||||||
158
src/pages/history/alarm/component/ListHistoryAlarm.jsx
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { Button, Row, Col, Card, Input } from 'antd';
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import TableList from '../../../../components/Global/TableList';
|
||||||
|
import { getAllHistoryAlarm } from '../../../../api/history-value';
|
||||||
|
|
||||||
|
const ListHistoryAlarm = memo(function ListHistoryAlarm(props) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Datetime',
|
||||||
|
dataIndex: 'datetime',
|
||||||
|
key: 'datetime',
|
||||||
|
width: '15%',
|
||||||
|
// render: (_, record) => toAppDateTimezoneFormatter(record.datetime),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Tag Name',
|
||||||
|
dataIndex: 'tag_name',
|
||||||
|
key: 'tag_name',
|
||||||
|
width: '40%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Value',
|
||||||
|
dataIndex: 'new_val',
|
||||||
|
key: 'new_val',
|
||||||
|
width: '10%',
|
||||||
|
render: (_, record) => Number(record.new_val).toFixed(4),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Threshold',
|
||||||
|
dataIndex: 'threshold',
|
||||||
|
key: 'threshold',
|
||||||
|
width: '10%',
|
||||||
|
render: (_, record) => {
|
||||||
|
switch (record.status) {
|
||||||
|
case 1:
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{record.lim_low} : {record.lim_high}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case 2:
|
||||||
|
return <span>{`< ${record.lim_low_crash}`}</span>;
|
||||||
|
case 3:
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{record.lim_low_crash} : {record.lim_low}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case 4:
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{record.lim_high} : {record.lim_high_crash}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case 5:
|
||||||
|
return <span>{`> ${record.lim_high_crash}`}</span>;
|
||||||
|
default:
|
||||||
|
return <span>Undefined</span>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Condition',
|
||||||
|
dataIndex: 'condition',
|
||||||
|
key: 'condition',
|
||||||
|
width: '20%',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Button type="text" style={{ backgroundColor: record.status_color, width: '100%' }}>
|
||||||
|
{record.condition}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Stat',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
width: '5%',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||||
|
|
||||||
|
const defaultFilter = { criteria: '' };
|
||||||
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setFormDataFilter({ criteria: searchValue });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchClear = () => {
|
||||||
|
setSearchValue('');
|
||||||
|
setFormDataFilter({ criteria: '' });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Card>
|
||||||
|
<Row>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Row justify="space-between" align="middle" gutter={[8, 8]}>
|
||||||
|
<Col xs={24} sm={24} md={12} lg={12}>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Search ..."
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setSearchValue(value);
|
||||||
|
if (value === '') {
|
||||||
|
handleSearchClear();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
allowClear={{
|
||||||
|
clearIcon: <span onClick={handleSearchClear}>✕</span>,
|
||||||
|
}}
|
||||||
|
enterButton={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
|
||||||
|
<TableList
|
||||||
|
getData={getAllHistoryAlarm}
|
||||||
|
queryParams={formDataFilter}
|
||||||
|
columns={columns}
|
||||||
|
triger={trigerFilter}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ListHistoryAlarm;
|
||||||
38
src/pages/history/event/IndexHistoryEvent.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import ListHistoryEvent from './component/ListHistoryEvent';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const IndexHistoryEvent = memo(function IndexHistoryEvent() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
const [selectedData, setSelectedData] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
setBreadcrumbItems([
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<Text strong style={{ fontSize: '14px' }}>
|
||||||
|
• History Event
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
navigate('/signin');
|
||||||
|
}
|
||||||
|
}, [navigate, setBreadcrumbItems]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ListHistoryEvent selectedData={selectedData} setSelectedData={setSelectedData} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IndexHistoryEvent;
|
||||||
117
src/pages/history/event/component/ListHistoryEvent.jsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { Button, Row, Col, Card, Input } from 'antd';
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import TableList from '../../../../components/Global/TableList';
|
||||||
|
import { getAllHistoryEvent } from '../../../../api/history-value';
|
||||||
|
|
||||||
|
const ListHistoryEvent = memo(function ListHistoryEvent(props) {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Datetime',
|
||||||
|
dataIndex: 'datetime',
|
||||||
|
key: 'datetime',
|
||||||
|
width: '15%',
|
||||||
|
// render: (_, record) => toAppDateTimezoneFormatter(record.datetime),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Tag Name',
|
||||||
|
dataIndex: 'tagname',
|
||||||
|
key: 'tagname',
|
||||||
|
width: '40%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Description',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
width: '20%',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Button type="text" style={{ backgroundColor: record.status_color, width: '100%' }}>
|
||||||
|
{record.description}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Stat',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
width: '5%',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||||
|
|
||||||
|
const defaultFilter = { criteria: '' };
|
||||||
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setFormDataFilter({ criteria: searchValue });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchClear = () => {
|
||||||
|
setSearchValue('');
|
||||||
|
setFormDataFilter({ criteria: '' });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Card>
|
||||||
|
<Row>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Row justify="space-between" align="middle" gutter={[8, 8]}>
|
||||||
|
<Col xs={24} sm={24} md={12} lg={12}>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Search ..."
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setSearchValue(value);
|
||||||
|
if (value === '') {
|
||||||
|
handleSearchClear();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
allowClear={{
|
||||||
|
clearIcon: <span onClick={handleSearchClear}>✕</span>,
|
||||||
|
}}
|
||||||
|
enterButton={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
|
||||||
|
<TableList
|
||||||
|
getData={getAllHistoryEvent}
|
||||||
|
queryParams={formDataFilter}
|
||||||
|
columns={columns}
|
||||||
|
triger={trigerFilter}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ListHistoryEvent;
|
||||||
@@ -9,44 +9,39 @@ const Home = () => {
|
|||||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => setIsMobile(window.innerWidth <= 768);
|
||||||
setIsMobile(window.innerWidth <= 768);
|
|
||||||
};
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('token');
|
setBreadcrumbItems([
|
||||||
if (token) {
|
{
|
||||||
setBreadcrumbItems([
|
title: (
|
||||||
{
|
<Text strong style={{ fontSize: '14px' }}>
|
||||||
title: (
|
Dashboard
|
||||||
<Text strong style={{ fontSize: '14px' }}>
|
</Text>
|
||||||
• Dashboard
|
),
|
||||||
</Text>
|
},
|
||||||
),
|
{
|
||||||
},
|
title: (
|
||||||
{
|
<Text strong style={{ fontSize: '14px' }}>
|
||||||
title: (
|
Home
|
||||||
<Text strong style={{ fontSize: '14px' }}>
|
</Text>
|
||||||
Home
|
),
|
||||||
</Text>
|
},
|
||||||
),
|
]);
|
||||||
},
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
navigate('/signin');
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<Flex align="center" justify="center">
|
<Flex align="center" justify="center">
|
||||||
<Text strong style={{fontSize:'30px'}}>Wellcome Call Of Duty App</Text>
|
<Text strong style={{ fontSize: '30px' }}>
|
||||||
|
Welcome to Call Of Duty App
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
21
src/pages/home/SvgAirDryerA.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import SvgTemplate from './SvgTemplate';
|
||||||
|
import SvgViewer from './SvgViewer';
|
||||||
|
import filePathSvg from '../../assets/svg/air_dryer_A_rev.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// const filePathSvg = '/src/assets/svg/air_dryer_A_rev.svg';
|
||||||
|
const topicMqtt = 'PIU_COD/AIR_DRYER/AIR_DRYER_A';
|
||||||
|
|
||||||
|
const SvgAirDryerA = () => {
|
||||||
|
return (
|
||||||
|
<SvgTemplate>
|
||||||
|
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||||
|
</SvgTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgAirDryerA;
|
||||||
21
src/pages/home/SvgAirDryerB.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import SvgTemplate from './SvgTemplate';
|
||||||
|
import SvgViewer from './SvgViewer';
|
||||||
|
import filePathSvg from '../../assets/svg/air_dryer_B_rev.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// const filePathSvg = '/src/assets/svg/air_dryer_B_rev.svg';
|
||||||
|
const topicMqtt = 'PIU_COD/AIR_DRYER/AIR_DRYER_B';
|
||||||
|
|
||||||
|
const SvgAirDryerB = () => {
|
||||||
|
return (
|
||||||
|
<SvgTemplate>
|
||||||
|
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||||
|
</SvgTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgAirDryerB;
|
||||||
21
src/pages/home/SvgAirDryerC.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import SvgTemplate from './SvgTemplate';
|
||||||
|
import SvgViewer from './SvgViewer';
|
||||||
|
import filePathSvg from '../../assets/svg/air_dryer_C_rev.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// const filePathSvg = '/src/assets/svg/air_dryer_C_rev.svg';
|
||||||
|
const topicMqtt = 'PIU_COD/AIR_DRYER/AIR_DRYER_C';
|
||||||
|
|
||||||
|
const SvgAirDryerC = () => {
|
||||||
|
return (
|
||||||
|
<SvgTemplate>
|
||||||
|
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||||
|
</SvgTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgAirDryerC;
|
||||||
21
src/pages/home/SvgCompressorA.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import SvgTemplate from './SvgTemplate';
|
||||||
|
import SvgViewer from './SvgViewer';
|
||||||
|
import filePathSvg from '../../assets/svg/compressorA_rev.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// const filePathSvg = '/src/assets/svg/test-new.svg';
|
||||||
|
const topicMqtt = 'PIU_COD/COMPRESSOR/COMPRESSOR_A';
|
||||||
|
|
||||||
|
const SvgCompressorA = () => {
|
||||||
|
return (
|
||||||
|
<SvgTemplate>
|
||||||
|
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||||
|
</SvgTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgCompressorA;
|
||||||
19
src/pages/home/SvgCompressorB.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import SvgTemplate from './SvgTemplate';
|
||||||
|
import SvgViewer from './SvgViewer';
|
||||||
|
import filePathSvg from '../../assets/svg/compressorB_rev.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
const topicMqtt = 'PIU_COD/COMPRESSOR/COMPRESSOR_B';
|
||||||
|
|
||||||
|
const SvgCompressorB = () => {
|
||||||
|
return (
|
||||||
|
<SvgTemplate>
|
||||||
|
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||||
|
</SvgTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgCompressorB;
|
||||||
21
src/pages/home/SvgCompressorC.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import SvgTemplate from './SvgTemplate';
|
||||||
|
import SvgViewer from './SvgViewer';
|
||||||
|
import filePathSvg from '../../assets/svg/compressorC_rev.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// const filePathSvg = '/src/assets/svg/test-new.svg';
|
||||||
|
const topicMqtt = 'PIU_COD/COMPRESSOR/COMPRESSOR_C';
|
||||||
|
|
||||||
|
const SvgCompressorC = () => {
|
||||||
|
return (
|
||||||
|
<SvgTemplate>
|
||||||
|
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||||
|
</SvgTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgCompressorC;
|
||||||
21
src/pages/home/SvgOverviewAirDryer.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import SvgTemplate from './SvgTemplate';
|
||||||
|
import SvgViewer from './SvgViewer';
|
||||||
|
import filePathSvg from '../../assets/svg/overview-airdryer.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// const filePathSvg = '/src/assets/svg/test-new.svg';
|
||||||
|
const topicMqtt = 'PIU_COD/AIR_DRYER/OVERVIEW';
|
||||||
|
|
||||||
|
const SvgOverviewAirDryer = () => {
|
||||||
|
return (
|
||||||
|
<SvgTemplate>
|
||||||
|
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||||
|
</SvgTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgOverviewAirDryer;
|
||||||
21
src/pages/home/SvgOverviewCompressor.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import SvgTemplate from './SvgTemplate';
|
||||||
|
import SvgViewer from './SvgViewer';
|
||||||
|
import filePathSvg from '../../assets/svg/overview-compressor.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// const filePathSvg = '/src/assets/svg/test-new.svg';
|
||||||
|
const topicMqtt = 'PIU_COD/COMPRESSOR/OVERVIEW';
|
||||||
|
|
||||||
|
const SvgOverviewCompressor = () => {
|
||||||
|
return (
|
||||||
|
<SvgTemplate>
|
||||||
|
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||||
|
</SvgTemplate>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgOverviewCompressor;
|
||||||
19
src/pages/home/SvgTemplate.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const SvgTemplate = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '80vh', // penuh 1 layar
|
||||||
|
width: '80vw', // penuh 1 layar lebar
|
||||||
|
overflow: 'hidden', // hilangkan scroll
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#fff', // opsional
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgTemplate;
|
||||||
33
src/pages/home/SvgTest.jsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Card, Typography, Flex } from 'antd';
|
||||||
|
// import { ReactSVG } from 'react-svg';
|
||||||
|
import { setValSvg } from '../../components/Global/MqttConnection';
|
||||||
|
import { ReactSVG } from 'react-svg';
|
||||||
|
import filePathSvg from '../../assets/svg/test-new.svg';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// const filePathSvg = '/src/assets/svg/test-new.svg';
|
||||||
|
const topicMqtt = 'PIU_GGCP/Devices/PB';
|
||||||
|
|
||||||
|
const SvgTest = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card>
|
||||||
|
<Flex align="center" justify="center">
|
||||||
|
<Text strong style={{ fontSize: '30px' }}>
|
||||||
|
Example SVG Value By Mqtt
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
<ReactSVG
|
||||||
|
src={filePathSvg}
|
||||||
|
beforeInjection={(svg) => {
|
||||||
|
setValSvg(topicMqtt, svg);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgTest;
|
||||||
19
src/pages/home/SvgViewer.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// SvgViewer.jsx
|
||||||
|
import { ReactSVG } from 'react-svg';
|
||||||
|
|
||||||
|
const SvgViewer = ({ filePathSvg, topicMqtt, setValSvg }) => {
|
||||||
|
return (
|
||||||
|
<ReactSVG
|
||||||
|
src={filePathSvg}
|
||||||
|
beforeInjection={(svg) => {
|
||||||
|
svg.setAttribute('width', '100%');
|
||||||
|
svg.setAttribute('height', '100%');
|
||||||
|
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
||||||
|
if (setValSvg) setValSvg(topicMqtt, svg);
|
||||||
|
}}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgViewer;
|
||||||
74
src/pages/jadwalShift/IndexJadwalShift.jsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import ListJadwalShift from './component/ListJadwalShift';
|
||||||
|
import DetailJadwalShift from './component/DetailJadwalShift';
|
||||||
|
import { useBreadcrumb } from '../../layout/LayoutBreadcrumb';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const IndexJadwalShift = memo(function IndexJadwalShift() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
|
||||||
|
const [actionMode, setActionMode] = useState('list');
|
||||||
|
const [selectedData, setSelectedData] = useState(null);
|
||||||
|
const [readOnly, setReadOnly] = useState(false);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
|
const setMode = (param) => {
|
||||||
|
setShowModal(true);
|
||||||
|
switch (param) {
|
||||||
|
case 'add':
|
||||||
|
setReadOnly(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit':
|
||||||
|
setReadOnly(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'preview':
|
||||||
|
setReadOnly(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
setShowModal(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
setActionMode(param);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
setBreadcrumbItems([
|
||||||
|
{ title: <Text strong style={{ fontSize: '14px' }}>• Jadwal</Text> },
|
||||||
|
{ title: <Text strong style={{ fontSize: '14px' }}>Jadwal Shift</Text> }
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
navigate('/signin');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ListJadwalShift
|
||||||
|
actionMode={actionMode}
|
||||||
|
setActionMode={setMode}
|
||||||
|
selectedData={selectedData}
|
||||||
|
setSelectedData={setSelectedData}
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
<DetailJadwalShift
|
||||||
|
setActionMode={setMode}
|
||||||
|
selectedData={selectedData}
|
||||||
|
setSelectedData={setSelectedData}
|
||||||
|
readOnly={readOnly}
|
||||||
|
showModal={showModal}
|
||||||
|
actionMode={actionMode}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IndexJadwalShift;
|
||||||
280
src/pages/jadwalShift/component/DetailJadwalShift.jsx
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Modal, Select, Typography, Button, ConfigProvider } from 'antd';
|
||||||
|
import { NotifOk } from '../../../components/Global/ToastNotif';
|
||||||
|
import { createJadwalShift, updateJadwalShift } from '../../../api/jadwal-shift';
|
||||||
|
import { getAllUser } from '../../../api/user';
|
||||||
|
import { getAllShift } from '../../../api/master-shift';
|
||||||
|
import { validateRun } from '../../../Utils/validate';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const DetailJadwalShift = (props) => {
|
||||||
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
const [employees, setEmployees] = useState([]);
|
||||||
|
const [shifts, setShifts] = useState([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(false);
|
||||||
|
|
||||||
|
const defaultData = {
|
||||||
|
id: '',
|
||||||
|
user_id: null,
|
||||||
|
shift_id: null,
|
||||||
|
schedule_id: '',
|
||||||
|
user_phone: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState(defaultData);
|
||||||
|
|
||||||
|
const handleSelectChange = (name, value) => {
|
||||||
|
const updates = { [name]: value };
|
||||||
|
|
||||||
|
if (name === 'user_id') {
|
||||||
|
const selectedEmployee = employees.find((emp) => emp.user_id === value);
|
||||||
|
updates.user_phone = selectedEmployee?.user_phone || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
...updates,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
props.setSelectedData(null);
|
||||||
|
props.setActionMode('list');
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoadingData(true);
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page: 1,
|
||||||
|
limit: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [usersResponse, shiftsResponse] = await Promise.all([
|
||||||
|
getAllUser(params),
|
||||||
|
getAllShift(params),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const userData = usersResponse?.data || usersResponse || [];
|
||||||
|
const shiftData = shiftsResponse?.data || shiftsResponse || [];
|
||||||
|
|
||||||
|
setEmployees(Array.isArray(userData) ? userData : []);
|
||||||
|
setShifts(Array.isArray(shiftData) ? shiftData : []);
|
||||||
|
} catch (error) {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: 'Gagal memuat data karyawan atau shift.',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoadingData(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setConfirmLoading(true);
|
||||||
|
|
||||||
|
// Daftar aturan validasi
|
||||||
|
const validationRules = [
|
||||||
|
{ field: 'user_id', label: 'Nama Karyawan', required: true },
|
||||||
|
{ field: 'shift_id', label: 'Shift', required: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
validateRun(formData, validationRules, (errorMessages) => {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Peringatan',
|
||||||
|
message: errorMessages,
|
||||||
|
});
|
||||||
|
setConfirmLoading(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
user_id: formData.user_id,
|
||||||
|
shift_id: formData.shift_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add schedule_id only if editing and it exists
|
||||||
|
if (props.actionMode === 'edit' && formData.schedule_id) {
|
||||||
|
payload.schedule_id = formData.schedule_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response =
|
||||||
|
props.actionMode === 'edit'
|
||||||
|
? await updateJadwalShift(formData.id, payload)
|
||||||
|
: await createJadwalShift(payload);
|
||||||
|
|
||||||
|
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||||
|
const action = props.actionMode === 'edit' ? 'diubah' : 'ditambahkan';
|
||||||
|
|
||||||
|
NotifOk({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: `Jadwal berhasil ${action}.`,
|
||||||
|
});
|
||||||
|
|
||||||
|
props.setActionMode('list');
|
||||||
|
} else {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: response?.message || 'Terjadi kesalahan saat menyimpan data.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: error.message || 'Terjadi kesalahan pada server.',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setConfirmLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.showModal) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.selectedData) {
|
||||||
|
setFormData({
|
||||||
|
id: props.selectedData.id || '',
|
||||||
|
user_id: props.selectedData.user_id || null,
|
||||||
|
shift_id: props.selectedData.shift_id || null,
|
||||||
|
schedule_id: props.selectedData.schedule_id || '',
|
||||||
|
user_phone: props.selectedData.whatsapp || props.selectedData.user_phone || null,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setFormData(defaultData);
|
||||||
|
}
|
||||||
|
}, [props.showModal, props.selectedData, props.actionMode]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={`${
|
||||||
|
props.actionMode === 'add'
|
||||||
|
? 'Tambah'
|
||||||
|
: props.actionMode === 'preview'
|
||||||
|
? 'Preview'
|
||||||
|
: 'Edit'
|
||||||
|
} Jadwal Shift`}
|
||||||
|
open={props.showModal}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={[
|
||||||
|
<React.Fragment key="modal-footer">
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Button: {
|
||||||
|
defaultBg: 'white',
|
||||||
|
defaultColor: '#23A55A',
|
||||||
|
defaultBorderColor: '#23A55A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button onClick={handleCancel}>{props.readOnly ? 'Tutup' : 'Batal'}</Button>
|
||||||
|
</ConfigProvider>
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Button: {
|
||||||
|
defaultBg: '#23a55a',
|
||||||
|
defaultColor: '#FFFFFF',
|
||||||
|
defaultBorderColor: '#23a55a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!props.readOnly && (
|
||||||
|
<Button loading={confirmLoading} onClick={handleSave}>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ConfigProvider>
|
||||||
|
</React.Fragment>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{formData && (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Nama Karyawan</Text>
|
||||||
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
|
<Select
|
||||||
|
value={formData.user_id}
|
||||||
|
onChange={(value) => handleSelectChange('user_id', value)}
|
||||||
|
placeholder="Pilih karyawan"
|
||||||
|
disabled={props.readOnly || loadingData}
|
||||||
|
loading={loadingData}
|
||||||
|
showSearch
|
||||||
|
optionFilterProp="children"
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
option?.children?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{employees
|
||||||
|
.filter((emp) => emp.user_id != null)
|
||||||
|
.map((emp) => (
|
||||||
|
<Option key={`emp-${emp.user_id}`} value={emp.user_id}>
|
||||||
|
{emp.user_fullname || emp.user_name}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>No. Telepon</Text>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '8px 12px',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
borderRadius: '6px',
|
||||||
|
|
||||||
|
marginTop: '4px',
|
||||||
|
color: formData.user_phone ? '#000' : '#999',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formData.user_phone || 'Pilih karyawan terlebih dahulu'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Shift</Text>
|
||||||
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
|
<Select
|
||||||
|
value={formData.shift_id}
|
||||||
|
onChange={(value) => handleSelectChange('shift_id', value)}
|
||||||
|
placeholder="Pilih shift"
|
||||||
|
disabled={props.readOnly || loadingData}
|
||||||
|
loading={loadingData}
|
||||||
|
showSearch
|
||||||
|
optionFilterProp="children"
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
option?.children?.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{shifts
|
||||||
|
.filter((shift) => shift.shift_id != null)
|
||||||
|
.map((shift) => (
|
||||||
|
<Option key={`shift-${shift.shift_id}`} value={shift.shift_id}>
|
||||||
|
{shift.shift_name}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailJadwalShift;
|
||||||
822
src/pages/jadwalShift/component/ListJadwalShift.jsx
Normal file
@@ -0,0 +1,822 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Space,
|
||||||
|
ConfigProvider,
|
||||||
|
Button,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Card,
|
||||||
|
Input,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
Checkbox,
|
||||||
|
Select,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getAllJadwalShift, deleteJadwalShift, updateJadwalShift } from '../../../api/jadwal-shift';
|
||||||
|
import { getAllShift } from '../../../api/master-shift';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
const ListJadwalShift = memo(function ListJadwalShift(props) {
|
||||||
|
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||||
|
const defaultFilter = { criteria: '' };
|
||||||
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||||
|
const [groupedSchedules, setGroupedSchedules] = useState({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const [selectedSchedules, setSelectedSchedules] = useState([]);
|
||||||
|
const [editingShift, setEditingShift] = useState(null);
|
||||||
|
const [pendingChanges, setPendingChanges] = useState({});
|
||||||
|
const [employeeOptions, setEmployeeOptions] = useState([]);
|
||||||
|
const [shiftOptions, setShiftOptions] = useState([]);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const formatRelativeTimestamp = (timestamp) => {
|
||||||
|
const now = new Date();
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
|
const startOfYesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
||||||
|
|
||||||
|
let dayString;
|
||||||
|
if (date >= startOfToday) {
|
||||||
|
dayString = 'Hari ini';
|
||||||
|
} else if (date >= startOfYesterday) {
|
||||||
|
dayString = 'Kemarin';
|
||||||
|
} else {
|
||||||
|
dayString = date.toLocaleDateString('id-ID', { day: 'numeric', month: 'long' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeString = date
|
||||||
|
.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', hour12: false })
|
||||||
|
.replace('.', ':');
|
||||||
|
return `${dayString}, ${timeString}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const paging = {
|
||||||
|
page: 1,
|
||||||
|
limit: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = new URLSearchParams({ ...paging, ...formDataFilter });
|
||||||
|
|
||||||
|
// Fetch both schedules and shifts data
|
||||||
|
const [schedulesResponse, shiftsResponse] = await Promise.all([
|
||||||
|
getAllJadwalShift(params),
|
||||||
|
getAllShift(params),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Handle nested data structure from backend
|
||||||
|
const rawData = schedulesResponse?.data || schedulesResponse || [];
|
||||||
|
const shifts = shiftsResponse?.data || shiftsResponse || [];
|
||||||
|
|
||||||
|
setShiftOptions(shifts);
|
||||||
|
|
||||||
|
// Parse backend response structure: [{ shift: { shift_id, shift_name, users: [...] } }]
|
||||||
|
const grouped = {};
|
||||||
|
const allUsers = [];
|
||||||
|
|
||||||
|
rawData.forEach((item) => {
|
||||||
|
if (item.shift && item.shift.shift_name) {
|
||||||
|
const shift = item.shift;
|
||||||
|
const shiftName = shift.shift_name.toUpperCase().trim();
|
||||||
|
|
||||||
|
// Initialize shift group
|
||||||
|
if (!grouped[shiftName]) {
|
||||||
|
grouped[shiftName] = {
|
||||||
|
shift_id: shift.shift_id,
|
||||||
|
users: [],
|
||||||
|
lastUpdate: { user: 'N/A', timestamp: '1970-01-01T00:00:00Z' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process users in this shift
|
||||||
|
if (shift.users && Array.isArray(shift.users)) {
|
||||||
|
shift.users.forEach((user) => {
|
||||||
|
const normalizedUser = {
|
||||||
|
id: user.user_schedule_id,
|
||||||
|
user_schedule_id: user.user_schedule_id,
|
||||||
|
user_id: user.user_id,
|
||||||
|
shift_id: shift.shift_id,
|
||||||
|
shift_name: shift.shift_name,
|
||||||
|
nama_employee: user.user_fullname || user.user_name || 'Unknown',
|
||||||
|
whatsapp: user.user_phone || '-',
|
||||||
|
user_fullname: user.user_fullname,
|
||||||
|
user_name: user.user_name,
|
||||||
|
user_phone: user.user_phone,
|
||||||
|
updated_at: user.updated_at,
|
||||||
|
created_at: user.created_at,
|
||||||
|
updated_by: user.updated_by,
|
||||||
|
};
|
||||||
|
|
||||||
|
grouped[shiftName].users.push(normalizedUser);
|
||||||
|
allUsers.push(normalizedUser);
|
||||||
|
|
||||||
|
// Update last update timestamp
|
||||||
|
const currentUpdate = new Date(
|
||||||
|
user.updated_at || user.created_at || new Date()
|
||||||
|
);
|
||||||
|
const lastUpdate = new Date(grouped[shiftName].lastUpdate.timestamp);
|
||||||
|
if (currentUpdate > lastUpdate) {
|
||||||
|
grouped[shiftName].lastUpdate = {
|
||||||
|
user: user.updated_by || 'N/A',
|
||||||
|
timestamp: currentUpdate.toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setEmployeeOptions(allUsers);
|
||||||
|
|
||||||
|
// Add empty shifts that don't have users yet
|
||||||
|
shifts.forEach((shift) => {
|
||||||
|
const shiftName = shift.shift_name.toUpperCase().trim();
|
||||||
|
if (!grouped[shiftName]) {
|
||||||
|
grouped[shiftName] = {
|
||||||
|
shift_id: shift.shift_id,
|
||||||
|
users: [],
|
||||||
|
lastUpdate: { user: 'N/A', timestamp: new Date().toISOString() },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setGroupedSchedules(grouped);
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal Memuat Data',
|
||||||
|
message: 'Terjadi kesalahan saat memuat data jadwal shift.',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
if (props.actionMode === 'list') {
|
||||||
|
setFormDataFilter(defaultFilter);
|
||||||
|
doFilter();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigate('/signin');
|
||||||
|
}
|
||||||
|
}, [props.actionMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.actionMode === 'list') {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}, [trigerFilter]);
|
||||||
|
|
||||||
|
const doFilter = () => {
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setFormDataFilter({ criteria: searchValue });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchClear = () => {
|
||||||
|
setSearchValue('');
|
||||||
|
setFormDataFilter({ criteria: '' });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showPreviewModal = (param) => {
|
||||||
|
props.setSelectedData(param);
|
||||||
|
props.setActionMode('preview');
|
||||||
|
};
|
||||||
|
|
||||||
|
const showEditModal = (param = null) => {
|
||||||
|
props.setSelectedData(param);
|
||||||
|
props.setActionMode('edit');
|
||||||
|
};
|
||||||
|
|
||||||
|
const showAddModal = (param = null) => {
|
||||||
|
props.setSelectedData(param);
|
||||||
|
props.setActionMode('add');
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDeleteDialog = (param) => {
|
||||||
|
NotifConfirmDialog({
|
||||||
|
icon: 'question',
|
||||||
|
title: 'Konfirmasi Hapus',
|
||||||
|
message: `Jadwal untuk karyawan "${param.nama_employee}" akan dihapus?`,
|
||||||
|
onConfirm: () => handleDelete(param.id),
|
||||||
|
onCancel: () => props.setSelectedData(null),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
const response = await deleteJadwalShift(id);
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: 'Jadwal berhasil dihapus.',
|
||||||
|
});
|
||||||
|
doFilter();
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: response?.message || 'Gagal menghapus jadwal.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShiftEditMode = (shiftName) => {
|
||||||
|
setEditingShift(shiftName);
|
||||||
|
setPendingChanges({}); // Clear pending changes
|
||||||
|
setSelectedSchedules([]); // Clear selections when entering a new edit mode
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelShiftEditMode = () => {
|
||||||
|
setEditingShift(null);
|
||||||
|
setPendingChanges({});
|
||||||
|
setSelectedSchedules([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectSchedule = (id, isChecked) => {
|
||||||
|
if (isChecked) {
|
||||||
|
setSelectedSchedules((prev) => [...prev, id]);
|
||||||
|
} else {
|
||||||
|
setSelectedSchedules((prev) => prev.filter((scheduleId) => scheduleId !== id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBulkUpdateChange = (scheduleId, field, value) => {
|
||||||
|
setPendingChanges((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[scheduleId]: {
|
||||||
|
...prev[scheduleId],
|
||||||
|
[field]: value,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBulkSave = async () => {
|
||||||
|
if (Object.keys(pendingChanges).length === 0) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'info',
|
||||||
|
title: 'Tidak Ada Perubahan',
|
||||||
|
message: 'Tidak ada perubahan untuk disimpan.',
|
||||||
|
});
|
||||||
|
cancelShiftEditMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePromises = Object.keys(pendingChanges).map((id) => {
|
||||||
|
const originalSchedule = groupedSchedules[editingShift].users.find(
|
||||||
|
(u) => u.id.toString() === id
|
||||||
|
);
|
||||||
|
const changes = pendingChanges[id];
|
||||||
|
|
||||||
|
// Build payload according to backend schema
|
||||||
|
const payload = {
|
||||||
|
user_id: changes.user_id || originalSchedule.user_id,
|
||||||
|
shift_id: changes.shift_id || originalSchedule.shift_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (originalSchedule.schedule_id) {
|
||||||
|
payload.schedule_id = originalSchedule.schedule_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateJadwalShift(id, payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(updatePromises);
|
||||||
|
NotifOk({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: 'Semua perubahan berhasil disimpan.',
|
||||||
|
});
|
||||||
|
doFilter();
|
||||||
|
cancelShiftEditMode();
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: 'Gagal menyimpan beberapa perubahan.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBulkDelete = () => {
|
||||||
|
if (selectedSchedules.length === 0) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Perhatian',
|
||||||
|
message: 'Pilih setidaknya satu jadwal untuk dihapus.',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NotifConfirmDialog({
|
||||||
|
icon: 'question',
|
||||||
|
title: `Konfirmasi Hapus`,
|
||||||
|
message: `Anda yakin ingin menghapus ${selectedSchedules.length} jadwal yang dipilih?`,
|
||||||
|
onConfirm: async () => {
|
||||||
|
await Promise.all(selectedSchedules.map((id) => deleteJadwalShift(id)));
|
||||||
|
NotifOk({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: `${selectedSchedules.length} jadwal berhasil dihapus.`,
|
||||||
|
});
|
||||||
|
doFilter();
|
||||||
|
setEditingShift(null);
|
||||||
|
setSelectedSchedules([]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Card>
|
||||||
|
<Title level={3}>Jadwal Shift</Title>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{/* <Row>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Row justify="end" align="middle" gutter={[8, 8]}>
|
||||||
|
<Col xs={24} sm={24} md={12} lg={12}>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Cari berdasarkan nama..."
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setSearchValue(value);
|
||||||
|
if (value === '') {
|
||||||
|
handleSearchClear();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
allowClear
|
||||||
|
enterButton={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row> */}
|
||||||
|
|
||||||
|
<div style={{ marginTop: '24px' }}>
|
||||||
|
{loading ? (
|
||||||
|
<Text>Memuat data...</Text>
|
||||||
|
) : Object.keys(groupedSchedules).length === 0 ? (
|
||||||
|
<Text>Tidak ada data jadwal untuk ditampilkan.</Text>
|
||||||
|
) : (
|
||||||
|
['SHIFT PAGI', 'SHIFT SORE', 'SHIFT MALAM']
|
||||||
|
.filter((shiftName) => groupedSchedules[shiftName])
|
||||||
|
.map((shiftName) => (
|
||||||
|
<div key={shiftName} style={{ marginBottom: '32px' }}>
|
||||||
|
{' '}
|
||||||
|
{/* Container for each shift section */}
|
||||||
|
<Row
|
||||||
|
justify="space-between"
|
||||||
|
align="middle"
|
||||||
|
style={{
|
||||||
|
paddingBottom: '12px',
|
||||||
|
borderBottom: '1px solid #f0f0f0',
|
||||||
|
marginBottom: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Col>
|
||||||
|
<Title level={5} style={{ margin: 0 }}>
|
||||||
|
{shiftName} (
|
||||||
|
{groupedSchedules[shiftName].users.length} Karyawan)
|
||||||
|
</Title>
|
||||||
|
</Col>
|
||||||
|
{editingShift === shiftName ? (
|
||||||
|
<Col>
|
||||||
|
<Space wrap>
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
onClick={cancelShiftEditMode}
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
key="delete"
|
||||||
|
type="primary"
|
||||||
|
danger
|
||||||
|
onClick={handleBulkDelete}
|
||||||
|
disabled={selectedSchedules.length === 0}
|
||||||
|
>
|
||||||
|
Hapus Dipilih ({selectedSchedules.length})
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
key="save"
|
||||||
|
type="primary"
|
||||||
|
onClick={handleBulkSave}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Simpan Perubahan
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
) : (
|
||||||
|
<Col>
|
||||||
|
<Space wrap>
|
||||||
|
<Button
|
||||||
|
key="add"
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => showAddModal()}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
disabled={editingShift !== null}
|
||||||
|
>
|
||||||
|
Tambah Jadwal Shift
|
||||||
|
</Button>
|
||||||
|
<ConfigProvider
|
||||||
|
key="edit-config"
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Button: {
|
||||||
|
defaultBg: 'white',
|
||||||
|
defaultColor: '#23A55A',
|
||||||
|
defaultBorderColor: '#23A55A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => handleShiftEditMode(shiftName)}
|
||||||
|
disabled={editingShift !== null}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</ConfigProvider>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
{/* Horizontal scrollable container for employee cards */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
overflowX: 'auto',
|
||||||
|
gap: '16px',
|
||||||
|
paddingTop: '8px',
|
||||||
|
paddingBottom: '10px',
|
||||||
|
minWidth: `${4 * (320 + 16)}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{groupedSchedules[shiftName].users.length > 0 ? (
|
||||||
|
groupedSchedules[shiftName].users.map((user) => (
|
||||||
|
<Card
|
||||||
|
key={user.id}
|
||||||
|
hoverable
|
||||||
|
style={{
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
flexShrink: 0,
|
||||||
|
textAlign: 'left',
|
||||||
|
border: '1px solid #42AAFF',
|
||||||
|
opacity:
|
||||||
|
editingShift !== null &&
|
||||||
|
editingShift !== shiftName
|
||||||
|
? 0.5
|
||||||
|
: 1,
|
||||||
|
pointerEvents:
|
||||||
|
editingShift !== null &&
|
||||||
|
editingShift !== shiftName
|
||||||
|
? 'none'
|
||||||
|
: 'auto',
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px', height: '100%' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{editingShift === shiftName ? (
|
||||||
|
// EDIT MODE VIEW
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
height: '100%',
|
||||||
|
gap: '12px',
|
||||||
|
padding: '16px 4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
paddingTop: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedSchedules.includes(
|
||||||
|
user.id
|
||||||
|
)}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleSelectSchedule(
|
||||||
|
user.id,
|
||||||
|
e.target.checked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
transform: 'scale(1.4)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '14px',
|
||||||
|
paddingRight: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: '#8c8c8c',
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '6px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
KARYAWAN
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="Pilih Karyawan"
|
||||||
|
optionFilterProp="children"
|
||||||
|
defaultValue={user.user_id}
|
||||||
|
onChange={(value) =>
|
||||||
|
handleBulkUpdateChange(
|
||||||
|
user.id,
|
||||||
|
'user_id',
|
||||||
|
value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{employeeOptions.map(
|
||||||
|
(emp) => (
|
||||||
|
<Select.Option
|
||||||
|
key={
|
||||||
|
emp.user_id ||
|
||||||
|
emp.id
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
emp.user_id ||
|
||||||
|
emp.id
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{emp.user_fullname ||
|
||||||
|
emp.user_name ||
|
||||||
|
emp.nama_employee}
|
||||||
|
</Select.Option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: '#8c8c8c',
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '6px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
NO. TELEPON
|
||||||
|
</Text>
|
||||||
|
<Input
|
||||||
|
value={
|
||||||
|
pendingChanges[user.id]
|
||||||
|
?.user_id
|
||||||
|
? employeeOptions.find(
|
||||||
|
(emp) =>
|
||||||
|
emp.user_id ===
|
||||||
|
pendingChanges[
|
||||||
|
user
|
||||||
|
.id
|
||||||
|
]?.user_id
|
||||||
|
)?.user_phone ||
|
||||||
|
user.whatsapp
|
||||||
|
: user.whatsapp
|
||||||
|
}
|
||||||
|
readOnly
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
'#f5f5f5',
|
||||||
|
color: '#595959',
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
}}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
color: '#8c8c8c',
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '6px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
SHIFT
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="Pilih Shift"
|
||||||
|
optionFilterProp="children"
|
||||||
|
defaultValue={user.shift_id}
|
||||||
|
onChange={(value) =>
|
||||||
|
handleBulkUpdateChange(
|
||||||
|
user.id,
|
||||||
|
'shift_id',
|
||||||
|
value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{shiftOptions.map(
|
||||||
|
(shift) => (
|
||||||
|
<Select.Option
|
||||||
|
key={
|
||||||
|
shift.shift_id
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
shift.shift_id
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
shift.shift_name
|
||||||
|
}
|
||||||
|
</Select.Option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// NORMAL VIEW
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#42AAFF',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
padding: '9px 12px',
|
||||||
|
borderRadius: '15px',
|
||||||
|
marginBottom: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user.nama_employee}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user.whatsapp}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
display: 'block',
|
||||||
|
lineHeight: '1.4',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text strong>
|
||||||
|
Terakhir diperbarui
|
||||||
|
</Text>{' '}
|
||||||
|
<br />
|
||||||
|
{formatRelativeTimestamp(
|
||||||
|
user.updated_at ||
|
||||||
|
user.created_at ||
|
||||||
|
new Date()
|
||||||
|
)}{' '}
|
||||||
|
<br />
|
||||||
|
oleh {user.updated_by || 'N/A'}
|
||||||
|
</Text>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() =>
|
||||||
|
showPreviewModal(user)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
color: '#1890ff',
|
||||||
|
borderColor: '#1890ff',
|
||||||
|
}}
|
||||||
|
title="View"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() =>
|
||||||
|
showEditModal(user)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
color: '#faad14',
|
||||||
|
borderColor: '#faad14',
|
||||||
|
}}
|
||||||
|
title="Edit"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() =>
|
||||||
|
showDeleteDialog(user)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
borderColor: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
title="Delete"
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Text type="secondary" style={{ marginLeft: '16px' }}>
|
||||||
|
Tidak ada karyawan yang dijadwalkan untuk shift ini.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ListJadwalShift;
|
||||||
151
src/pages/master/brand/ErrorCode.jsx
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { Card, Typography, Button, Modal, Form, Input, message } from 'antd';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import TableList from '../../../components/Global/TableList';
|
||||||
|
// import { getAllErrorCodesByBrand, createErrorCode, updateErrorCode, deleteErrorCode } from '../../api/master-errorcode'; // Mock this later
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
// Mock API functions for now
|
||||||
|
const mockApi = {
|
||||||
|
errorCodes: [
|
||||||
|
{ error_code_id: 1, brand_id: 1, error_code: 'E-001', description: 'Paper Jam' },
|
||||||
|
{ error_code_id: 2, brand_id: 1, error_code: 'E-002', description: 'Low Ink' },
|
||||||
|
],
|
||||||
|
getAllErrorCodesByBrand: async (brandId) => {
|
||||||
|
return { status: 200, data: { data: mockApi.errorCodes.filter(ec => ec.brand_id == brandId) } };
|
||||||
|
},
|
||||||
|
createErrorCode: async (data) => {
|
||||||
|
const newId = Math.max(...mockApi.errorCodes.map(ec => ec.error_code_id)) + 1;
|
||||||
|
const newErrorCode = { ...data, error_code_id: newId };
|
||||||
|
mockApi.errorCodes.push(newErrorCode);
|
||||||
|
return { statusCode: 201, data: newErrorCode };
|
||||||
|
},
|
||||||
|
updateErrorCode: async (id, data) => {
|
||||||
|
const index = mockApi.errorCodes.findIndex(ec => ec.error_code_id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
mockApi.errorCodes[index] = { ...mockApi.errorCodes[index], ...data };
|
||||||
|
return { statusCode: 200, data: mockApi.errorCodes[index] };
|
||||||
|
}
|
||||||
|
return { statusCode: 404, message: 'Not Found' };
|
||||||
|
},
|
||||||
|
deleteErrorCode: async (id) => {
|
||||||
|
const index = mockApi.errorCodes.findIndex(ec => ec.error_code_id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
mockApi.errorCodes.splice(index, 1);
|
||||||
|
return { statusCode: 200 };
|
||||||
|
}
|
||||||
|
return { statusCode: 404, message: 'Not Found' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ErrorCodePage = () => {
|
||||||
|
const { brandId } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const [errorCodes, setErrorCodes] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [editingErrorCode, setEditingErrorCode] = useState(null);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await mockApi.getAllErrorCodesByBrand(brandId);
|
||||||
|
if (response.status === 200) {
|
||||||
|
setErrorCodes(response.data.data);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [brandId]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: 'Error Code', dataIndex: 'error_code', key: 'error_code' },
|
||||||
|
{ title: 'Description', dataIndex: 'description', key: 'description' },
|
||||||
|
{
|
||||||
|
title: 'Action',
|
||||||
|
key: 'action',
|
||||||
|
render: (_, record) => (
|
||||||
|
<>
|
||||||
|
<Button type="link" onClick={() => handleEdit(record)}>Edit</Button>
|
||||||
|
<Button type="link" danger onClick={() => handleDelete(record.error_code_id)}>Delete</Button>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setEditingErrorCode(null);
|
||||||
|
form.resetFields();
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (errorCode) => {
|
||||||
|
setEditingErrorCode(errorCode);
|
||||||
|
form.setFieldsValue(errorCode);
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
await mockApi.deleteErrorCode(id);
|
||||||
|
message.success('Error code deleted successfully');
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalOk = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
if (editingErrorCode) {
|
||||||
|
await mockApi.updateErrorCode(editingErrorCode.error_code_id, values);
|
||||||
|
message.success('Error code updated successfully');
|
||||||
|
} else {
|
||||||
|
await mockApi.createErrorCode({ ...values, brand_id: brandId });
|
||||||
|
message.success('Error code created successfully');
|
||||||
|
}
|
||||||
|
setIsModalVisible(false);
|
||||||
|
fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Validate Failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Title level={4}>Manage Error Codes for Brand ID: {brandId}</Title>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAdd}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
Add Error Code
|
||||||
|
</Button>
|
||||||
|
<TableList
|
||||||
|
columns={columns}
|
||||||
|
getData={async () => ({ data: { data: errorCodes } })}
|
||||||
|
triger={brandId}
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
title={editingErrorCode ? 'Edit Error Code' : 'Add Error Code'}
|
||||||
|
visible={isModalVisible}
|
||||||
|
onOk={handleModalOk}
|
||||||
|
onCancel={() => setIsModalVisible(false)}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item name="error_code" label="Error Code" rules={[{ required: true }]}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="description" label="Description" rules={[{ required: true }]}>
|
||||||
|
<Input.TextArea />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorCodePage;
|
||||||
59
src/pages/master/brand/FormBrand.jsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Form, Input, Button, Typography, Card, message } from 'antd';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { createBrand } from '../../api/master-brand';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
const FormBrand = () => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const onFinish = async (values) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await createBrand(values);
|
||||||
|
if (response.statusCode === 200 || response.statusCode === 201) {
|
||||||
|
message.success('Brand created successfully!');
|
||||||
|
const newBrandId = response.data.brand_id;
|
||||||
|
// Redirect to the error code page for the new brand
|
||||||
|
navigate(`/master/brand/${newBrandId}/error-codes`);
|
||||||
|
} else {
|
||||||
|
message.error(response.message || 'Failed to create brand.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('An error occurred while creating the brand.');
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Title level={4}>Add New Brand</Title>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={onFinish}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="brand_name"
|
||||||
|
label="Brand Name"
|
||||||
|
rules={[{ required: true, message: 'Please input the brand name!' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter brand name" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" loading={loading}>
|
||||||
|
Lanjut ke Error Code
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormBrand;
|
||||||
1104
src/pages/master/brandDevice/AddBrandDevice.jsx
Normal file
1014
src/pages/master/brandDevice/EditBrandDevice.jsx
Normal file
32
src/pages/master/brandDevice/IndexBrandDevice.jsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React, { memo, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import ListBrandDevice from './component/ListBrandDevice';
|
||||||
|
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const IndexBrandDevice = memo(function IndexBrandDevice() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
setBreadcrumbItems([
|
||||||
|
{ title: <Text strong style={{ fontSize: '14px' }}>• Master</Text> },
|
||||||
|
{ title: <Text strong style={{ fontSize: '14px' }}>Brand Device</Text> }
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
navigate('/signin');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ListBrandDevice />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IndexBrandDevice;
|
||||||
766
src/pages/master/brandDevice/ViewBrandDevice.jsx
Normal file
@@ -0,0 +1,766 @@
|
|||||||
|
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||||
|
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Divider,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
Steps,
|
||||||
|
Form,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Card,
|
||||||
|
Spin,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
ConfigProvider,
|
||||||
|
Empty
|
||||||
|
} from 'antd';
|
||||||
|
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||||
|
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||||
|
import { NotifAlert } from '../../../components/Global/ToastNotif';
|
||||||
|
import { getBrandById, getErrorCodesByBrandId } from '../../../api/master-brand';
|
||||||
|
import { getFileUrl, getFolderFromFileType } from '../../../api/file-uploads';
|
||||||
|
import { SendRequest } from '../../../components/Global/ApiRequest';
|
||||||
|
import ListErrorCode from './component/ListErrorCode';
|
||||||
|
import BrandForm from './component/BrandForm';
|
||||||
|
import ErrorCodeForm from './component/ErrorCodeForm';
|
||||||
|
import SolutionForm from './component/SolutionForm';
|
||||||
|
import SparepartSelect from './component/SparepartSelect';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
const { Step } = Steps;
|
||||||
|
|
||||||
|
const ViewBrandDevice = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { id } = useParams();
|
||||||
|
const location = useLocation();
|
||||||
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
const [brandForm] = Form.useForm();
|
||||||
|
const [errorCodeForm] = Form.useForm();
|
||||||
|
const [solutionForm] = Form.useForm();
|
||||||
|
|
||||||
|
const [brandData, setBrandData] = useState(null);
|
||||||
|
const [errorCodes, setErrorCodes] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
const [selectedErrorCode, setSelectedErrorCode] = useState(null);
|
||||||
|
const [selectedSparepartIds, setSelectedSparepartIds] = useState([]);
|
||||||
|
const [errorCodeIcon, setErrorCodeIcon] = useState(null);
|
||||||
|
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||||
|
const [searchText, setSearchText] = useState('');
|
||||||
|
|
||||||
|
|
||||||
|
const [solutionFields, setSolutionFields] = useState([0]);
|
||||||
|
const [solutionTypes, setSolutionTypes] = useState({ 0: 'text' });
|
||||||
|
const [solutionStatuses, setSolutionStatuses] = useState({ 0: true });
|
||||||
|
const [currentSolutionData, setCurrentSolutionData] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
|
const [brandInfo, setBrandInfo] = useState({});
|
||||||
|
|
||||||
|
const resetSolutionFields = () => {
|
||||||
|
if (solutionForm && solutionForm.resetFields) {
|
||||||
|
solutionForm.resetFields();
|
||||||
|
solutionForm.setFieldsValue({
|
||||||
|
solution_items: {
|
||||||
|
0: {
|
||||||
|
name: '',
|
||||||
|
type: 'text',
|
||||||
|
text: '',
|
||||||
|
status: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setCurrentSolutionData([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSolutionData = () => {
|
||||||
|
if (!solutionForm) return [];
|
||||||
|
try {
|
||||||
|
const values = solutionForm.getFieldsValue(true);
|
||||||
|
|
||||||
|
let solutions = [];
|
||||||
|
|
||||||
|
if (values.solution_items) {
|
||||||
|
if (Array.isArray(values.solution_items)) {
|
||||||
|
solutions = values.solution_items.filter(Boolean);
|
||||||
|
} else if (typeof values.solution_items === 'object') {
|
||||||
|
solutions = Object.values(values.solution_items).filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return solutions;
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const savedPhase = location.state?.phase || localStorage.getItem(`brand_device_${id}_last_phase`);
|
||||||
|
if (savedPhase) {
|
||||||
|
setCurrentStep(parseInt(savedPhase));
|
||||||
|
localStorage.removeItem(`brand_device_${id}_last_phase`);
|
||||||
|
}
|
||||||
|
}, [location.state, id]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setBreadcrumbItems([
|
||||||
|
{
|
||||||
|
title: <span style={{ fontSize: '14px', fontWeight: 'bold' }}>• Master</span>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<span
|
||||||
|
style={{ fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}
|
||||||
|
onClick={() => navigate('/master/brand-device')}
|
||||||
|
>
|
||||||
|
Brand Device
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<span style={{ fontSize: '14px', fontWeight: 'bold' }}>
|
||||||
|
View Brand Device
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [setBreadcrumbItems, navigate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchBrandData = async () => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
navigate('/signin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await getBrandById(id);
|
||||||
|
|
||||||
|
if (response && response.statusCode === 200) {
|
||||||
|
const brandData = response.data;
|
||||||
|
|
||||||
|
const brandInfoData = {
|
||||||
|
brand_code: brandData.brand_code,
|
||||||
|
brand_name: brandData.brand_name,
|
||||||
|
brand_type: brandData.brand_type || '',
|
||||||
|
brand_manufacture: brandData.brand_manufacture || '',
|
||||||
|
brand_model: brandData.brand_model || '',
|
||||||
|
is_active: brandData.is_active
|
||||||
|
};
|
||||||
|
|
||||||
|
setBrandInfo(brandInfoData);
|
||||||
|
setBrandData(brandData);
|
||||||
|
brandForm.setFieldsValue(brandInfoData);
|
||||||
|
|
||||||
|
if (brandData.brand_id) {
|
||||||
|
try {
|
||||||
|
const errorCodesResponse = await getErrorCodesByBrandId(id || brandData.brand_id);
|
||||||
|
if (errorCodesResponse && errorCodesResponse.statusCode === 200) {
|
||||||
|
const apiErrorData = errorCodesResponse.data || [];
|
||||||
|
const existingCodes = apiErrorData.map(ec => ({
|
||||||
|
...ec,
|
||||||
|
tempId: `existing_${ec.error_code_id}`,
|
||||||
|
status: 'existing',
|
||||||
|
solution: ec.solution || [],
|
||||||
|
spareparts: ec.spareparts || []
|
||||||
|
}));
|
||||||
|
setErrorCodes(existingCodes);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: response?.message || 'Failed to fetch brand device data',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: error.message || 'Failed to fetch brand device data',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchBrandData();
|
||||||
|
}, [id, navigate, brandForm]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentStep === 1 && id) {
|
||||||
|
setTrigerFilter(prev => !prev);
|
||||||
|
}
|
||||||
|
}, [currentStep, id]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentStep === 1 && errorCodes.length > 0 && !selectedErrorCode) {
|
||||||
|
handleErrorCodeSelect(errorCodes[0]);
|
||||||
|
}
|
||||||
|
}, [currentStep, errorCodes]);
|
||||||
|
|
||||||
|
const setSolutionsForExistingRecord = (solutions, targetForm) => {
|
||||||
|
|
||||||
|
if (!targetForm || !solutions || solutions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetForm.resetFields();
|
||||||
|
|
||||||
|
const solutionItems = {};
|
||||||
|
const newSolutionFields = [];
|
||||||
|
const newSolutionTypes = {};
|
||||||
|
const newSolutionStatuses = {};
|
||||||
|
|
||||||
|
solutions.forEach((solution, index) => {
|
||||||
|
const fieldKey = index;
|
||||||
|
newSolutionFields.push(fieldKey);
|
||||||
|
|
||||||
|
const isFileType = solution.type_solution && solution.type_solution !== 'text';
|
||||||
|
newSolutionTypes[fieldKey] = isFileType ? 'file' : 'text';
|
||||||
|
newSolutionStatuses[fieldKey] = solution.is_active;
|
||||||
|
|
||||||
|
let fileObject = null;
|
||||||
|
if (isFileType && (solution.path_solution || solution.path_document)) {
|
||||||
|
fileObject = {
|
||||||
|
uploadPath: solution.path_solution || solution.path_document,
|
||||||
|
path_solution: solution.path_solution || solution.path_document,
|
||||||
|
name: solution.file_upload_name || (solution.path_solution || solution.path_document).split('/').pop() || 'File',
|
||||||
|
type_solution: solution.type_solution,
|
||||||
|
isExisting: true,
|
||||||
|
size: 0,
|
||||||
|
url: solution.path_solution || solution.path_document
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
solutionItems[fieldKey] = {
|
||||||
|
brand_code_solution_id: solution.brand_code_solution_id,
|
||||||
|
name: solution.solution_name || '',
|
||||||
|
type: isFileType ? 'file' : 'text',
|
||||||
|
text: solution.text_solution || '',
|
||||||
|
status: solution.is_active,
|
||||||
|
file: fileObject,
|
||||||
|
fileUpload: fileObject,
|
||||||
|
path_solution: solution.path_solution || solution.path_document || null,
|
||||||
|
fileName: solution.file_upload_name || null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setSolutionFields(newSolutionFields);
|
||||||
|
setSolutionTypes(newSolutionTypes);
|
||||||
|
setSolutionStatuses(newSolutionStatuses);
|
||||||
|
|
||||||
|
targetForm.resetFields();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
targetForm.setFieldsValue({
|
||||||
|
solution_items: solutionItems
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
Object.keys(solutionItems).forEach(key => {
|
||||||
|
const solution = solutionItems[key];
|
||||||
|
targetForm.setFieldValue(['solution_items', key, 'name'], solution.name);
|
||||||
|
targetForm.setFieldValue(['solution_items', key, 'type'], solution.type);
|
||||||
|
targetForm.setFieldValue(['solution_items', key, 'text'], solution.text);
|
||||||
|
targetForm.setFieldValue(['solution_items', key, 'file'], solution.file);
|
||||||
|
targetForm.setFieldValue(['solution_items', key, 'fileUpload'], solution.fileUpload);
|
||||||
|
targetForm.setFieldValue(['solution_items', key, 'status'], solution.status);
|
||||||
|
targetForm.setFieldValue(['solution_items', key, 'path_solution'], solution.path_solution);
|
||||||
|
targetForm.setFieldValue(['solution_items', key, 'fileName'], solution.fileName);
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalValues = targetForm.getFieldsValue();
|
||||||
|
}, 100);
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleErrorCodeSelect = async (errorCode) => {
|
||||||
|
|
||||||
|
setSelectedErrorCode(errorCode);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const directResponse = await SendRequest({
|
||||||
|
method: 'get',
|
||||||
|
prefix: `error-code/${errorCode.error_code_id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiResponse = directResponse.data;
|
||||||
|
|
||||||
|
if (apiResponse && apiResponse.statusCode === 200 && apiResponse.data) {
|
||||||
|
const fullErrorCodeData = {
|
||||||
|
...apiResponse.data,
|
||||||
|
tempId: `existing_${apiResponse.data.error_code_id}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const formValues = {
|
||||||
|
error_code: fullErrorCodeData.error_code,
|
||||||
|
error_code_name: fullErrorCodeData.error_code_name,
|
||||||
|
error_code_description: fullErrorCodeData.error_code_description || '',
|
||||||
|
error_code_color: fullErrorCodeData.error_code_color && fullErrorCodeData.error_code_color !== '' ? fullErrorCodeData.error_code_color : '#000000',
|
||||||
|
status: fullErrorCodeData.is_active,
|
||||||
|
};
|
||||||
|
|
||||||
|
errorCodeForm.setFieldsValue(formValues);
|
||||||
|
|
||||||
|
if (fullErrorCodeData.path_icon && fullErrorCodeData.path_icon !== '') {
|
||||||
|
const iconData = {
|
||||||
|
name: fullErrorCodeData.path_icon.split('/').pop(),
|
||||||
|
uploadPath: fullErrorCodeData.path_icon,
|
||||||
|
};
|
||||||
|
setErrorCodeIcon(iconData);
|
||||||
|
} else {
|
||||||
|
setErrorCodeIcon(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResponse.data.solution && apiResponse.data.solution.length > 0) {
|
||||||
|
setCurrentSolutionData(apiResponse.data.solution);
|
||||||
|
setSolutionsForExistingRecord(apiResponse.data.solution, solutionForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResponse.data.spareparts && apiResponse.data.spareparts.length > 0) {
|
||||||
|
setSelectedSparepartIds(apiResponse.data.spareparts.map(sp => sp.sparepart_id));
|
||||||
|
} else {
|
||||||
|
setSelectedSparepartIds([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const basicErrorCodeData = {
|
||||||
|
...errorCode,
|
||||||
|
tempId: `existing_${errorCode.error_code_id}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const formValues = {
|
||||||
|
error_code: basicErrorCodeData.error_code,
|
||||||
|
error_code_name: basicErrorCodeData.error_code_name,
|
||||||
|
error_code_description: basicErrorCodeData.error_code_description || '',
|
||||||
|
error_code_color: basicErrorCodeData.error_code_color && basicErrorCodeData.error_code_color !== '' ? basicErrorCodeData.error_code_color : '#000000',
|
||||||
|
status: basicErrorCodeData.is_active,
|
||||||
|
};
|
||||||
|
|
||||||
|
errorCodeForm.setFieldsValue(formValues);
|
||||||
|
|
||||||
|
if (basicErrorCodeData.path_icon && basicErrorCodeData.path_icon !== '') {
|
||||||
|
const iconData = {
|
||||||
|
name: basicErrorCodeData.path_icon.split('/').pop(),
|
||||||
|
uploadPath: basicErrorCodeData.path_icon,
|
||||||
|
};
|
||||||
|
setErrorCodeIcon(iconData);
|
||||||
|
} else {
|
||||||
|
setErrorCodeIcon(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSolutionFields();
|
||||||
|
setSelectedSparepartIds([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const basicErrorCodeData = {
|
||||||
|
...errorCode,
|
||||||
|
tempId: `existing_${errorCode.error_code_id}`
|
||||||
|
};
|
||||||
|
|
||||||
|
const formValues = {
|
||||||
|
error_code: basicErrorCodeData.error_code,
|
||||||
|
error_code_name: basicErrorCodeData.error_code_name,
|
||||||
|
error_code_description: basicErrorCodeData.error_code_description || '',
|
||||||
|
error_code_color: basicErrorCodeData.error_code_color && basicErrorCodeData.error_code_color !== '' ? basicErrorCodeData.error_code_color : '#000000',
|
||||||
|
status: basicErrorCodeData.is_active,
|
||||||
|
};
|
||||||
|
|
||||||
|
errorCodeForm.setFieldsValue(formValues);
|
||||||
|
|
||||||
|
if (basicErrorCodeData.path_icon && basicErrorCodeData.path_icon !== '') {
|
||||||
|
const iconData = {
|
||||||
|
name: basicErrorCodeData.path_icon.split('/').pop(),
|
||||||
|
uploadPath: basicErrorCodeData.path_icon,
|
||||||
|
};
|
||||||
|
setErrorCodeIcon(iconData);
|
||||||
|
} else {
|
||||||
|
setErrorCodeIcon(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSolutionFields();
|
||||||
|
setSelectedSparepartIds([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBrandFormValuesChange = useCallback((changedValues, allValues) => {
|
||||||
|
setBrandInfo(allValues);
|
||||||
|
}, [setBrandInfo]);
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchClear = () => {
|
||||||
|
setSearchText('');
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileView = (fileName) => {
|
||||||
|
try {
|
||||||
|
let fileUrl = '';
|
||||||
|
let actualFileName = '';
|
||||||
|
|
||||||
|
const filePath = fileName || '';
|
||||||
|
if (filePath) {
|
||||||
|
actualFileName = filePath.split('/').pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actualFileName) {
|
||||||
|
const fileExtension = actualFileName.split('.').pop()?.toLowerCase();
|
||||||
|
const folder = getFolderFromFileType(fileExtension);
|
||||||
|
fileUrl = getFileUrl(folder, actualFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileUrl && filePath) {
|
||||||
|
fileUrl = filePath.startsWith('http') ? filePath : `${import.meta.env.VITE_API_SERVER}/${filePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileUrl && actualFileName) {
|
||||||
|
const fileExtension = actualFileName.split('.').pop()?.toLowerCase();
|
||||||
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
||||||
|
const pdfExtensions = ['pdf'];
|
||||||
|
|
||||||
|
if (imageExtensions.includes(fileExtension) || pdfExtensions.includes(fileExtension)) {
|
||||||
|
const viewerUrl = `/image-viewer/${encodeURIComponent(actualFileName)}`;
|
||||||
|
window.open(viewerUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
} else {
|
||||||
|
window.open(fileUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'File URL not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Failed to open file preview'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNextStep = () => {
|
||||||
|
setCurrentStep(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderStepContent = () => {
|
||||||
|
if (currentStep === 0) {
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
{loading && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 10,
|
||||||
|
borderRadius: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin size="large" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<BrandForm
|
||||||
|
form={brandForm}
|
||||||
|
onValuesChange={handleBrandFormValuesChange}
|
||||||
|
isEdit={false}
|
||||||
|
readOnly={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStep === 1) {
|
||||||
|
return (
|
||||||
|
<Row gutter={[16, 8]} style={{ minHeight: '70vh' }}>
|
||||||
|
<Col xs={24} md={8} lg={8}>
|
||||||
|
<ListErrorCode
|
||||||
|
brandId={id}
|
||||||
|
selectedErrorCode={selectedErrorCode}
|
||||||
|
onErrorCodeSelect={handleErrorCodeSelect}
|
||||||
|
tempErrorCodes={[]}
|
||||||
|
trigerFilter={trigerFilter}
|
||||||
|
searchText={searchText}
|
||||||
|
onSearchChange={(value) => {
|
||||||
|
setSearchText(value);
|
||||||
|
if (value === '') {
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
onSearchClear={handleSearchClear}
|
||||||
|
isReadOnly={true}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs={24} md={16} lg={16}>
|
||||||
|
<div style={{
|
||||||
|
paddingLeft: '12px'
|
||||||
|
}}>
|
||||||
|
{selectedErrorCode ? (
|
||||||
|
<Card
|
||||||
|
title={
|
||||||
|
<span style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#262626',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px'
|
||||||
|
}}>
|
||||||
|
<span style={{
|
||||||
|
width: '4px',
|
||||||
|
height: '20px',
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderRadius: '2px'
|
||||||
|
}}></span>
|
||||||
|
Error Code Form
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
|
||||||
|
borderRadius: '12px'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px 24px 12px 24px' },
|
||||||
|
header: {
|
||||||
|
padding: '16px 24px',
|
||||||
|
borderBottom: '1px solid #f0f0f0',
|
||||||
|
backgroundColor: '#fafafa'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||||
|
<div style={{
|
||||||
|
padding: '16px',
|
||||||
|
border: '1px solid #f0f0f0',
|
||||||
|
borderRadius: '10px',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
marginBottom: '0',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
marginBottom: '12px',
|
||||||
|
paddingBottom: '8px',
|
||||||
|
borderBottom: '1px solid #f5f5f5'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: '3px',
|
||||||
|
height: '16px',
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderRadius: '2px'
|
||||||
|
}}></div>
|
||||||
|
<h4 style={{ margin: 0, color: '#262626', fontSize: '14px', fontWeight: '600' }}>
|
||||||
|
Error Code Details
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<ErrorCodeForm
|
||||||
|
errorCodeForm={errorCodeForm}
|
||||||
|
isErrorCodeFormReadOnly={true}
|
||||||
|
errorCodeIcon={errorCodeIcon}
|
||||||
|
onErrorCodeIconUpload={() => { }}
|
||||||
|
onErrorCodeIconRemove={() => { }}
|
||||||
|
isEdit={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Row gutter={[20, 0]} style={{ marginTop: '0' }}>
|
||||||
|
<Col xs={24} md={12} lg={12}>
|
||||||
|
<div style={{
|
||||||
|
padding: '16px',
|
||||||
|
border: '1px solid #f0f0f0',
|
||||||
|
borderRadius: '10px',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
marginBottom: '12px',
|
||||||
|
paddingBottom: '8px',
|
||||||
|
borderBottom: '1px solid #f5f5f5'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: '3px',
|
||||||
|
height: '16px',
|
||||||
|
backgroundColor: '#1890ff',
|
||||||
|
borderRadius: '2px'
|
||||||
|
}}></div>
|
||||||
|
<h4 style={{ margin: 0, color: '#262626', fontSize: '14px', fontWeight: '600' }}>
|
||||||
|
Solution
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<SolutionForm
|
||||||
|
solutionForm={solutionForm}
|
||||||
|
solutionFields={solutionFields}
|
||||||
|
solutionTypes={solutionTypes}
|
||||||
|
solutionStatuses={solutionStatuses}
|
||||||
|
onAddSolutionField={() => { }}
|
||||||
|
onRemoveSolutionField={() => { }}
|
||||||
|
onSolutionTypeChange={() => { }}
|
||||||
|
onSolutionStatusChange={() => { }}
|
||||||
|
onSolutionFileUpload={() => { }}
|
||||||
|
onFileView={(fileData) => {
|
||||||
|
if (fileData && (fileData.url || fileData.uploadPath)) {
|
||||||
|
window.open(fileData.url || fileData.uploadPath, '_blank');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
isReadOnly={true}
|
||||||
|
solutionData={currentSolutionData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12} lg={12}>
|
||||||
|
<div style={{
|
||||||
|
padding: '16px',
|
||||||
|
border: '1px solid #f0f0f0',
|
||||||
|
borderRadius: '10px',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
marginBottom: '12px',
|
||||||
|
paddingBottom: '8px',
|
||||||
|
borderBottom: '1px solid #f5f5f5'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: '3px',
|
||||||
|
height: '16px',
|
||||||
|
backgroundColor: '#faad14',
|
||||||
|
borderRadius: '2px'
|
||||||
|
}}></div>
|
||||||
|
<h4 style={{ margin: 0, color: '#262626', fontSize: '14px', fontWeight: '600' }}>
|
||||||
|
Sparepart Selection
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
maxHeight: '45vh',
|
||||||
|
overflow: 'auto',
|
||||||
|
border: '1px solid #e8e8e8',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
backgroundColor: '#fafafa'
|
||||||
|
}}>
|
||||||
|
<SparepartSelect
|
||||||
|
selectedSparepartIds={selectedSparepartIds}
|
||||||
|
onSparepartChange={() => { }}
|
||||||
|
isReadOnly={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
height: '100%', display: 'flex', flexDirection: 'column',
|
||||||
|
justifyContent: 'center', alignItems: 'center',
|
||||||
|
backgroundColor: '#ffffff', borderRadius: '12px',
|
||||||
|
border: '1px dashed #d9d9d9', color: '#8c8c8c', padding: '48px'
|
||||||
|
}}>
|
||||||
|
<Empty description="Select an error code to view details" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Switch: {
|
||||||
|
colorPrimary: '#23A55A',
|
||||||
|
colorPrimaryHover: '#23A55A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<Steps current={currentStep} style={{ marginBottom: 24 }}>
|
||||||
|
<Step title="Brand Device Details" />
|
||||||
|
<Step title="Error Codes & Solutions" />
|
||||||
|
</Steps>
|
||||||
|
{renderStepContent()}
|
||||||
|
<Divider />
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<div>
|
||||||
|
{currentStep === 1 && (
|
||||||
|
<Button
|
||||||
|
onClick={() => setCurrentStep(0)}
|
||||||
|
>
|
||||||
|
Back to Brand Info
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{currentStep === 0 && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleNextStep}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Error Code
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{currentStep === 1 && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => navigate('/master/brand-device')}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Selesai
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewBrandDevice;
|
||||||
461
src/pages/master/brandDevice/ViewFilePage.jsx
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { Card, Button, Typography, Spin, Alert, Space } from 'antd';
|
||||||
|
import { NotifAlert } from '../../../components/Global/ToastNotif';
|
||||||
|
import { ArrowLeftOutlined, FilePdfOutlined, FileImageOutlined, DownloadOutlined } from '@ant-design/icons';
|
||||||
|
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||||
|
import { getBrandById } from '../../../api/master-brand';
|
||||||
|
import {
|
||||||
|
downloadFile,
|
||||||
|
getFile,
|
||||||
|
getFileUrl,
|
||||||
|
getFolderFromFileType,
|
||||||
|
} from '../../../api/file-uploads';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
const ViewFilePage = () => {
|
||||||
|
const params = useParams();
|
||||||
|
const { id, fileType, fileName } = params;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [brandData, setBrandData] = useState(null);
|
||||||
|
const [actualFileName, setActualFileName] = useState('');
|
||||||
|
const [pdfBlobUrl, setPdfBlobUrl] = useState(null);
|
||||||
|
const [pdfLoading, setPdfLoading] = useState(false);
|
||||||
|
|
||||||
|
const isFromEdit = window.location.pathname.includes('/edit/');
|
||||||
|
|
||||||
|
let fallbackId = id;
|
||||||
|
let fallbackFileType = fileType;
|
||||||
|
let fallbackFileName = fileName;
|
||||||
|
|
||||||
|
if (!fileName || !fileType || !id) {
|
||||||
|
|
||||||
|
const urlParts = window.location.pathname.split('/');
|
||||||
|
|
||||||
|
const viewIndex = urlParts.indexOf('view');
|
||||||
|
const editIndex = urlParts.indexOf('edit');
|
||||||
|
const actionIndex = viewIndex !== -1 ? viewIndex : editIndex;
|
||||||
|
|
||||||
|
if (actionIndex !== -1 && urlParts.length > actionIndex + 4) {
|
||||||
|
fallbackId = urlParts[actionIndex + 1];
|
||||||
|
fallbackFileType = urlParts[actionIndex + 3];
|
||||||
|
fallbackFileName = decodeURIComponent(urlParts[actionIndex + 4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPdfBlobUrl(null);
|
||||||
|
setPdfLoading(false);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
navigate('/signin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const actualId = fallbackId || id;
|
||||||
|
const actualFileName = fallbackFileName || fileName;
|
||||||
|
|
||||||
|
const brandResponse = await getBrandById(actualId);
|
||||||
|
if (brandResponse && brandResponse.statusCode === 200) {
|
||||||
|
setBrandData(brandResponse.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedFileName = decodeURIComponent(actualFileName);
|
||||||
|
setActualFileName(decodedFileName);
|
||||||
|
|
||||||
|
const fileExtension = decodedFileName.split('.').pop().toLowerCase();
|
||||||
|
if (fileExtension === 'pdf') {
|
||||||
|
setPdfLoading(true);
|
||||||
|
const folder = getFolderFromFileType('pdf');
|
||||||
|
try {
|
||||||
|
const blobData = await getFile(folder, decodedFileName);
|
||||||
|
const blobUrl = window.URL.createObjectURL(blobData);
|
||||||
|
setPdfBlobUrl(blobUrl);
|
||||||
|
} catch (pdfError) {
|
||||||
|
setError('Failed to load PDF file: ' + (pdfError.message || pdfError));
|
||||||
|
setPdfBlobUrl(null);
|
||||||
|
} finally {
|
||||||
|
setPdfLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setError('Failed to load data');
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (pdfBlobUrl) {
|
||||||
|
window.URL.revokeObjectURL(pdfBlobUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [id, fileName, fileType, navigate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (brandData) {
|
||||||
|
const breadcrumbItems = [
|
||||||
|
{ title: <strong style={{ fontSize: '14px' }}>• Master</strong> },
|
||||||
|
{
|
||||||
|
title: <strong style={{ fontSize: '14px' }} onClick={() => navigate('/master/brand-device')}>Brand Device</strong>
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isFromEdit) {
|
||||||
|
breadcrumbItems.push({
|
||||||
|
title: <strong style={{ fontSize: '14px' }} onClick={() => navigate(`/master/brand-device/edit/${fallbackId || id}`)}>Edit Brand Device</strong>
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
breadcrumbItems.push({
|
||||||
|
title: <strong style={{ fontSize: '14px' }} onClick={() => navigate(`/master/brand-device/view/${fallbackId || id}`)}>View Brand Device</strong>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
breadcrumbItems.push({ title: <strong style={{ fontSize: '14px' }}>View Document</strong> });
|
||||||
|
|
||||||
|
setBreadcrumbItems(breadcrumbItems);
|
||||||
|
}
|
||||||
|
}, [brandData, id, isFromEdit, fallbackId, navigate, setBreadcrumbItems]);
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
if (isFromEdit) {
|
||||||
|
const savedPhase = localStorage.getItem(`brand_device_edit_${fallbackId || id}_last_phase`);
|
||||||
|
|
||||||
|
if (savedPhase) {
|
||||||
|
localStorage.removeItem(`brand_device_edit_${fallbackId || id}_last_phase`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPhase = savedPhase ? parseInt(savedPhase) : 1;
|
||||||
|
|
||||||
|
navigate(`/master/brand-device/edit/${fallbackId || id}`, {
|
||||||
|
state: { phase: targetPhase, fromFileViewer: true },
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigate(`/master/brand-device/view/${fallbackId || id}`, {
|
||||||
|
state: { phase: 1 },
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
message="Error Loading File"
|
||||||
|
description={error}
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
style={{ margin: '20px 0' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayFileName = actualFileName || 'Loading...';
|
||||||
|
const fileExtension = displayFileName.split('.').pop().toLowerCase();
|
||||||
|
const isImage = ['jpg', 'jpeg', 'png', 'gif'].includes(fileExtension);
|
||||||
|
const isPdf = fileExtension === 'pdf';
|
||||||
|
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
|
{isImage ? (
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '300px',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#999'
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<FileImageOutlined style={{ fontSize: '48px', marginBottom: '16px' }} />
|
||||||
|
<div>Loading image...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : isPdf ? (
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '400px',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#999'
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<FilePdfOutlined style={{ fontSize: '48px', marginBottom: '16px' }} />
|
||||||
|
<div>Loading PDF...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '200px',
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#999'
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<FilePdfOutlined style={{ fontSize: '48px', marginBottom: '16px' }} />
|
||||||
|
<div>Loading file...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isImage) {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center', padding: '20px' }}>
|
||||||
|
<img
|
||||||
|
src={getFileUrl(getFolderFromFileType(fallbackFileType || fileType), actualFileName)}
|
||||||
|
alt={actualFileName}
|
||||||
|
style={{
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '70vh',
|
||||||
|
objectFit: 'contain',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: '8px'
|
||||||
|
}}
|
||||||
|
onError={() => setError('Failed to load image')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPdf) {
|
||||||
|
const displayUrl = pdfBlobUrl || getFileUrl(getFolderFromFileType(fallbackFileType || fileType), actualFileName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: '75vh', width: '100%', border: '1px solid #d9d9d9', borderRadius: '8px', overflow: 'hidden' }}>
|
||||||
|
{pdfBlobUrl ? (
|
||||||
|
<iframe
|
||||||
|
src={pdfBlobUrl}
|
||||||
|
title={actualFileName}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px'
|
||||||
|
}}
|
||||||
|
onError={() => {
|
||||||
|
setError('Failed to load PDF. Please try downloading the file.');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : pdfLoading ? (
|
||||||
|
<div style={{
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '16px',
|
||||||
|
backgroundColor: '#f5f5f5'
|
||||||
|
}}>
|
||||||
|
<Spin size="large" />
|
||||||
|
<div style={{ fontSize: '16px', color: '#666', textAlign: 'center' }}>
|
||||||
|
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>Memuat PDF...</div>
|
||||||
|
<div>Silakan tunggu sebentar</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '16px',
|
||||||
|
backgroundColor: '#f5f5f5'
|
||||||
|
}}>
|
||||||
|
<FilePdfOutlined style={{ fontSize: '48px', color: '#ff4d4f' }} />
|
||||||
|
<div style={{ fontSize: '16px', color: '#666', textAlign: 'center' }}>
|
||||||
|
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>PDF tidak dapat dimuat</div>
|
||||||
|
<div>Silakan download file untuk melihat kontennya</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: '12px' }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
const folder = getFolderFromFileType(fallbackFileType || fileType);
|
||||||
|
downloadFile(folder, actualFileName);
|
||||||
|
}}
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
>
|
||||||
|
Download PDF
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setPdfLoading(true);
|
||||||
|
const folder = getFolderFromFileType('pdf');
|
||||||
|
getFile(folder, actualFileName)
|
||||||
|
.then(blobData => {
|
||||||
|
const blobUrl = window.URL.createObjectURL(blobData);
|
||||||
|
setPdfBlobUrl(blobUrl);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
setError('Failed to load PDF file: ' + (error.message || error));
|
||||||
|
setPdfBlobUrl(null);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setPdfLoading(false);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Coba Lagi
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center', padding: '50px' }}>
|
||||||
|
<FilePdfOutlined style={{ fontSize: '48px', color: '#ff4d4f', marginBottom: '16px' }} />
|
||||||
|
<div style={{ fontSize: '16px', marginBottom: '8px' }}>Preview tidak tersedia untuk jenis file ini</div>
|
||||||
|
<div style={{ color: '#666', marginBottom: '16px' }}>{actualFileName}</div>
|
||||||
|
<div style={{ marginTop: '16px' }}>
|
||||||
|
<Button type="primary" href={getFileUrl(getFolderFromFileType(fallbackFileType || fileType), actualFileName)} target="_blank" rel="noopener noreferrer">
|
||||||
|
Buka di Tab Baru
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileIcon = () => {
|
||||||
|
const displayFileName = actualFileName || 'Loading...';
|
||||||
|
const fileExtension = displayFileName.split('.').pop().toLowerCase();
|
||||||
|
const isImage = ['jpg', 'jpeg', 'png', 'gif'].includes(fileExtension);
|
||||||
|
const isPdf = fileExtension === 'pdf';
|
||||||
|
|
||||||
|
if (isImage) return <FileImageOutlined style={{ color: '#1890ff', fontSize: '20px' }} />;
|
||||||
|
if (isPdf) return <FilePdfOutlined style={{ color: '#ff4d4f', fontSize: '20px' }} />;
|
||||||
|
return <FilePdfOutlined style={{ color: '#ff4d4f', fontSize: '20px' }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileTypeColor = () => {
|
||||||
|
const displayFileName = actualFileName || 'Loading...';
|
||||||
|
const fileExtension = displayFileName.split('.').pop().toLowerCase();
|
||||||
|
const isImage = ['jpg', 'jpeg', 'png', 'gif'].includes(fileExtension);
|
||||||
|
const isPdf = fileExtension === 'pdf';
|
||||||
|
|
||||||
|
if (isImage) return '#1890ff';
|
||||||
|
if (isPdf) return '#ff4d4f';
|
||||||
|
return '#ff4d4f';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '24px', minHeight: '100vh', backgroundColor: '#f5f5f5' }}>
|
||||||
|
<Card>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
{getFileIcon()}
|
||||||
|
<div>
|
||||||
|
<Title level={4} style={{ margin: 0 }}>
|
||||||
|
{actualFileName || 'Loading...'}
|
||||||
|
</Title>
|
||||||
|
{brandData ? (
|
||||||
|
<div style={{ color: '#666', fontSize: '14px' }}>
|
||||||
|
Brand: {brandData.brand_name} | ID: {brandData.brand_id}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ color: '#666', fontSize: '14px' }}>
|
||||||
|
Loading brand information...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={<ArrowLeftOutlined />}
|
||||||
|
onClick={handleBack}
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
const folder = getFolderFromFileType(fallbackFileType || fileType);
|
||||||
|
downloadFile(folder, actualFileName);
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Download File
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<div style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '4px 12px',
|
||||||
|
backgroundColor: getFileTypeColor() + '15',
|
||||||
|
border: `1px solid ${getFileTypeColor()}30`,
|
||||||
|
borderRadius: '16px',
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: getFileTypeColor()
|
||||||
|
}}>
|
||||||
|
{(fallbackFileType || fileType || 'FILE')?.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
backdropFilter: 'blur(0.8px)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 5,
|
||||||
|
borderRadius: '8px'
|
||||||
|
}}>
|
||||||
|
<Spin size="large" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ filter: loading ? 'blur(0.5px)' : 'none', transition: 'filter 0.3s ease' }}>
|
||||||
|
{renderContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewFilePage;
|
||||||
99
src/pages/master/brandDevice/component/BrandForm.jsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form, Input, Row, Col, Typography, Switch } from 'antd';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const BrandForm = ({
|
||||||
|
form,
|
||||||
|
onValuesChange,
|
||||||
|
isEdit = false,
|
||||||
|
brandInfo = null,
|
||||||
|
readOnly = false,
|
||||||
|
}) => {
|
||||||
|
const isActive = Form.useWatch('is_active', form) ?? true;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (brandInfo && brandInfo.brand_code) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
brand_code: brandInfo.brand_code
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [brandInfo, form]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
form={form}
|
||||||
|
onValuesChange={onValuesChange}
|
||||||
|
initialValues={{
|
||||||
|
brand_name: '',
|
||||||
|
brand_type: '',
|
||||||
|
brand_model: '',
|
||||||
|
brand_manufacture: '',
|
||||||
|
is_active: true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item label="Status">
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Form.Item name="is_active" valuePropName="checked" noStyle>
|
||||||
|
<Switch
|
||||||
|
style={{ backgroundColor: isActive ? '#23A55A' : '#bfbfbf' }}
|
||||||
|
disabled={readOnly}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Text style={{ marginLeft: 8 }}>
|
||||||
|
{isActive ? 'Running' : 'Offline'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="Brand Code" name="brand_code">
|
||||||
|
<Input
|
||||||
|
disabled={true}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
cursor: 'not-allowed'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Brand Name"
|
||||||
|
name="brand_name"
|
||||||
|
rules={[{ required: !readOnly, message: 'Brand Name wajib diisi!' }]}
|
||||||
|
>
|
||||||
|
<Input disabled={readOnly} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Manufacturer"
|
||||||
|
name="brand_manufacture"
|
||||||
|
rules={[{ required: !readOnly, message: 'Manufacturer wajib diisi!' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter Manufacturer" disabled={readOnly} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item label="Brand Type" name="brand_type">
|
||||||
|
<Input placeholder="Enter Brand Type (Optional)" disabled={readOnly} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item label="Model" name="brand_model">
|
||||||
|
<Input placeholder="Enter Model (Optional)" disabled={readOnly} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BrandForm;
|
||||||
397
src/pages/master/brandDevice/component/CustomSparepartCard.jsx
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Card, Typography, Tag, Button, Modal, Row, Col, Space } from 'antd';
|
||||||
|
import { EyeOutlined, DeleteOutlined, CheckOutlined } from '@ant-design/icons';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
|
const CustomSparepartCard = ({
|
||||||
|
sparepart,
|
||||||
|
isSelected = false,
|
||||||
|
isReadOnly = false,
|
||||||
|
showPreview = true,
|
||||||
|
showDelete = false,
|
||||||
|
onPreview,
|
||||||
|
onDelete,
|
||||||
|
onCardClick,
|
||||||
|
loading = false,
|
||||||
|
size = 'small',
|
||||||
|
style = {},
|
||||||
|
}) => {
|
||||||
|
const [previewModalVisible, setPreviewModalVisible] = useState(false);
|
||||||
|
|
||||||
|
const getImageSrc = () => {
|
||||||
|
if (sparepart.sparepart_foto) {
|
||||||
|
if (sparepart.sparepart_foto.startsWith('http')) {
|
||||||
|
return sparepart.sparepart_foto;
|
||||||
|
} else {
|
||||||
|
const fileName = sparepart.sparepart_foto.split('/').pop();
|
||||||
|
if (fileName === 'defaultSparepartImg.jpg') {
|
||||||
|
return `/assets/defaultSparepartImg.jpg`;
|
||||||
|
} else {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
const baseURL = import.meta.env.VITE_API_SERVER || '';
|
||||||
|
return `${baseURL}/file-uploads/images/${encodeURIComponent(fileName)}${token ? `?token=${encodeURIComponent(token)}` : ''}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'https://via.placeholder.com/150';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePreview = () => {
|
||||||
|
if (onPreview) {
|
||||||
|
onPreview(sparepart);
|
||||||
|
} else {
|
||||||
|
setPreviewModalVisible(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const truncateText = (text, maxLength = 15) => {
|
||||||
|
if (!text) return 'Unnamed';
|
||||||
|
return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCardClick = () => {
|
||||||
|
if (!isReadOnly && onCardClick) {
|
||||||
|
onCardClick(sparepart);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCardActions = () => {
|
||||||
|
const actions = [];
|
||||||
|
|
||||||
|
if (showPreview) {
|
||||||
|
actions.push(
|
||||||
|
<Button
|
||||||
|
key="preview"
|
||||||
|
type="text"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
title="Lihat Detail"
|
||||||
|
style={{ color: '#1890ff' }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePreview();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDelete && !isReadOnly) {
|
||||||
|
actions.push(
|
||||||
|
<Button
|
||||||
|
key="delete"
|
||||||
|
type="text"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
title="Hapus"
|
||||||
|
style={{ color: '#ff4d4f' }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete?.(sparepart);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCardStyle = () => {
|
||||||
|
const baseStyle = {
|
||||||
|
borderRadius: '12px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: isSelected ? '2px solid #1890ff' : '1px solid #E0E0E0',
|
||||||
|
cursor: isReadOnly ? 'default' : 'pointer',
|
||||||
|
position: 'relative',
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (size) {
|
||||||
|
case 'small':
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
height: '180px',
|
||||||
|
minHeight: '180px'
|
||||||
|
};
|
||||||
|
case 'large':
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
height: '280px',
|
||||||
|
minHeight: '280px'
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
...baseStyle,
|
||||||
|
height: '220px',
|
||||||
|
minHeight: '220px'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid #f0f0f0',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '12px 16px',
|
||||||
|
marginBottom: '8px',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
cursor: onCardClick && !isReadOnly ? 'pointer' : 'default',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
}}
|
||||||
|
onClick={handleCardClick}
|
||||||
|
>
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '4px' }}>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#262626',
|
||||||
|
marginRight: '12px'
|
||||||
|
}}
|
||||||
|
title={sparepart.sparepart_name || sparepart.name || 'Unnamed'}
|
||||||
|
>
|
||||||
|
{truncateText(sparepart.sparepart_name || sparepart.name || 'Unnamed')}
|
||||||
|
</Text>
|
||||||
|
<Tag
|
||||||
|
color={sparepart.sparepart_stok === 'Available' ? 'green' : 'red'}
|
||||||
|
style={{ fontSize: '11px', margin: 0 }}
|
||||||
|
>
|
||||||
|
{sparepart.sparepart_stok || 'Not Available'}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Text style={{ fontSize: '12px', color: '#666', marginRight: '4px' }}>
|
||||||
|
qty:
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: '#262626'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sparepart.sparepart_qty || 0}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Space size="small">
|
||||||
|
{showPreview && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
size="small"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlePreview();
|
||||||
|
}}
|
||||||
|
title="Preview"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showDelete && !isReadOnly && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
size="small"
|
||||||
|
danger
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete?.(sparepart);
|
||||||
|
}}
|
||||||
|
title="Remove"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="Sparepart Details"
|
||||||
|
open={previewModalVisible}
|
||||||
|
onCancel={() => setPreviewModalVisible(false)}
|
||||||
|
footer={[
|
||||||
|
<Button key="close" onClick={() => setPreviewModalVisible(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
width={800}
|
||||||
|
centered
|
||||||
|
styles={{ body: { padding: '24px' } }}
|
||||||
|
>
|
||||||
|
<Row gutter={[24, 24]}>
|
||||||
|
<Col span={10}>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#f0f0f0',
|
||||||
|
width: '220px',
|
||||||
|
height: '220px',
|
||||||
|
margin: '0 auto 16px',
|
||||||
|
position: 'relative',
|
||||||
|
borderRadius: '12px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: '1px solid #E0E0E0',
|
||||||
|
boxShadow: '0 4px 12px rgba(0,0,0,0.1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={getImageSrc()}
|
||||||
|
alt={sparepart.sparepart_name || 'Sparepart'}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'cover'
|
||||||
|
}}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.src = 'https://via.placeholder.com/220x220/d9d9d9/666666?text=No+Image';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{sparepart.sparepart_item_type && (
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<Tag color="blue" style={{ fontSize: '14px', padding: '4px 12px' }}>
|
||||||
|
{sparepart.sparepart_item_type}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
textAlign: 'left',
|
||||||
|
background: '#f8f9fa',
|
||||||
|
padding: '12px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
marginTop: '25px'
|
||||||
|
}}>
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<Text strong style={{ fontSize: '14px', color: '#262626' }}>Stock Status:</Text>
|
||||||
|
<Tag
|
||||||
|
color={sparepart.sparepart_stok === 'Available' ? 'green' : 'red'}
|
||||||
|
style={{ marginLeft: '8px', fontSize: '12px' }}
|
||||||
|
>
|
||||||
|
{sparepart.sparepart_stok || 'Not Available'}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text strong style={{ fontSize: '14px', color: '#262626' }}>Quantity:</Text>
|
||||||
|
<Text style={{ fontSize: '14px', marginLeft: '8px', fontWeight: 600 }}>
|
||||||
|
{sparepart.sparepart_qty || 0} {sparepart.sparepart_unit || ''}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={14}>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Title level={3} style={{ marginBottom: '20px', color: '#262626' }}>
|
||||||
|
{sparepart.sparepart_name || 'Unnamed'}
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '24px' }}>
|
||||||
|
<Row gutter={[16, 12]}>
|
||||||
|
<Col span={24}>
|
||||||
|
<div style={{
|
||||||
|
padding: '12px',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
}}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={8}>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>Code</Text>
|
||||||
|
<div style={{ fontSize: '15px', fontWeight: 500, marginTop: '2px' }}>
|
||||||
|
{sparepart.sparepart_code || 'N/A'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>Brand</Text>
|
||||||
|
<div style={{ fontSize: '15px', fontWeight: 500, marginTop: '2px' }}>
|
||||||
|
{sparepart.sparepart_merk || '-'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>Unit</Text>
|
||||||
|
<div style={{ fontSize: '15px', fontWeight: 500, marginTop: '2px' }}>
|
||||||
|
{sparepart.sparepart_unit || '-'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
{sparepart.sparepart_model && (
|
||||||
|
<Col span={24}>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>Model</Text>
|
||||||
|
<div style={{ fontSize: '15px', fontWeight: 500, marginTop: '2px' }}>
|
||||||
|
{sparepart.sparepart_model}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{sparepart.sparepart_description && (
|
||||||
|
<Col span={24}>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>Description</Text>
|
||||||
|
<div style={{ fontSize: '15px', marginTop: '2px', lineHeight: '1.5' }}>
|
||||||
|
{sparepart.sparepart_description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{sparepart.created_at && (
|
||||||
|
<div>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>Created</Text>
|
||||||
|
<div style={{ fontSize: '13px', marginTop: '2px' }}>
|
||||||
|
{dayjs(sparepart.created_at).format('DD MMM YYYY, HH:mm')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>Last Updated</Text>
|
||||||
|
<div style={{ fontSize: '13px', marginTop: '2px' }}>
|
||||||
|
{dayjs(sparepart.updated_at).format('DD MMM YYYY, HH:mm')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomSparepartCard;
|
||||||
288
src/pages/master/brandDevice/component/ErrorCodeForm.jsx
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Form, Input, Switch, Typography, ConfigProvider, Card, Button } from 'antd';
|
||||||
|
import { FileOutlined, EyeOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import FileUploadHandler from './FileUploadHandler';
|
||||||
|
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
||||||
|
import { getFileUrl, getFolderFromFileType } from '../../../../api/file-uploads';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const ErrorCodeForm = ({
|
||||||
|
errorCodeForm,
|
||||||
|
isErrorCodeFormReadOnly = false,
|
||||||
|
errorCodeIcon,
|
||||||
|
onErrorCodeIconUpload,
|
||||||
|
onErrorCodeIconRemove,
|
||||||
|
isEdit = false,
|
||||||
|
}) => {
|
||||||
|
const [currentIcon, setCurrentIcon] = useState(null);
|
||||||
|
const statusWatch = Form.useWatch('status', errorCodeForm) ?? true;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (errorCodeIcon && typeof errorCodeIcon === 'object' && Object.keys(errorCodeIcon).length > 0) {
|
||||||
|
setCurrentIcon(errorCodeIcon);
|
||||||
|
} else {
|
||||||
|
setCurrentIcon(null);
|
||||||
|
}
|
||||||
|
}, [errorCodeIcon]);
|
||||||
|
|
||||||
|
const handleIconRemove = () => {
|
||||||
|
setCurrentIcon(null);
|
||||||
|
onErrorCodeIconRemove();
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderIconUpload = () => {
|
||||||
|
if (currentIcon) {
|
||||||
|
const displayFileName = currentIcon.name || currentIcon.uploadPath?.split('/').pop() || currentIcon.url?.split('/').pop() || 'Icon File';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
marginTop: 8,
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||||
|
border: '1px solid #e8e8e8'
|
||||||
|
}}
|
||||||
|
styles={{ body: { padding: '16px' } }}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: '#f0f5ff',
|
||||||
|
flexShrink: 0
|
||||||
|
}}>
|
||||||
|
<FileOutlined style={{ fontSize: 24, color: '#1890ff' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: '#262626',
|
||||||
|
marginBottom: 4,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}}>
|
||||||
|
{displayFileName}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#8c8c8c' }}>
|
||||||
|
{currentIcon.size ? `${(currentIcon.size / 1024).toFixed(1)} KB` : 'Icon uploaded'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="middle"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
try {
|
||||||
|
let iconUrl = '';
|
||||||
|
let actualFileName = '';
|
||||||
|
|
||||||
|
const filePath = currentIcon.uploadPath || currentIcon.url || currentIcon.path || '';
|
||||||
|
const iconDisplayName = currentIcon.name || '';
|
||||||
|
|
||||||
|
if (iconDisplayName) {
|
||||||
|
actualFileName = iconDisplayName;
|
||||||
|
} else if (filePath) {
|
||||||
|
actualFileName = filePath.split('/').pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actualFileName) {
|
||||||
|
const fileExtension = actualFileName.split('.').pop()?.toLowerCase();
|
||||||
|
const folder = getFolderFromFileType(fileExtension);
|
||||||
|
iconUrl = getFileUrl(folder, actualFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!iconUrl && filePath) {
|
||||||
|
iconUrl = filePath.startsWith('http') ? filePath : `${import.meta.env.VITE_API_SERVER}/${filePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iconUrl && actualFileName) {
|
||||||
|
const fileExtension = actualFileName.split('.').pop()?.toLowerCase();
|
||||||
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
||||||
|
const pdfExtensions = ['pdf'];
|
||||||
|
|
||||||
|
if (imageExtensions.includes(fileExtension) || pdfExtensions.includes(fileExtension)) {
|
||||||
|
const viewerUrl = `/image-viewer/${encodeURIComponent(actualFileName)}`;
|
||||||
|
window.open(viewerUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
} else {
|
||||||
|
window.open(iconUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: `File URL not found. FileName: ${actualFileName}, FilePath: ${filePath}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: `Failed to open file preview: ${error.message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
size="middle"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
onClick={handleIconRemove}
|
||||||
|
disabled={isErrorCodeFormReadOnly}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<FileUploadHandler
|
||||||
|
type="error_code"
|
||||||
|
existingFile={null}
|
||||||
|
accept="image/*"
|
||||||
|
onFileUpload={(fileData) => {
|
||||||
|
setCurrentIcon(fileData);
|
||||||
|
onErrorCodeIconUpload(fileData);
|
||||||
|
}}
|
||||||
|
onFileRemove={handleIconRemove}
|
||||||
|
buttonText="Upload Icon"
|
||||||
|
buttonStyle={{
|
||||||
|
width: '100%',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
color: '#23A55A',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '8px'
|
||||||
|
}}
|
||||||
|
uploadText="Upload error code icon"
|
||||||
|
disabled={isErrorCodeFormReadOnly}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Switch: {
|
||||||
|
colorPrimary: '#23A55A',
|
||||||
|
colorPrimaryHover: '#23A55A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={errorCodeForm}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{
|
||||||
|
status: true,
|
||||||
|
error_code_color: '#000000'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header bar with color picker, icon upload, and status toggle */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
marginBottom: '16px',
|
||||||
|
gap: '16px'
|
||||||
|
}}>
|
||||||
|
{/* Color picker on left */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Form.Item
|
||||||
|
name="error_code_color"
|
||||||
|
noStyle
|
||||||
|
getValueFromEvent={(e) => e.target.value}
|
||||||
|
getValueProps={(value) => ({ value: value || '#000000' })}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
style={{
|
||||||
|
width: '120px',
|
||||||
|
height: '40px',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: 4,
|
||||||
|
cursor: isErrorCodeFormReadOnly ? 'not-allowed' : 'pointer',
|
||||||
|
}}
|
||||||
|
disabled={isErrorCodeFormReadOnly}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{/* Icon upload beside color picker */}
|
||||||
|
<div style={{ flex: 1, maxWidth: '300px' }}>
|
||||||
|
{renderIconUpload()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status toggle on right */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Form.Item name="status" valuePropName="checked" noStyle>
|
||||||
|
<Switch
|
||||||
|
disabled={isErrorCodeFormReadOnly}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Text style={{ marginLeft: 8 }}>
|
||||||
|
{statusWatch ? 'Active' : 'Inactive'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error Code and Error Name in one row with 1/3 and 2/3 ratio */}
|
||||||
|
<div style={{ display: 'flex', gap: '12px', marginBottom: '16px' }}>
|
||||||
|
<Form.Item
|
||||||
|
label="Error Code"
|
||||||
|
name="error_code"
|
||||||
|
rules={[{ required: true, message: 'Error code wajib diisi!' }]}
|
||||||
|
style={{ flex: 1, marginBottom: 0, maxWidth: '33.33%' }}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Enter error code"
|
||||||
|
disabled={isErrorCodeFormReadOnly}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Error Name"
|
||||||
|
name="error_code_name"
|
||||||
|
rules={[{ required: !isErrorCodeFormReadOnly, message: 'Error name wajib diisi!' }]}
|
||||||
|
style={{ flex: 2, marginBottom: 0, maxWidth: '66.67%' }}
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter error name" disabled={isErrorCodeFormReadOnly} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form.Item label="Description" name="error_code_description">
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder="Enter error description"
|
||||||
|
rows={3}
|
||||||
|
disabled={isErrorCodeFormReadOnly}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorCodeForm;
|
||||||
421
src/pages/master/brandDevice/component/FileUploadHandler.jsx
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Upload, Modal, Button, Typography, Space, Image } from 'antd';
|
||||||
|
import { UploadOutlined, EyeOutlined, DeleteOutlined, FileOutlined } from '@ant-design/icons';
|
||||||
|
import { NotifOk, NotifAlert } from '../../../../components/Global/ToastNotif';
|
||||||
|
import { uploadFile, getFolderFromFileType, getFileUrl, getFileType } from '../../../../api/file-uploads';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const FileUploadHandler = ({
|
||||||
|
type = 'solution',
|
||||||
|
maxCount = 1,
|
||||||
|
accept = '.pdf,.jpg,.jpeg,.png,.gif',
|
||||||
|
disabled = false,
|
||||||
|
|
||||||
|
fileList = [],
|
||||||
|
onFileUpload,
|
||||||
|
onFileRemove,
|
||||||
|
|
||||||
|
existingFile = null,
|
||||||
|
clearSignal = null,
|
||||||
|
debugProps = {},
|
||||||
|
|
||||||
|
uploadText = 'Click or drag file to this area to upload',
|
||||||
|
uploadHint = 'Support for PDF and image files only',
|
||||||
|
buttonText = 'Upload File',
|
||||||
|
buttonType = 'default',
|
||||||
|
|
||||||
|
containerStyle = {},
|
||||||
|
buttonStyle = {},
|
||||||
|
showPreview = true
|
||||||
|
}) => {
|
||||||
|
const [previewOpen, setPreviewOpen] = useState(false);
|
||||||
|
const [previewImage, setPreviewImage] = useState('');
|
||||||
|
const [previewTitle, setPreviewTitle] = useState('');
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
const [uploadedFile, setUploadedFile] = useState(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (clearSignal !== null && clearSignal > 0) {
|
||||||
|
setUploadedFile(null);
|
||||||
|
}
|
||||||
|
}, [clearSignal, debugProps]);
|
||||||
|
|
||||||
|
const getBase64 = (file) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = (error) => reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePreview = async (file) => {
|
||||||
|
if (!file.url && !file.preview) {
|
||||||
|
file.preview = await getBase64(file.originFileObj);
|
||||||
|
}
|
||||||
|
setPreviewImage(file.url || file.preview);
|
||||||
|
setPreviewOpen(true);
|
||||||
|
setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateFile = (file) => {
|
||||||
|
const isAllowedType = [
|
||||||
|
'application/pdf',
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/gif',
|
||||||
|
].includes(file.type);
|
||||||
|
|
||||||
|
if (!isAllowedType) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: `${file.name} bukan file PDF atau gambar yang diizinkan.`,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileUpload = async (file) => {
|
||||||
|
if (isUploading) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateFile(file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsUploading(true);
|
||||||
|
|
||||||
|
const fileExtension = file.name.split('.').pop().toLowerCase();
|
||||||
|
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExtension);
|
||||||
|
const fileType = isImage ? 'image' : 'pdf';
|
||||||
|
const folder = getFolderFromFileType(fileType);
|
||||||
|
|
||||||
|
const uploadResponse = await uploadFile(file, folder);
|
||||||
|
|
||||||
|
const isSuccess = uploadResponse && (
|
||||||
|
uploadResponse.statusCode === 200 ||
|
||||||
|
uploadResponse.statusCode === 201
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isSuccess) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: uploadResponse?.message || `Gagal mengupload ${file.name}`,
|
||||||
|
});
|
||||||
|
setIsUploading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let actualPath = '';
|
||||||
|
if (uploadResponse && typeof uploadResponse === 'object') {
|
||||||
|
if (uploadResponse.data && uploadResponse.data.path_document) {
|
||||||
|
actualPath = uploadResponse.data.path_document;
|
||||||
|
}
|
||||||
|
else if (uploadResponse.path_document) {
|
||||||
|
actualPath = uploadResponse.path_document;
|
||||||
|
}
|
||||||
|
else if (uploadResponse.data && uploadResponse.data.path_solution) {
|
||||||
|
actualPath = uploadResponse.data.path_solution;
|
||||||
|
}
|
||||||
|
else if (uploadResponse.data && typeof uploadResponse.data === 'object') {
|
||||||
|
if (uploadResponse.data.file_url) {
|
||||||
|
actualPath = uploadResponse.data.file_url;
|
||||||
|
} else if (uploadResponse.data.url) {
|
||||||
|
actualPath = uploadResponse.data.url;
|
||||||
|
} else if (uploadResponse.data.path) {
|
||||||
|
actualPath = uploadResponse.data.path;
|
||||||
|
} else if (uploadResponse.data.location) {
|
||||||
|
actualPath = uploadResponse.data.location;
|
||||||
|
} else if (uploadResponse.data.filePath) {
|
||||||
|
actualPath = uploadResponse.data.filePath;
|
||||||
|
} else if (uploadResponse.data.file_path) {
|
||||||
|
actualPath = uploadResponse.data.file_path;
|
||||||
|
} else if (uploadResponse.data.publicUrl) {
|
||||||
|
actualPath = uploadResponse.data.publicUrl;
|
||||||
|
} else if (uploadResponse.data.public_url) {
|
||||||
|
actualPath = uploadResponse.data.public_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (uploadResponse && typeof uploadResponse === 'string') {
|
||||||
|
actualPath = uploadResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (actualPath) {
|
||||||
|
let fileObject;
|
||||||
|
|
||||||
|
if (type === 'error_code') {
|
||||||
|
fileObject = {
|
||||||
|
name: file.name,
|
||||||
|
path_icon: actualPath,
|
||||||
|
uploadPath: actualPath,
|
||||||
|
url: actualPath,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
fileExtension
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
fileObject = {
|
||||||
|
name: file.name,
|
||||||
|
path_solution: actualPath,
|
||||||
|
uploadPath: actualPath,
|
||||||
|
type_solution: fileType,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileUpload(fileObject);
|
||||||
|
setUploadedFile(fileObject);
|
||||||
|
|
||||||
|
NotifOk({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: `${file.name} berhasil diupload!`
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsUploading(false);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: `Gagal mengupload ${file.name}. Tidak dapat menemukan path file dalam response.`,
|
||||||
|
});
|
||||||
|
setIsUploading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: `Gagal mengupload ${file.name}. Silakan coba lagi.`,
|
||||||
|
});
|
||||||
|
setIsUploading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = ({ fileList }) => {
|
||||||
|
if (fileList && fileList.length > 0 && fileList[0] && fileList[0].originFileObj) {
|
||||||
|
handleFileUpload(fileList[0].originFileObj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = () => {
|
||||||
|
if (existingFile && onFileRemove) {
|
||||||
|
onFileRemove(existingFile);
|
||||||
|
} else if (onFileRemove) {
|
||||||
|
onFileRemove(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderExistingFile = () => {
|
||||||
|
const fileToShow = existingFile || uploadedFile;
|
||||||
|
if (!fileToShow) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = fileToShow.uploadPath || fileToShow.url || fileToShow.path_icon || fileToShow.path_solution;
|
||||||
|
const fileName = fileToShow.name || filePath?.split('/').pop() || 'Unknown file';
|
||||||
|
const fileType = getFileType(fileName);
|
||||||
|
const isImage = fileType === 'image';
|
||||||
|
|
||||||
|
const handlePreview = () => {
|
||||||
|
if (!showPreview || !filePath) return;
|
||||||
|
|
||||||
|
if (isImage) {
|
||||||
|
const folder = fileToShow.type_solution === 'pdf' ? 'pdf' : 'images';
|
||||||
|
const filename = filePath.split('/').pop();
|
||||||
|
const imageUrl = getFileUrl(folder, filename);
|
||||||
|
|
||||||
|
if (imageUrl) {
|
||||||
|
setPreviewImage(imageUrl);
|
||||||
|
setPreviewOpen(true);
|
||||||
|
setPreviewTitle(fileName);
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Cannot generate image preview URL',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const folder = fileToShow.type_solution === 'pdf' ? 'pdf' : 'images';
|
||||||
|
const filename = filePath.split('/').pop();
|
||||||
|
const fileUrl = getFileUrl(folder, filename);
|
||||||
|
|
||||||
|
if (fileUrl) {
|
||||||
|
window.open(fileUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Cannot generate file preview URL',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getThumbnailUrl = () => {
|
||||||
|
if (!isImage || !filePath) return null;
|
||||||
|
|
||||||
|
const folder = fileToShow.type_solution === 'pdf' ? 'pdf' : 'images';
|
||||||
|
const filename = filePath.split('/').pop();
|
||||||
|
return getFileUrl(folder, filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
const thumbnailUrl = getThumbnailUrl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
padding: '8px',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: '#fafafa'
|
||||||
|
}}>
|
||||||
|
{isImage ? (
|
||||||
|
<img
|
||||||
|
src={thumbnailUrl || filePath}
|
||||||
|
alt={fileName}
|
||||||
|
style={{
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
objectFit: 'cover',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: 4,
|
||||||
|
cursor: showPreview ? 'pointer' : 'default'
|
||||||
|
}}
|
||||||
|
onClick={handlePreview}
|
||||||
|
onError={(e) => {
|
||||||
|
e.target.src = filePath;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
cursor: showPreview ? 'pointer' : 'default'
|
||||||
|
}}
|
||||||
|
onClick={handlePreview}
|
||||||
|
>
|
||||||
|
<FileOutlined style={{ fontSize: 24, color: '#666' }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Text style={{ fontSize: 12, fontWeight: 500 }}>
|
||||||
|
{fileName}
|
||||||
|
</Text>
|
||||||
|
<br />
|
||||||
|
<Text type="secondary" style={{ fontSize: 10 }}>
|
||||||
|
{fileType === 'image' ? 'Image' : fileType === 'pdf' ? 'PDF' : 'File'}
|
||||||
|
{fileToShow.size && ` • ${(fileToShow.size / 1024).toFixed(1)} KB`}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
|
{showPreview && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
size="small"
|
||||||
|
onClick={handlePreview}
|
||||||
|
title={isImage ? "Preview Image" : "Open File"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
size="small"
|
||||||
|
onClick={handleRemove}
|
||||||
|
title="Remove File"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadProps = {
|
||||||
|
name: 'file',
|
||||||
|
multiple: false,
|
||||||
|
accept,
|
||||||
|
disabled: disabled || isUploading,
|
||||||
|
fileList: [],
|
||||||
|
beforeUpload: () => false,
|
||||||
|
onChange: handleFileChange,
|
||||||
|
onPreview: handlePreview,
|
||||||
|
maxCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ ...containerStyle }}>
|
||||||
|
{!existingFile && (
|
||||||
|
<Upload {...uploadProps}>
|
||||||
|
{type === 'drag' ? (
|
||||||
|
<Upload.Dragger>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<UploadOutlined />
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">{uploadText}</p>
|
||||||
|
<p className="ant-upload-hint">{uploadHint}</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type={buttonType}
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
loading={isUploading}
|
||||||
|
style={{ ...buttonStyle }}
|
||||||
|
>
|
||||||
|
{isUploading ? 'Uploading...' : buttonText}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Upload>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{showPreview && (
|
||||||
|
<Modal
|
||||||
|
open={previewOpen}
|
||||||
|
title={previewTitle}
|
||||||
|
footer={null}
|
||||||
|
onCancel={() => setPreviewOpen(false)}
|
||||||
|
width={600}
|
||||||
|
style={{ top: 100 }}
|
||||||
|
>
|
||||||
|
{previewImage && (
|
||||||
|
<img
|
||||||
|
alt={previewTitle}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
src={previewImage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileUploadHandler;
|
||||||
263
src/pages/master/brandDevice/component/ListBrandDevice.jsx
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import { Button, Col, Row, Space, Input, ConfigProvider, Card, Tag, Spin } from 'antd';
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { NotifAlert, NotifConfirmDialog, NotifOk } from '../../../../components/Global/ToastNotif';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import TableList from '../../../../components/Global/TableList';
|
||||||
|
import { getAllBrands, deleteBrand } from '../../../../api/master-brand';
|
||||||
|
|
||||||
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Brand Device ',
|
||||||
|
dataIndex: 'brand_name',
|
||||||
|
key: 'brand_name',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Manufacturer',
|
||||||
|
dataIndex: 'brand_manufacture',
|
||||||
|
key: 'brand_manufacture',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Status',
|
||||||
|
dataIndex: 'is_active',
|
||||||
|
key: 'is_active',
|
||||||
|
width: '10%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, { is_active }) => (
|
||||||
|
<>
|
||||||
|
{is_active === true ? (
|
||||||
|
<Tag color={'green'} key={'status'}>
|
||||||
|
Running
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag color={'red'} key={'status'}>
|
||||||
|
Offline
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Action',
|
||||||
|
key: 'action',
|
||||||
|
align: 'center',
|
||||||
|
width: '15%',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => showPreviewModal(record)}
|
||||||
|
style={{
|
||||||
|
color: '#1890ff',
|
||||||
|
borderColor: '#1890ff',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => showEditModal(record)}
|
||||||
|
style={{
|
||||||
|
color: '#faad14',
|
||||||
|
borderColor: '#faad14',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => showDeleteDialog(record)}
|
||||||
|
style={{
|
||||||
|
borderColor: '#ff4d4f',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ListBrandDevice = memo(function ListBrandDevice(props) {
|
||||||
|
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||||
|
|
||||||
|
const defaultFilter = { criteria: '' };
|
||||||
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||||
|
const [searchText, setSearchText] = useState('');
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
if (props.actionMode === 'list') {
|
||||||
|
setFormDataFilter(defaultFilter);
|
||||||
|
doFilter();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigate('/signin');
|
||||||
|
}
|
||||||
|
}, [props.actionMode, navigate]);
|
||||||
|
|
||||||
|
const doFilter = () => {
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setFormDataFilter({ criteria: searchText });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchClear = () => {
|
||||||
|
setSearchText('');
|
||||||
|
setFormDataFilter({ criteria: '' });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showPreviewModal = (param) => {
|
||||||
|
navigate(`/master/brand-device/view/${param.brand_id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showEditModal = (param = null) => {
|
||||||
|
if (param) {
|
||||||
|
navigate(`/master/brand-device/edit/${param.brand_id}`);
|
||||||
|
} else {
|
||||||
|
navigate('/master/brand-device/add');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDeleteDialog = (param) => {
|
||||||
|
NotifConfirmDialog({
|
||||||
|
icon: 'question',
|
||||||
|
title: 'Konfirmasi',
|
||||||
|
message: 'Apakah anda yakin hapus data "' + param.brand_name + '" ?',
|
||||||
|
onConfirm: () => handleDelete(param.brand_id, param.brand_name),
|
||||||
|
onCancel: () => { },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (brand_id, brand_name) => {
|
||||||
|
try {
|
||||||
|
const response = await deleteBrand(brand_id);
|
||||||
|
|
||||||
|
if (response && response.statusCode === 200) {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: `Brand ${brand_name} deleted successfully.`,
|
||||||
|
});
|
||||||
|
doFilter();
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: response?.message || 'Gagal menghapus Data Brand Device',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: error.message || 'Gagal menghapus Data Brand Device',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Card>
|
||||||
|
<Row>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Row justify="space-between" align="middle" gutter={[8, 8]}>
|
||||||
|
<Col xs={24} sm={24} md={12} lg={12}>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Search brand device..."
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setSearchText(value);
|
||||||
|
if (value === '') {
|
||||||
|
setFormDataFilter({ criteria: '' });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
allowClear={{
|
||||||
|
clearIcon: <span onClick={handleSearchClear}>✕</span>,
|
||||||
|
}}
|
||||||
|
enterButton={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Space wrap size="small">
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
token: { colorBgContainer: '#E9F6EF' },
|
||||||
|
components: {
|
||||||
|
Button: {
|
||||||
|
defaultBg: 'white',
|
||||||
|
defaultColor: '#23A55A',
|
||||||
|
defaultBorderColor: '#23A55A',
|
||||||
|
defaultHoverColor: '#23A55A',
|
||||||
|
defaultHoverBorderColor: '#23A55A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/master/brand-device/add');
|
||||||
|
}}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
Add data
|
||||||
|
</Button>
|
||||||
|
</ConfigProvider>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
|
||||||
|
<TableList
|
||||||
|
mobile
|
||||||
|
cardColor={'#42AAFF'}
|
||||||
|
header={'brand_name'}
|
||||||
|
showPreviewModal={showPreviewModal}
|
||||||
|
showEditModal={showEditModal}
|
||||||
|
showDeleteDialog={showDeleteDialog}
|
||||||
|
getData={getAllBrands}
|
||||||
|
queryParams={formDataFilter}
|
||||||
|
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
||||||
|
triger={trigerFilter}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ListBrandDevice;
|
||||||
315
src/pages/master/brandDevice/component/ListErrorCode.jsx
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
|
import { Card, Input, Button, Row, Col, Empty } from 'antd';
|
||||||
|
import { PlusOutlined, SearchOutlined, DeleteOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
|
import { getErrorCodesByBrandId, deleteErrorCode } from '../../../../api/master-brand';
|
||||||
|
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
|
||||||
|
|
||||||
|
const ListErrorCode = ({
|
||||||
|
brandId,
|
||||||
|
selectedErrorCode,
|
||||||
|
onErrorCodeSelect,
|
||||||
|
onAddNew,
|
||||||
|
tempErrorCodes = [],
|
||||||
|
trigerFilter,
|
||||||
|
searchText,
|
||||||
|
onSearchChange,
|
||||||
|
onSearch,
|
||||||
|
onSearchClear,
|
||||||
|
isReadOnly = false,
|
||||||
|
errorCodes: propErrorCodes = null
|
||||||
|
}) => {
|
||||||
|
const [errorCodes, setErrorCodes] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
current_page: 1,
|
||||||
|
current_limit: 15,
|
||||||
|
total_limit: 0,
|
||||||
|
total_page: 0,
|
||||||
|
});
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const pageSize = 15;
|
||||||
|
|
||||||
|
const queryParams = useMemo(() => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set('page', currentPage.toString());
|
||||||
|
params.set('limit', pageSize.toString());
|
||||||
|
if (searchText) {
|
||||||
|
params.set('criteria', searchText);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}, [searchText, currentPage, pageSize]);
|
||||||
|
|
||||||
|
const fetchErrorCodes = async () => {
|
||||||
|
if (!brandId) {
|
||||||
|
setErrorCodes([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await getErrorCodesByBrandId(brandId, queryParams);
|
||||||
|
|
||||||
|
if (response && response.statusCode === 200) {
|
||||||
|
const apiErrorData = response.data || [];
|
||||||
|
const allErrorCodes = [
|
||||||
|
...apiErrorData.map(ec => ({
|
||||||
|
...ec,
|
||||||
|
tempId: `existing_${ec.error_code_id}`,
|
||||||
|
status: 'existing'
|
||||||
|
})),
|
||||||
|
...tempErrorCodes.filter(ec => ec.status !== 'deleted')
|
||||||
|
];
|
||||||
|
|
||||||
|
setErrorCodes(allErrorCodes);
|
||||||
|
|
||||||
|
if (response.paging) {
|
||||||
|
setPagination({
|
||||||
|
current_page: response.paging.current_page || 1,
|
||||||
|
current_limit: response.paging.current_limit || 15,
|
||||||
|
total_limit: response.paging.total_limit || 0,
|
||||||
|
total_page: response.paging.total_page || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setErrorCodes([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setErrorCodes([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isReadOnly && propErrorCodes) {
|
||||||
|
|
||||||
|
setErrorCodes(propErrorCodes);
|
||||||
|
setLoading(false);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fetchErrorCodes();
|
||||||
|
}
|
||||||
|
}, [brandId, queryParams, tempErrorCodes, trigerFilter, isReadOnly, propErrorCodes]);
|
||||||
|
|
||||||
|
const handlePrevious = () => {
|
||||||
|
if (pagination.current_page > 1) {
|
||||||
|
setCurrentPage(pagination.current_page - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
if (pagination.current_page < pagination.total_page) {
|
||||||
|
setCurrentPage(pagination.current_page + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
if (onSearch) {
|
||||||
|
onSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchClear = () => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
if (onSearchClear) {
|
||||||
|
onSearchClear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (item, e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (item.status === 'existing' && item.error_code_id) {
|
||||||
|
NotifConfirmDialog({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Hapus Error Code',
|
||||||
|
message: `Apakah Anda yakin ingin menghapus error code ${item.error_code}?`,
|
||||||
|
onConfirm: () => performDelete(item),
|
||||||
|
onCancel: () => { },
|
||||||
|
confirmButtonText: 'Hapus'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const performDelete = async (item) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (!item.error_code_id || item.error_code_id === 'undefined') {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Error code ID tidak valid'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.brand_id || item.brand_id === 'undefined') {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Brand ID tidak valid'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await deleteErrorCode(item.brand_id, item.error_code_id);
|
||||||
|
|
||||||
|
if (response && response.statusCode === 200) {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: 'Error code berhasil dihapus'
|
||||||
|
});
|
||||||
|
fetchErrorCodes();
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: 'Gagal menghapus error code'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Terjadi kesalahan saat menghapus error code'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title="Daftar Error Code"
|
||||||
|
style={{ width: '100%', minWidth: '472px' }}
|
||||||
|
styles={{ body: { padding: '12px' } }}
|
||||||
|
>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Cari error code..."
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (onSearchChange) {
|
||||||
|
onSearchChange(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
allowClear
|
||||||
|
enterButton={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
onClick={handleSearch}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#23A55A',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
height: '32px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
size="default"
|
||||||
|
style={{
|
||||||
|
marginBottom: 12,
|
||||||
|
height: '32px',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
height: '90vh',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: '6px',
|
||||||
|
overflow: 'auto',
|
||||||
|
marginBottom: 12,
|
||||||
|
backgroundColor: '#fafafa'
|
||||||
|
}}>
|
||||||
|
{errorCodes.length === 0 ? (
|
||||||
|
<Empty
|
||||||
|
description="Belum ada error code"
|
||||||
|
style={{ marginTop: 50 }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{ padding: '8px' }}>
|
||||||
|
{errorCodes.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.tempId || item.error_code_id}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
marginBottom: '4px',
|
||||||
|
border: selectedErrorCode?.tempId === item.tempId ? '2px solid #23A55A' : '1px solid #d9d9d9',
|
||||||
|
backgroundColor: selectedErrorCode?.tempId === item.tempId ? '#f6ffed' : '#fff',
|
||||||
|
transition: 'all 0.2s ease'
|
||||||
|
}}
|
||||||
|
onClick={() => onErrorCodeSelect(item)}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{ fontWeight: 'bold', fontSize: '12px' }}>
|
||||||
|
{item.error_code}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '11px', color: '#666' }}>
|
||||||
|
{item.error_code_name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{item.status === 'existing' && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={(e) => handleDelete(item, e)}
|
||||||
|
style={{
|
||||||
|
padding: '2px 6px',
|
||||||
|
height: '24px',
|
||||||
|
fontSize: '11px',
|
||||||
|
border: '1px solid #ff4d4f'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pagination.total_limit > 0 && (
|
||||||
|
<Row justify="space-between" align="middle" gutter={16}>
|
||||||
|
<Col flex="auto">
|
||||||
|
<span style={{ fontSize: '12px', color: '#666' }}>
|
||||||
|
Menampilkan {pagination.current_limit} data halaman{' '}
|
||||||
|
{pagination.current_page} dari total {pagination.total_limit} data
|
||||||
|
</span>
|
||||||
|
</Col>
|
||||||
|
<Col flex="none">
|
||||||
|
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||||
|
<Button
|
||||||
|
icon={<LeftOutlined />}
|
||||||
|
onClick={handlePrevious}
|
||||||
|
disabled={pagination.current_page <= 1}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
<span style={{ fontSize: '12px', color: '#666', minWidth: '60px', textAlign: 'center' }}>
|
||||||
|
{pagination.current_page} / {pagination.total_page}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
icon={<RightOutlined />}
|
||||||
|
onClick={handleNext}
|
||||||
|
disabled={pagination.current_page >= pagination.total_page}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListErrorCode;
|
||||||
496
src/pages/master/brandDevice/component/SolutionField.jsx
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Form, Input, Button, Switch, Radio, Typography, Space, Card, ConfigProvider } from 'antd';
|
||||||
|
import { DeleteOutlined, EyeOutlined, FileOutlined } from '@ant-design/icons';
|
||||||
|
import FileUploadHandler from './FileUploadHandler';
|
||||||
|
import { NotifAlert } from '../../../../components/Global/ToastNotif';
|
||||||
|
import { getFileUrl, getFolderFromFileType } from '../../../../api/file-uploads';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
const SolutionFieldNew = ({
|
||||||
|
fieldKey,
|
||||||
|
fieldName,
|
||||||
|
index,
|
||||||
|
solutionType,
|
||||||
|
solutionStatus,
|
||||||
|
isReadOnly = false,
|
||||||
|
canRemove = true,
|
||||||
|
onTypeChange,
|
||||||
|
onStatusChange,
|
||||||
|
onRemove,
|
||||||
|
onFileUpload,
|
||||||
|
onFileView,
|
||||||
|
fileList = [],
|
||||||
|
originalSolutionData = null
|
||||||
|
}) => {
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const [currentFile, setCurrentFile] = useState(null);
|
||||||
|
const [isDeleted, setIsDeleted] = useState(false);
|
||||||
|
|
||||||
|
const fileUpload = Form.useWatch(['solution_items', fieldKey, 'fileUpload'], form);
|
||||||
|
const file = Form.useWatch(['solution_items', fieldKey, 'file'], form);
|
||||||
|
const nameValue = Form.useWatch(['solution_items', fieldKey, 'name'], form);
|
||||||
|
const fileNameValue = Form.useWatch(['solution_items', fieldKey, 'fileName'], form);
|
||||||
|
const statusValue = Form.useWatch(['solution_items', fieldKey, 'status'], form) ?? true;
|
||||||
|
|
||||||
|
const pathSolution = Form.useWatch(['solution_items', fieldKey, 'path_solution'], form);
|
||||||
|
|
||||||
|
const [deleteCounter, setDeleteCounter] = useState(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!nameValue || nameValue === '') {
|
||||||
|
setCurrentFile(null);
|
||||||
|
setIsDeleted(false);
|
||||||
|
setDeleteCounter(prev => prev + 1);
|
||||||
|
}
|
||||||
|
}, [nameValue]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const getFileFromFormValues = () => {
|
||||||
|
const hasValidFileUpload = fileUpload && typeof fileUpload === 'object' && Object.keys(fileUpload).length > 0;
|
||||||
|
const hasValidFile = file && typeof file === 'object' && Object.keys(file).length > 0;
|
||||||
|
const hasValidPath = pathSolution && pathSolution.trim() !== '';
|
||||||
|
|
||||||
|
const wasExplicitlyDeleted =
|
||||||
|
(fileUpload === null || file === null || pathSolution === null) &&
|
||||||
|
!hasValidFileUpload &&
|
||||||
|
!hasValidFile &&
|
||||||
|
!hasValidPath;
|
||||||
|
|
||||||
|
if (wasExplicitlyDeleted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (solutionType === 'text') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasValidFileUpload) {
|
||||||
|
return fileUpload;
|
||||||
|
}
|
||||||
|
if (hasValidFile) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
if (hasValidPath) {
|
||||||
|
return {
|
||||||
|
name: fileNameValue || pathSolution.split('/').pop() || 'File',
|
||||||
|
uploadPath: pathSolution,
|
||||||
|
url: pathSolution,
|
||||||
|
path: pathSolution
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileFromForm = getFileFromFormValues();
|
||||||
|
|
||||||
|
if (JSON.stringify(currentFile) !== JSON.stringify(fileFromForm)) {
|
||||||
|
setCurrentFile(fileFromForm);
|
||||||
|
}
|
||||||
|
}, [fileUpload, file, pathSolution, solutionType, deleteCounter, fileNameValue, fieldKey]);
|
||||||
|
|
||||||
|
|
||||||
|
const renderSolutionContent = () => {
|
||||||
|
if (solutionType === 'text') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
name={['solution_items', fieldKey, 'text']}
|
||||||
|
rules={[{ required: true, message: 'Text solution wajib diisi!' }]}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
placeholder="Enter solution text"
|
||||||
|
rows={3}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
style={{ fontSize: 12 }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (solutionType === 'file') {
|
||||||
|
const hasOriginalFile = originalSolutionData && (
|
||||||
|
originalSolutionData.path_solution ||
|
||||||
|
originalSolutionData.path_document
|
||||||
|
);
|
||||||
|
|
||||||
|
let displayFile = null;
|
||||||
|
|
||||||
|
if (currentFile && Object.keys(currentFile).length > 0) {
|
||||||
|
displayFile = currentFile;
|
||||||
|
}
|
||||||
|
else if (hasOriginalFile && !isDeleted) {
|
||||||
|
displayFile = {
|
||||||
|
name: originalSolutionData.file_upload_name ||
|
||||||
|
(originalSolutionData.path_solution || originalSolutionData.path_document)?.split('/').pop() ||
|
||||||
|
'File',
|
||||||
|
uploadPath: originalSolutionData.path_solution || originalSolutionData.path_document,
|
||||||
|
url: originalSolutionData.path_solution || originalSolutionData.path_document,
|
||||||
|
path: originalSolutionData.path_solution || originalSolutionData.path_document,
|
||||||
|
isExisting: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (fileUpload && typeof fileUpload === 'object' && Object.keys(fileUpload).length > 0) {
|
||||||
|
displayFile = fileUpload;
|
||||||
|
}
|
||||||
|
else if (file && typeof file === 'object' && Object.keys(file).length > 0) {
|
||||||
|
displayFile = file;
|
||||||
|
}
|
||||||
|
else if (pathSolution && pathSolution.trim() !== '') {
|
||||||
|
displayFile = {
|
||||||
|
name: pathSolution.split('/').pop() || 'File',
|
||||||
|
uploadPath: pathSolution,
|
||||||
|
url: pathSolution,
|
||||||
|
path: pathSolution
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (displayFile) {
|
||||||
|
const getFileNameFromPath = () => {
|
||||||
|
const filePath = displayFile.uploadPath || displayFile.url || displayFile.path || '';
|
||||||
|
if (filePath) {
|
||||||
|
const fileName = filePath.split('/').pop();
|
||||||
|
return fileName || 'Uploaded File';
|
||||||
|
}
|
||||||
|
return displayFile.name || 'Uploaded File';
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayFileName = getFileNameFromPath();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
marginBottom: 8,
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||||
|
border: '1px solid #e8e8e8'
|
||||||
|
}}
|
||||||
|
styles={{ body: { padding: '16px' } }}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: '#f0f5ff',
|
||||||
|
flexShrink: 0
|
||||||
|
}}>
|
||||||
|
<FileOutlined style={{ fontSize: 24, color: '#1890ff' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: '#262626',
|
||||||
|
marginBottom: 4,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}}>
|
||||||
|
{displayFileName}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: '#8c8c8c' }}>
|
||||||
|
{displayFile.size ? `${(displayFile.size / 1024).toFixed(1)} KB` : 'File uploaded'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="middle"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
try {
|
||||||
|
let fileUrl = '';
|
||||||
|
let actualFileName = '';
|
||||||
|
|
||||||
|
const filePath = displayFile.uploadPath || displayFile.url || displayFile.path || '';
|
||||||
|
|
||||||
|
if (filePath) {
|
||||||
|
actualFileName = filePath.split('/').pop();
|
||||||
|
|
||||||
|
if (actualFileName) {
|
||||||
|
const fileExtension = actualFileName.split('.').pop()?.toLowerCase();
|
||||||
|
const folder = getFolderFromFileType(fileExtension);
|
||||||
|
|
||||||
|
fileUrl = getFileUrl(folder, actualFileName);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileUrl && filePath) {
|
||||||
|
fileUrl = filePath.startsWith('http') ? filePath : `${import.meta.env.VITE_API_SERVER}/${filePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileUrl && actualFileName) {
|
||||||
|
const fileExtension = actualFileName.split('.').pop()?.toLowerCase();
|
||||||
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
||||||
|
|
||||||
|
if (imageExtensions.includes(fileExtension)) {
|
||||||
|
const viewerUrl = `/image-viewer/${encodeURIComponent(actualFileName)}`;
|
||||||
|
window.open(viewerUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
} else {
|
||||||
|
window.open(fileUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'File URL not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Failed to open file preview'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
size="middle"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setIsDeleted(true);
|
||||||
|
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'fileUpload'], null);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'file'], null);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'path_solution'], null);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'fileName'], null);
|
||||||
|
|
||||||
|
setCurrentFile(null);
|
||||||
|
|
||||||
|
if (onFileUpload && typeof onFileUpload === 'function') {
|
||||||
|
onFileUpload(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeleteCounter(prev => prev + 1);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
form.validateFields(['solution_items', fieldKey]);
|
||||||
|
}, 50);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<FileUploadHandler
|
||||||
|
type="solution"
|
||||||
|
existingFile={null}
|
||||||
|
clearSignal={deleteCounter}
|
||||||
|
debugProps={{
|
||||||
|
currentFile: !!currentFile,
|
||||||
|
deleteCounter,
|
||||||
|
shouldClear: !currentFile && deleteCounter > 0
|
||||||
|
}}
|
||||||
|
onFileUpload={(fileObject) => {
|
||||||
|
setIsDeleted(false);
|
||||||
|
|
||||||
|
const filePath = fileObject.path_solution || fileObject.uploadPath || fileObject.path || fileObject.url;
|
||||||
|
|
||||||
|
const fileWithKey = {
|
||||||
|
...fileObject,
|
||||||
|
solutionId: fieldKey,
|
||||||
|
path_solution: filePath,
|
||||||
|
uploadPath: filePath
|
||||||
|
};
|
||||||
|
|
||||||
|
if (onFileUpload && typeof onFileUpload === 'function') {
|
||||||
|
onFileUpload(fileWithKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'fileUpload'], fileWithKey);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'file'], fileWithKey);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'type'], 'file');
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'path_solution'], filePath);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'fileName'], fileObject.name);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const values = form.getFieldValue(['solution_items', fieldKey]);
|
||||||
|
const pathSolutionValue = form.getFieldValue(['solution_items', fieldKey, 'path_solution']);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setCurrentFile(fileWithKey);
|
||||||
|
}}
|
||||||
|
onFileRemove={() => {
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'fileUpload'], null);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'file'], null);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'path_solution'], null);
|
||||||
|
|
||||||
|
setCurrentFile(null);
|
||||||
|
|
||||||
|
if (onFileUpload && typeof onFileUpload === 'function') {
|
||||||
|
onFileUpload(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeleteCounter(prev => prev + 1);
|
||||||
|
}}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
buttonText="Upload File"
|
||||||
|
buttonStyle={{ width: '100%', fontSize: 12 }}
|
||||||
|
uploadText="Upload solution file (includes images, PDF, documents)"
|
||||||
|
acceptFileTypes="*"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
components: {
|
||||||
|
Switch: {
|
||||||
|
colorPrimary: '#23A55A',
|
||||||
|
colorPrimaryHover: '#23A55A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: 6,
|
||||||
|
padding: 12,
|
||||||
|
marginBottom: 12,
|
||||||
|
backgroundColor: isReadOnly ? '#f5f5f5' : 'white'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
marginBottom: 8,
|
||||||
|
gap: 8
|
||||||
|
}}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||||||
|
<Text strong style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#262626',
|
||||||
|
display: 'block'
|
||||||
|
}}>
|
||||||
|
Solution #{index + 1}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
|
<Form.Item name={['solution_items', fieldKey, 'status']} valuePropName="checked" noStyle>
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
disabled={isReadOnly}
|
||||||
|
onChange={(checked) => {
|
||||||
|
onStatusChange(fieldKey, checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Text style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: '#666',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}}>
|
||||||
|
{statusValue ? 'Active' : 'Inactive'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{canRemove && !isReadOnly && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={onRemove}
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
padding: '2px 4px',
|
||||||
|
height: '24px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name={['solution_items', fieldKey, 'name']}
|
||||||
|
rules={[{ required: true, message: 'Solution name wajib diisi!' }]}
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Solution name"
|
||||||
|
disabled={isReadOnly}
|
||||||
|
size="default"
|
||||||
|
style={{ fontSize: 13 }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name={['solution_items', fieldKey, 'type']}
|
||||||
|
rules={[{ required: true, message: 'Solution type wajib diisi!' }]}
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
initialValue={solutionType || 'text'}
|
||||||
|
>
|
||||||
|
<Radio.Group
|
||||||
|
onChange={(e) => {
|
||||||
|
const newType = e.target.value;
|
||||||
|
|
||||||
|
if (newType === 'text') {
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'fileUpload'], null);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'file'], null);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'path_solution'], null);
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'fileName'], null);
|
||||||
|
setCurrentFile(null);
|
||||||
|
setIsDeleted(true);
|
||||||
|
|
||||||
|
if (onFileUpload && typeof onFileUpload === 'function') {
|
||||||
|
onFileUpload(null);
|
||||||
|
}
|
||||||
|
} else if (newType === 'file') {
|
||||||
|
form.setFieldValue(['solution_items', fieldKey, 'text'], null);
|
||||||
|
setIsDeleted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTypeChange(fieldKey, newType);
|
||||||
|
}}
|
||||||
|
disabled={isReadOnly}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Radio value="text" style={{ fontSize: 12 }}>Text</Radio>
|
||||||
|
<Radio value="file" style={{ fontSize: 12 }}>File</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name={['solution_items', fieldKey, 'status']}
|
||||||
|
initialValue={solutionStatus !== false ? true : false}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
<input type="hidden" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{renderSolutionContent()}
|
||||||
|
</div>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SolutionFieldNew;
|
||||||
77
src/pages/master/brandDevice/component/SolutionForm.jsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Typography, Divider, Button, Form } from 'antd';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import SolutionFieldNew from './SolutionField';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const SolutionForm = ({
|
||||||
|
solutionForm,
|
||||||
|
solutionFields,
|
||||||
|
solutionTypes,
|
||||||
|
solutionStatuses,
|
||||||
|
onAddSolutionField,
|
||||||
|
onRemoveSolutionField,
|
||||||
|
onSolutionTypeChange,
|
||||||
|
onSolutionStatusChange,
|
||||||
|
onSolutionFileUpload,
|
||||||
|
onFileView,
|
||||||
|
fileList,
|
||||||
|
isReadOnly = false,
|
||||||
|
solutionData = [],
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 0 }}>
|
||||||
|
|
||||||
|
<Form form={solutionForm} layout="vertical">
|
||||||
|
<div style={{
|
||||||
|
maxHeight: '400px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
paddingRight: '8px'
|
||||||
|
}}>
|
||||||
|
{solutionFields.map((field, displayIndex) => (
|
||||||
|
<SolutionFieldNew
|
||||||
|
key={field}
|
||||||
|
fieldKey={field}
|
||||||
|
fieldName={['solution_items', field]}
|
||||||
|
index={displayIndex}
|
||||||
|
solutionType={solutionTypes[field]}
|
||||||
|
solutionStatus={solutionStatuses[field]}
|
||||||
|
onTypeChange={onSolutionTypeChange}
|
||||||
|
onStatusChange={onSolutionStatusChange}
|
||||||
|
onRemove={() => onRemoveSolutionField(field)}
|
||||||
|
onFileUpload={onSolutionFileUpload}
|
||||||
|
onFileView={onFileView}
|
||||||
|
fileList={fileList}
|
||||||
|
isReadOnly={isReadOnly}
|
||||||
|
canRemove={solutionFields.length > 1 && displayIndex > 0}
|
||||||
|
originalSolutionData={solutionData[displayIndex]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isReadOnly && (
|
||||||
|
<div style={{ marginBottom: 8, marginTop: 12 }}>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={onAddSolutionField}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
borderColor: '#23A55A',
|
||||||
|
color: '#23A55A',
|
||||||
|
height: '32px',
|
||||||
|
fontSize: '12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add sollution
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SolutionForm;
|
||||||