Compare commits

..

13 Commits

45 changed files with 2498 additions and 285 deletions

View File

@@ -45,6 +45,7 @@
"react-grid-layout": "^1.5.1", "react-grid-layout": "^1.5.1",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"recharts": "^2.15.2",
"sonner": "^2.0.1", "sonner": "^2.0.1",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.15", "tailwindcss": "^4.0.15",
@@ -327,6 +328,18 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.26.9", "version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
@@ -3376,6 +3389,69 @@
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
"license": "MIT"
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"license": "MIT"
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"license": "MIT"
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"license": "MIT",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"license": "MIT"
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"license": "MIT",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"license": "MIT",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"license": "MIT"
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -4105,9 +4181,129 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"license": "ISC",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/data-uri-to-buffer": { "node_modules/data-uri-to-buffer": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
@@ -4154,6 +4350,12 @@
} }
} }
}, },
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
"license": "MIT"
},
"node_modules/decode-formdata": { "node_modules/decode-formdata": {
"version": "0.8.0", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/decode-formdata/-/decode-formdata-0.8.0.tgz", "resolved": "https://registry.npmjs.org/decode-formdata/-/decode-formdata-0.8.0.tgz",
@@ -4209,6 +4411,16 @@
"node": ">=0.3.1" "node": ">=0.3.1"
} }
}, },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "16.4.7", "version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
@@ -4545,6 +4757,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4958,6 +5176,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/is-arguments": { "node_modules/is-arguments": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
@@ -5484,6 +5711,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.includes": { "node_modules/lodash.includes": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -6078,6 +6311,30 @@
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
} }
}, },
"node_modules/react-smooth": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
"integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
"license": "MIT",
"dependencies": {
"fast-equals": "^5.0.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-smooth/node_modules/fast-equals": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
"integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/react-style-singleton": { "node_modules/react-style-singleton": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
@@ -6100,6 +6357,22 @@
} }
} }
}, },
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -6113,6 +6386,50 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/recharts": {
"version": "2.15.2",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.2.tgz",
"integrity": "sha512-xv9lVztv3ingk7V3Jf05wfAZbM9Q2umJzu5t/cfnAK7LUslNrGT7LPBr74G+ok8kSCeFMaePmWMg0rcYOnczTw==",
"license": "MIT",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.21",
"react-is": "^18.3.1",
"react-smooth": "^4.0.4",
"recharts-scale": "^0.4.4",
"tiny-invariant": "^1.3.1",
"victory-vendor": "^36.6.8"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/recharts-scale": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
"license": "MIT",
"dependencies": {
"decimal.js-light": "^2.4.1"
}
},
"node_modules/recharts/node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/resize-observer-polyfill": { "node_modules/resize-observer-polyfill": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@@ -6712,6 +7029,28 @@
"which-typed-array": "^1.1.2" "which-typed-array": "^1.1.2"
} }
}, },
"node_modules/victory-vendor": {
"version": "36.9.2",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
"license": "MIT AND ISC",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "6.2.2", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz",

View File

@@ -49,6 +49,7 @@
"react-grid-layout": "^1.5.1", "react-grid-layout": "^1.5.1",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"recharts": "^2.15.2",
"sonner": "^2.0.1", "sonner": "^2.0.1",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.15", "tailwindcss": "^4.0.15",

View File

@@ -0,0 +1,146 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { toast } from "sonner";
import { useState } from "react";
//import { z } from "zod";
//import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { useSessionStore } from "@/lib/store/sessionStore";
import axios from "axios";
import { useForm } from "@tanstack/react-form";
import { Checkbox } from "@/components/ui/checkbox";
import { getModules } from "@/utils/querys/admin/modules";
// const FormSchema = z.object({
// subModule: z.boolean(),
// });
export function ChangeModule({ module }: { module: any }) {
const { token } = useSessionStore();
const { refetch } = useQuery(getModules(token ?? ""));
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
const form = useForm({
defaultValues: {
active: module.active,
},
onSubmit: async ({ value }) => {
console.log(value);
try {
const result = await axios.patch(
`/api/server/modules/${module.module_id}`,
{ active: value.active },
{
headers: { Authorization: `Bearer ${token}` },
}
);
if (result.data.success) {
setOpen(!open);
setSaving(false);
refetch();
toast.success(result.data.message);
}
} catch (error) {
console.log(error);
}
},
});
return (
<>
<Dialog
open={open}
onOpenChange={(isOpen) => {
if (!open) {
form.reset();
}
setOpen(isOpen);
// toast.message("Model was something", {
// description: isOpen ? "Modal is open" : "Modal is closed",
// });
}}
>
<DialogTrigger asChild>
<Button variant="outline">Edit</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{module.name}</DialogTitle>
<DialogDescription>
Set to active or deactivated.
</DialogDescription>
</DialogHeader>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<div>
<>
<form.Field
name="active"
// validators={{
// // We can choose between form-wide and field-specific validators
// onChange: ({ value }) =>
// value.length > 3
// ? undefined
// : "Username must be longer than 3 letters",
// }}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2 flex flex-row">
<Label htmlFor="active">
Active
</Label>
<Checkbox
className="ml-2"
name={field.name}
onBlur={field.handleBlur}
checked={field.state.value}
onCheckedChange={(e) =>
field.handleChange(e)
}
/>
</div>
);
}}
/>
</>
</div>
<DialogFooter>
<div className="flex justify-end mt-2">
<Button
type="submit"
disabled={saving}
onClick={form.handleSubmit}
>
{saving ? (
<>
<span>Saving....</span>
</>
) : (
<span>Save setting</span>
)}
</Button>
</div>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</>
);
}

View File

@@ -0,0 +1,116 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useSessionStore } from "@/lib/store/sessionStore";
import { useModuleStore } from "@/lib/store/useModuleStore";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "@tanstack/react-router";
import { Skeleton } from "@/components/ui/skeleton";
import { getModules } from "@/utils/querys/admin/modules";
import { ChangeModule } from "./ModuleForm";
export type Settings = {
settings_id?: string;
name?: string;
value?: string;
description?: string;
};
export default function ModulesPage() {
const { user, token } = useSessionStore();
const { modules } = useModuleStore();
const router = useRouter();
const adminModule = modules.filter((n) => n.name === "admin");
const userLevel =
user?.roles.filter((r) => r.module_id === adminModule[0].module_id) ||
[];
if (!adminModule[0].roles.includes(userLevel[0]?.role)) {
router.navigate({ to: "/" });
}
const { data, isError, error, isLoading } = useQuery(
getModules(token ?? "")
);
// if (isLoading) {
// return <div>Loading.....</div>;
// }
if (isError) {
return <div>{JSON.stringify(error)}</div>;
}
return (
<LstCard className="m-2 flex place-content-center w-fit">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Description</TableHead>
<TableHead>Module In</TableHead>
<TableHead>Roles</TableHead>
<TableHead>Active</TableHead>
<TableHead>Edit</TableHead>
</TableRow>
</TableHeader>
{isLoading ? (
<>
<TableBody>
{Array(10)
.fill(0)
.map((_, i) => (
<TableRow key={i}>
<TableCell className="font-medium">
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
</TableRow>
))}
</TableBody>
</>
) : (
<TableBody>
{data?.map((i: any) => (
<TableRow key={i.submodule_id}>
<TableCell className="font-medium">
{i.name}
</TableCell>
<TableCell className="font-medium">
{i.description}
</TableCell>
<TableCell className="font-medium">
{i.moduleName}
</TableCell>
<TableCell className="font-medium">
{JSON.stringify(i.roles)}
</TableCell>
<TableCell className="font-medium">
{i.active ? "Yes" : "No"}
</TableCell>
<TableCell className="font-medium">
<ChangeModule module={i} />
</TableCell>
</TableRow>
))}
</TableBody>
)}
</Table>
</LstCard>
);
}

View File

@@ -0,0 +1,145 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { toast } from "sonner";
import { useState } from "react";
//import { z } from "zod";
//import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { useSessionStore } from "@/lib/store/sessionStore";
import axios from "axios";
import { useForm } from "@tanstack/react-form";
import { Checkbox } from "@/components/ui/checkbox";
import { getSubModules } from "@/utils/querys/admin/subModules";
// const FormSchema = z.object({
// subModule: z.boolean(),
// });
export function ChangeSubModule({ subModule }: { subModule: any }) {
const { token } = useSessionStore();
const { refetch } = useQuery(getSubModules(token ?? ""));
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
const form = useForm({
defaultValues: {
active: subModule.active,
},
onSubmit: async ({ value }) => {
console.log(value);
try {
const result = await axios.patch(
`/api/server/submodules/${subModule.submodule_id}`,
{ active: value.active },
{
headers: { Authorization: `Bearer ${token}` },
}
);
if (result.data.success) {
setOpen(!open);
setSaving(false);
refetch();
toast.success(result.data.message);
}
} catch (error) {
console.log(error);
}
},
});
return (
<>
<Dialog
open={open}
onOpenChange={(isOpen) => {
if (!open) {
form.reset();
}
setOpen(isOpen);
// toast.message("Model was something", {
// description: isOpen ? "Modal is open" : "Modal is closed",
// });
}}
>
<DialogTrigger asChild>
<Button variant="outline">Edit</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{subModule.name}</DialogTitle>
<DialogDescription>
Set to active or deactivated.
</DialogDescription>
</DialogHeader>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<div>
<>
<form.Field
name="active"
// validators={{
// // We can choose between form-wide and field-specific validators
// onChange: ({ value }) =>
// value.length > 3
// ? undefined
// : "Username must be longer than 3 letters",
// }}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2 flex flex-row">
<Label htmlFor="active">
Active
</Label>
<Checkbox
className="ml-2"
name={field.name}
onBlur={field.handleBlur}
checked={field.state.value}
onCheckedChange={(e) =>
field.handleChange(e)
}
/>
</div>
);
}}
/>
</>
</div>
<DialogFooter>
<div className="flex justify-end mt-2">
<Button
type="submit"
disabled={saving}
onClick={form.handleSubmit}
>
{saving ? (
<>
<span>Saving....</span>
</>
) : (
<span>Save setting</span>
)}
</Button>
</div>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</>
);
}

View File

@@ -0,0 +1,125 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useSessionStore } from "@/lib/store/sessionStore";
import { useModuleStore } from "@/lib/store/useModuleStore";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "@tanstack/react-router";
import { ChangeSubModule } from "./SubModuleForm";
import { Skeleton } from "@/components/ui/skeleton";
import { getSubModules } from "@/utils/querys/admin/subModules";
export type Settings = {
settings_id?: string;
name?: string;
value?: string;
description?: string;
};
export default function SubModulePage() {
const { user, token } = useSessionStore();
const { modules } = useModuleStore();
const router = useRouter();
const adminModule = modules.filter((n) => n.name === "admin");
const userLevel =
user?.roles.filter((r) => r.module_id === adminModule[0].module_id) ||
[];
if (!adminModule[0].roles.includes(userLevel[0]?.role)) {
router.navigate({ to: "/" });
}
const { data, isError, error, isLoading } = useQuery(
getSubModules(token ?? "")
);
// if (isLoading) {
// return <div>Loading.....</div>;
// }
if (isError) {
return <div>{JSON.stringify(error)}</div>;
}
return (
<LstCard className="m-2 flex place-content-center w-fit">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Description</TableHead>
<TableHead>Module In</TableHead>
<TableHead>Roles</TableHead>
<TableHead>Active</TableHead>
<TableHead>Edit</TableHead>
</TableRow>
</TableHeader>
{isLoading ? (
<>
<TableBody>
{Array(10)
.fill(0)
.map((_, i) => (
<TableRow key={i}>
<TableCell className="font-medium">
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
</TableRow>
))}
</TableBody>
</>
) : (
<TableBody>
{data?.map((i: any) => (
<TableRow key={i.submodule_id}>
<TableCell className="font-medium">
{i.name}
</TableCell>
<TableCell className="font-medium">
{i.description}
</TableCell>
<TableCell className="font-medium">
{i.moduleName}
</TableCell>
<TableCell className="font-medium">
{JSON.stringify(i.roles)}
</TableCell>
<TableCell className="font-medium">
{i.active ? "Yes" : "No"}
</TableCell>
<TableCell className="font-medium">
<ChangeSubModule subModule={i} />
</TableCell>
</TableRow>
))}
</TableBody>
)}
</Table>
</LstCard>
);
}

View File

@@ -0,0 +1,31 @@
import ModuleForm from "./ModuleForm";
import { getModules } from "@/utils/querys/admin/modules";
import { useQuery } from "@tanstack/react-query";
//import { getUserRoles } from "@/utils/querys/admin/userRoles";
//import { Checkbox } from "@radix-ui/react-checkbox";
export default function ModuleAccess(data: any) {
const token = localStorage.getItem("auth_token");
// const { data: userRoles } = useQuery(getUserRoles());
const {
data: modules,
isError,
isLoading,
} = useQuery(getModules(token ?? ""));
if (isError) return <div>Error gettings Roles</div>;
if (isLoading) return <div>Loading modules</div>;
return (
<div className="flex flex-row flex-wrap">
{modules?.map((m: any) => {
return (
<ModuleForm
key={m.module_id}
i={m}
username={data.user.username}
/>
);
})}
</div>
);
}

View File

@@ -0,0 +1,119 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { getUserRoles } from "@/utils/querys/admin/userRoles";
import { useForm } from "@tanstack/react-form";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { toast } from "sonner";
export default function ModuleForm(props: any) {
const { refetch } = useQuery(getUserRoles());
const token = localStorage.getItem("auth_token");
const form = useForm({
defaultValues: { role: props.i.role },
onSubmit: async ({ value }) => {
const data = {
username: props.username,
module: props.i.name,
role: value.role,
};
console.log(data);
try {
const res = await axios.post("/api/auth/setuseraccess", data, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (res.data.success) {
toast.success(res.data.message);
refetch();
form.reset();
} else {
res.data.message;
}
} catch (error) {
console.log(error);
}
},
});
return (
<div className="m-2 p-1">
<LstCard>
<p className="text-center">Module: {props.i.name}</p>
<p className="p-1">Current role: </p>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<form.Field
name="role"
//listeners={{onChange: ({value})=>{}}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label htmlFor={field.name}>
Select role
</Label>
<Select
value={field.state.value}
onValueChange={field.handleChange}
>
<SelectTrigger className="w-[180px]">
<SelectValue
id={field.name}
placeholder="Select Role"
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Roles</SelectLabel>
<SelectItem value="viewer">
Viewer
</SelectItem>
<SelectItem value="technician">
Technician
</SelectItem>
<SelectItem value="supervisor">
Supervisor
</SelectItem>
<SelectItem value="manager">
Manager
</SelectItem>
<SelectItem value="tester">
Tester
</SelectItem>
<SelectItem value="admin">
Admin
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
);
}}
/>
<div className="mt-4">
<Button type="submit" onClick={form.handleSubmit}>
Save
</Button>
</div>
</form>
</LstCard>
</div>
);
}

View File

@@ -0,0 +1,31 @@
import { useGetUserRoles } from "@/lib/store/useGetRoles";
import { useSubModuleStore } from "@/lib/store/useSubModuleStore";
import SubModuleForm from "./SubmoduleForm";
//import { Checkbox } from "@radix-ui/react-checkbox";
export default function UserSubRoles(data: any) {
const { userRoles } = useGetUserRoles();
const { subModules } = useSubModuleStore();
return (
<div className="flex flex-row flex-wrap">
{subModules?.map((m: any) => {
const hasRole: any = userRoles.filter(
(r: any) =>
r.user_id.includes(data.user.user_id) &&
r.module_id === m.module_id
);
return (
<div key={m.module_id}>
<SubModuleForm
i={m}
hasRole={hasRole}
user={data.user}
/>
</div>
);
})}
</div>
);
}

View File

@@ -0,0 +1,93 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useForm } from "@tanstack/react-form";
export default function SubModuleForm(props: any) {
const form = useForm({
defaultValues: { role: "" },
onSubmit: async ({ value }) => {
console.log(value);
},
});
return (
<div className="m-2 p-1">
<LstCard>
<p className="text-center">
Module: {props.i.moduleName}, <br />
SubModule: {props.i.name}
</p>
<p className="p-1">
Current role:{" "}
{props.hasRole[0]?.role
? props.hasRole[0].role
: "not assigned"}
</p>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<form.Field
name="role"
//listeners={{onChange: ({value})=>{}}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label htmlFor={field.name}>
Select role
</Label>
<Select
value={field.state.value}
onValueChange={field.handleChange}
>
<SelectTrigger className="w-[180px]">
<SelectValue
id={field.name}
placeholder="Select Role"
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Roles</SelectLabel>
<SelectItem value="viewer">
Viewer
</SelectItem>
<SelectItem value="technician">
Technician
</SelectItem>
<SelectItem value="supervisor">
Supervisor
</SelectItem>
<SelectItem value="manager">
Manager
</SelectItem>
<SelectItem value="tester">
Tester
</SelectItem>
<SelectItem value="admin">
Admin
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
);
}}
/>
</form>
</LstCard>
</div>
);
}

View File

@@ -20,7 +20,7 @@ import { useForm } from "@tanstack/react-form";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import UserRoles from "./UserRoles";
import { CardHeader } from "@/components/ui/card"; import { CardHeader } from "@/components/ui/card";
export default function UserCard(data: any) { export default function UserCard(data: any) {
@@ -270,16 +270,11 @@ export default function UserCard(data: any) {
/> />
</form> </form>
</LstCard> </LstCard>
<div> <div className="mt-4">
<Button onClick={form.handleSubmit}>Save</Button> <Button onClick={form.handleSubmit}>Save</Button>
</div> </div>
</div> </div>
<div className="m-2"> <div className="flex flex-col"></div>
<LstCard>
<CardHeader>User Module / Role Access</CardHeader>
<UserRoles user={data.user} />
</LstCard>
</div>
</div> </div>
); );
} }

View File

@@ -1,16 +0,0 @@
import { Label } from "@/components/ui/label";
import { useModuleStore } from "@/lib/store/useModuleStore";
//import { Checkbox } from "@radix-ui/react-checkbox";
export default function UserRoles(user: any) {
const { modules } = useModuleStore();
console.log(user);
return (
<div>
{modules?.map((m: any) => {
console.log(m);
return <Label>{m.name}</Label>;
})}
</div>
);
}

View File

@@ -1,4 +1,5 @@
import { import {
AlignJustify,
Atom, Atom,
Logs, Logs,
Minus, Minus,
@@ -25,76 +26,120 @@ import {
CollapsibleContent, CollapsibleContent,
CollapsibleTrigger, CollapsibleTrigger,
} from "../../ui/collapsible"; } from "../../ui/collapsible";
import { useSubModuleStore } from "@/lib/store/useSubModuleStore";
const items = [ const iconMap: any = {
{ ShieldCheck: ShieldCheck,
title: "Servers", AlignJustify: AlignJustify,
url: "/servers", Settings: Settings,
icon: Server, Atom: Atom,
isActive: false, Logs: Logs,
}, Users: Users,
]; Webhook: Webhook,
const data = { Server: Server,
navMain: [
{
title: "Admin",
url: "#",
icon: ShieldCheck,
items: [
{
title: "Settings",
url: "/settings",
icon: Settings,
isActive: true,
},
{
title: "Modules",
url: "/modules",
icon: Settings,
isActive: false,
},
{
title: "Swagger",
url: "#",
icon: Webhook,
isActive: true,
},
{
title: "Logs",
url: "#",
icon: Logs,
isActive: false,
},
{
title: "Users",
url: "/users",
icon: Users,
isActive: true,
},
{
title: "UCD",
url: "https://ucd.alpla.net:8443/",
icon: Atom,
isActive: false,
newWindow: true,
},
{
title: "Lst Api",
url: "/api/docs",
icon: Webhook,
isActive: false,
},
],
},
],
}; };
export function AdminSideBar() { export function AdminSideBar() {
const { subModules } = useSubModuleStore();
const items = subModules.filter((m) => m.moduleName === "admin");
return ( return (
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel>Admin section</SidebarGroupLabel> <SidebarGroupLabel>Admin section</SidebarGroupLabel>
<SidebarGroupContent> <SidebarGroupContent>
{items.map((item: any, index) => {
const Icon = iconMap[item.icon] || AlignJustify;
// drop down menu setup
return (
<SidebarMenu key={item.name}>
{item.link === "#" ? (
<Collapsible
key={item.name}
defaultOpen={index === 1}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton>
<Icon />
{item.name}{" "}
<Plus className="ml-auto group-data-[state=open]/collapsible:hidden" />
<Minus className="ml-auto group-data-[state=closed]/collapsible:hidden" />
</SidebarMenuButton>
</CollapsibleTrigger>
{item.subSubModule?.length > 0 ? (
<CollapsibleContent>
<SidebarMenuSub>
{item.subSubModule.map(
(i: any) => {
const SubIcon =
iconMap[
i.icon
] ||
AlignJustify;
return (
<SidebarMenuSubItem
key={i.name}
>
{i.isActive && (
<SidebarMenuSubButton
asChild
>
<a
href={
i.link
}
target={
i.newWindow
? "_blank"
: "_self"
}
>
<SubIcon />
<span>
{
i.name
}
</span>
</a>
</SidebarMenuSubButton>
)}
</SidebarMenuSubItem>
);
}
)}
</SidebarMenuSub>
</CollapsibleContent>
) : null}
</SidebarMenuItem>
</Collapsible>
) : (
<SidebarMenu> <SidebarMenu>
{items.map((item) => {
if (item.link === "#") return;
return (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<a href={item.link}>
<Icon />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</SidebarMenu>
)}
</SidebarMenu>
);
})}
</SidebarGroupContent>
</SidebarGroup>
);
}
{
/* <SidebarMenu>
{data.navMain.map((item, index) => ( {data.navMain.map((item, index) => (
<Collapsible <Collapsible
key={item.title} key={item.title}
@@ -156,8 +201,5 @@ export function AdminSideBar() {
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
))} ))}
</SidebarMenu> </SidebarMenu> */
</SidebarGroupContent>
</SidebarGroup>
);
} }

View File

@@ -1,4 +1,4 @@
//import { Cylinder, Package, Truck } from "lucide-react"; import { Cylinder, Package, Truck } from "lucide-react";
import { import {
SidebarGroup, SidebarGroup,
SidebarGroupContent, SidebarGroupContent,
@@ -10,67 +10,12 @@ import {
import { hasPageAccess } from "@/utils/userAccess"; import { hasPageAccess } from "@/utils/userAccess";
import { User } from "@/types/users"; import { User } from "@/types/users";
import { useSubModuleStore } from "@/lib/store/useSubModuleStore"; import { useSubModuleStore } from "@/lib/store/useSubModuleStore";
// this will need to be moved to a links section the db to make it more easy to remove and add
// const items = [ const iconMap: any = {
// { Package: Package,
// title: "Silo Adjustments", Truck: Truck,
// url: "#", Cylinder: Cylinder,
// icon: Cylinder, };
// role: ["admin", "systemAdmin"],
// module: "logistics",
// active: true,
// },
// {
// name: "Bulk orders",
// moduleName: "logistics",
// description: "",
// link: "#",
// icon: Truck,
// role: ["systemAdmin"],
// active: true,
// subSubModule: [],
// },
// {
// name: "Forecast",
// moduleName: "logistics",
// description: "",
// link: "#",
// icon: Truck,
// role: ["systemAdmin"],
// active: true,
// subSubModule: [],
// },
// {
// name: "Ocme cycle counts",
// moduleName: "logistics",
// description: "",
// link: "#",
// icon: Package,
// role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
// active: false,
// subSubModule: [],
// },
// {
// name: "Material Helper",
// moduleName: "logistics",
// description: "",
// link: "/materialHelper/consumption",
// icon: Package,
// role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
// active: true,
// subSubModule: [],
// },
// {
// name: "Ocme Cyclecount",
// moduleName: "logistics",
// description: "",
// link: "/cyclecount",
// icon: Package,
// role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
// active: true,
// subSubModule: [],
// },
// ];
export function LogisticsSideBar({ export function LogisticsSideBar({
user, user,
@@ -82,13 +27,14 @@ export function LogisticsSideBar({
const { subModules } = useSubModuleStore(); const { subModules } = useSubModuleStore();
const items = subModules.filter((m) => m.moduleName === "logistics"); const items = subModules.filter((m) => m.moduleName === "logistics");
//console.log(items);
return ( return (
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel>Logistics</SidebarGroupLabel> <SidebarGroupLabel>Logistics</SidebarGroupLabel>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
{items.map((item) => { {items.map((item) => {
const Icon = iconMap[item.icon];
return ( return (
<SidebarMenuItem key={item.submodule_id}> <SidebarMenuItem key={item.submodule_id}>
<> <>
@@ -100,6 +46,7 @@ export function LogisticsSideBar({
item.active && ( item.active && (
<SidebarMenuButton asChild> <SidebarMenuButton asChild>
<a href={item.link}> <a href={item.link}>
<Icon />
<span>{item.name}</span> <span>{item.name}</span>
</a> </a>
</SidebarMenuButton> </SidebarMenuButton>

View File

@@ -81,6 +81,30 @@ export default function SiloCard(data: any) {
<hr className="m-2" /> <hr className="m-2" />
</div> </div>
<div> <div>
{silo.Stock_Total === 0 ? (
<div className="flex justify-center flex-col">
<span>
The silo is currently empty you will not be
able to do an adjustment until you have
received material in.
</span>
<hr />
<ul>
<li>
-Someone click "Take inventory on a
empty location" in stock.
</li>
<li>
-Silo virtualy ran empty due to
production over consumption.
</li>
<li>
-Someone forgot to move a railcar
compartment over to this location.
</li>
</ul>
</div>
) : (
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
@@ -110,8 +134,12 @@ export default function SiloCard(data: any) {
<div className="flex flex-row"> <div className="flex flex-row">
<Input <Input
name={field.name} name={field.name}
value={field.state.value} value={
onBlur={field.handleBlur} field.state.value
}
onBlur={
field.handleBlur
}
type="decimal" type="decimal"
onChange={(e) => onChange={(e) =>
field.handleChange( field.handleChange(
@@ -122,7 +150,9 @@ export default function SiloCard(data: any) {
<Button <Button
className="ml-1" className="ml-1"
type="submit" type="submit"
onClick={form.handleSubmit} onClick={
form.handleSubmit
}
disabled={submitting} disabled={submitting}
> >
{submitting ? ( {submitting ? (
@@ -137,7 +167,8 @@ export default function SiloCard(data: any) {
</Button> </Button>
</div> </div>
{field.state.meta.errors.length ? ( {field.state.meta.errors
.length ? (
<em> <em>
{field.state.meta.errors.join( {field.state.meta.errors.join(
"," ","
@@ -149,6 +180,7 @@ export default function SiloCard(data: any) {
}} }}
/> />
</form> </form>
)}
</div> </div>
</LstCard> </LstCard>
<div className="grow max-w-[400px]"> <div className="grow max-w-[400px]">

View File

@@ -0,0 +1,351 @@
import * as React from "react"
import * as RechartsPrimitive from "recharts"
import { cn } from "@/lib/utils"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
}
type ChartContextProps = {
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />")
}
return context
}
function ChartContainer({
id,
className,
children,
config,
...props
}: React.ComponentProps<"div"> & {
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
}) {
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-slot="chart"
data-chart={chartId}
className={cn(
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
)
}
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color
)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`
)
.join("\n"),
}}
/>
)
}
const ChartTooltip = RechartsPrimitive.Tooltip
function ChartTooltipContent({
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}) {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
)
}
if (!value) {
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
className={cn(
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
className
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
key={item.dataKey}
className={cn(
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
indicator === "dot" && "items-center"
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
}
)}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="text-foreground font-mono font-medium tabular-nums">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div>
</div>
)
}
const ChartLegend = RechartsPrimitive.Legend
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = "bottom",
nameKey,
}: React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}) {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
className={cn(
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
)
})}
</div>
)
}
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string
) {
if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config]
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
}

View File

@@ -21,6 +21,7 @@ import { Route as OcpIndexImport } from './routes/ocp/index'
import { Route as EomEomImport } from './routes/_eom/eom' import { Route as EomEomImport } from './routes/_eom/eom'
import { Route as AuthProfileImport } from './routes/_auth/profile' import { Route as AuthProfileImport } from './routes/_auth/profile'
import { Route as AdminUsersImport } from './routes/_admin/users' import { Route as AdminUsersImport } from './routes/_admin/users'
import { Route as AdminSubModulesImport } from './routes/_admin/subModules'
import { Route as AdminSettingsImport } from './routes/_admin/settings' import { Route as AdminSettingsImport } from './routes/_admin/settings'
import { Route as AdminServersImport } from './routes/_admin/servers' import { Route as AdminServersImport } from './routes/_admin/servers'
import { Route as AdminModulesImport } from './routes/_admin/modules' import { Route as AdminModulesImport } from './routes/_admin/modules'
@@ -28,6 +29,7 @@ import { Route as ocmeCyclecountIndexImport } from './routes/(ocme)/cyclecount/i
import { Route as logisticsSiloAdjustmentsIndexImport } from './routes/(logistics)/siloAdjustments/index' import { Route as logisticsSiloAdjustmentsIndexImport } from './routes/(logistics)/siloAdjustments/index'
import { Route as logisticsMaterialHelperIndexImport } from './routes/(logistics)/materialHelper/index' import { Route as logisticsMaterialHelperIndexImport } from './routes/(logistics)/materialHelper/index'
import { Route as EomArticleAvImport } from './routes/_eom/article/$av' import { Route as EomArticleAvImport } from './routes/_eom/article/$av'
import { Route as logisticsSiloAdjustmentsHistImport } from './routes/(logistics)/siloAdjustments/$hist'
import { Route as logisticsMaterialHelperSiloLinkIndexImport } from './routes/(logistics)/materialHelper/siloLink/index' import { Route as logisticsMaterialHelperSiloLinkIndexImport } from './routes/(logistics)/materialHelper/siloLink/index'
import { Route as logisticsMaterialHelperConsumptionIndexImport } from './routes/(logistics)/materialHelper/consumption/index' import { Route as logisticsMaterialHelperConsumptionIndexImport } from './routes/(logistics)/materialHelper/consumption/index'
import { Route as logisticsSiloAdjustmentsCommentCommentImport } from './routes/(logistics)/siloAdjustments/comment/$comment' import { Route as logisticsSiloAdjustmentsCommentCommentImport } from './routes/(logistics)/siloAdjustments/comment/$comment'
@@ -91,6 +93,12 @@ const AdminUsersRoute = AdminUsersImport.update({
getParentRoute: () => AdminRoute, getParentRoute: () => AdminRoute,
} as any) } as any)
const AdminSubModulesRoute = AdminSubModulesImport.update({
id: '/subModules',
path: '/subModules',
getParentRoute: () => AdminRoute,
} as any)
const AdminSettingsRoute = AdminSettingsImport.update({ const AdminSettingsRoute = AdminSettingsImport.update({
id: '/settings', id: '/settings',
path: '/settings', path: '/settings',
@@ -135,6 +143,13 @@ const EomArticleAvRoute = EomArticleAvImport.update({
getParentRoute: () => EomRoute, getParentRoute: () => EomRoute,
} as any) } as any)
const logisticsSiloAdjustmentsHistRoute =
logisticsSiloAdjustmentsHistImport.update({
id: '/(logistics)/siloAdjustments/$hist',
path: '/siloAdjustments/$hist',
getParentRoute: () => rootRoute,
} as any)
const logisticsMaterialHelperSiloLinkIndexRoute = const logisticsMaterialHelperSiloLinkIndexRoute =
logisticsMaterialHelperSiloLinkIndexImport.update({ logisticsMaterialHelperSiloLinkIndexImport.update({
id: '/(logistics)/materialHelper/siloLink/', id: '/(logistics)/materialHelper/siloLink/',
@@ -223,6 +238,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AdminSettingsImport preLoaderRoute: typeof AdminSettingsImport
parentRoute: typeof AdminImport parentRoute: typeof AdminImport
} }
'/_admin/subModules': {
id: '/_admin/subModules'
path: '/subModules'
fullPath: '/subModules'
preLoaderRoute: typeof AdminSubModulesImport
parentRoute: typeof AdminImport
}
'/_admin/users': { '/_admin/users': {
id: '/_admin/users' id: '/_admin/users'
path: '/users' path: '/users'
@@ -251,6 +273,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof OcpIndexImport preLoaderRoute: typeof OcpIndexImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/(logistics)/siloAdjustments/$hist': {
id: '/(logistics)/siloAdjustments/$hist'
path: '/siloAdjustments/$hist'
fullPath: '/siloAdjustments/$hist'
preLoaderRoute: typeof logisticsSiloAdjustmentsHistImport
parentRoute: typeof rootRoute
}
'/_eom/article/$av': { '/_eom/article/$av': {
id: '/_eom/article/$av' id: '/_eom/article/$av'
path: '/article/$av' path: '/article/$av'
@@ -309,6 +338,7 @@ interface AdminRouteChildren {
AdminModulesRoute: typeof AdminModulesRoute AdminModulesRoute: typeof AdminModulesRoute
AdminServersRoute: typeof AdminServersRoute AdminServersRoute: typeof AdminServersRoute
AdminSettingsRoute: typeof AdminSettingsRoute AdminSettingsRoute: typeof AdminSettingsRoute
AdminSubModulesRoute: typeof AdminSubModulesRoute
AdminUsersRoute: typeof AdminUsersRoute AdminUsersRoute: typeof AdminUsersRoute
} }
@@ -316,6 +346,7 @@ const AdminRouteChildren: AdminRouteChildren = {
AdminModulesRoute: AdminModulesRoute, AdminModulesRoute: AdminModulesRoute,
AdminServersRoute: AdminServersRoute, AdminServersRoute: AdminServersRoute,
AdminSettingsRoute: AdminSettingsRoute, AdminSettingsRoute: AdminSettingsRoute,
AdminSubModulesRoute: AdminSubModulesRoute,
AdminUsersRoute: AdminUsersRoute, AdminUsersRoute: AdminUsersRoute,
} }
@@ -351,10 +382,12 @@ export interface FileRoutesByFullPath {
'/modules': typeof AdminModulesRoute '/modules': typeof AdminModulesRoute
'/servers': typeof AdminServersRoute '/servers': typeof AdminServersRoute
'/settings': typeof AdminSettingsRoute '/settings': typeof AdminSettingsRoute
'/subModules': typeof AdminSubModulesRoute
'/users': typeof AdminUsersRoute '/users': typeof AdminUsersRoute
'/profile': typeof AuthProfileRoute '/profile': typeof AuthProfileRoute
'/eom': typeof EomEomRoute '/eom': typeof EomEomRoute
'/ocp': typeof OcpIndexRoute '/ocp': typeof OcpIndexRoute
'/siloAdjustments/$hist': typeof logisticsSiloAdjustmentsHistRoute
'/article/$av': typeof EomArticleAvRoute '/article/$av': typeof EomArticleAvRoute
'/materialHelper': typeof logisticsMaterialHelperIndexRoute '/materialHelper': typeof logisticsMaterialHelperIndexRoute
'/siloAdjustments': typeof logisticsSiloAdjustmentsIndexRoute '/siloAdjustments': typeof logisticsSiloAdjustmentsIndexRoute
@@ -372,10 +405,12 @@ export interface FileRoutesByTo {
'/modules': typeof AdminModulesRoute '/modules': typeof AdminModulesRoute
'/servers': typeof AdminServersRoute '/servers': typeof AdminServersRoute
'/settings': typeof AdminSettingsRoute '/settings': typeof AdminSettingsRoute
'/subModules': typeof AdminSubModulesRoute
'/users': typeof AdminUsersRoute '/users': typeof AdminUsersRoute
'/profile': typeof AuthProfileRoute '/profile': typeof AuthProfileRoute
'/eom': typeof EomEomRoute '/eom': typeof EomEomRoute
'/ocp': typeof OcpIndexRoute '/ocp': typeof OcpIndexRoute
'/siloAdjustments/$hist': typeof logisticsSiloAdjustmentsHistRoute
'/article/$av': typeof EomArticleAvRoute '/article/$av': typeof EomArticleAvRoute
'/materialHelper': typeof logisticsMaterialHelperIndexRoute '/materialHelper': typeof logisticsMaterialHelperIndexRoute
'/siloAdjustments': typeof logisticsSiloAdjustmentsIndexRoute '/siloAdjustments': typeof logisticsSiloAdjustmentsIndexRoute
@@ -396,10 +431,12 @@ export interface FileRoutesById {
'/_admin/modules': typeof AdminModulesRoute '/_admin/modules': typeof AdminModulesRoute
'/_admin/servers': typeof AdminServersRoute '/_admin/servers': typeof AdminServersRoute
'/_admin/settings': typeof AdminSettingsRoute '/_admin/settings': typeof AdminSettingsRoute
'/_admin/subModules': typeof AdminSubModulesRoute
'/_admin/users': typeof AdminUsersRoute '/_admin/users': typeof AdminUsersRoute
'/_auth/profile': typeof AuthProfileRoute '/_auth/profile': typeof AuthProfileRoute
'/_eom/eom': typeof EomEomRoute '/_eom/eom': typeof EomEomRoute
'/ocp/': typeof OcpIndexRoute '/ocp/': typeof OcpIndexRoute
'/(logistics)/siloAdjustments/$hist': typeof logisticsSiloAdjustmentsHistRoute
'/_eom/article/$av': typeof EomArticleAvRoute '/_eom/article/$av': typeof EomArticleAvRoute
'/(logistics)/materialHelper/': typeof logisticsMaterialHelperIndexRoute '/(logistics)/materialHelper/': typeof logisticsMaterialHelperIndexRoute
'/(logistics)/siloAdjustments/': typeof logisticsSiloAdjustmentsIndexRoute '/(logistics)/siloAdjustments/': typeof logisticsSiloAdjustmentsIndexRoute
@@ -419,10 +456,12 @@ export interface FileRouteTypes {
| '/modules' | '/modules'
| '/servers' | '/servers'
| '/settings' | '/settings'
| '/subModules'
| '/users' | '/users'
| '/profile' | '/profile'
| '/eom' | '/eom'
| '/ocp' | '/ocp'
| '/siloAdjustments/$hist'
| '/article/$av' | '/article/$av'
| '/materialHelper' | '/materialHelper'
| '/siloAdjustments' | '/siloAdjustments'
@@ -439,10 +478,12 @@ export interface FileRouteTypes {
| '/modules' | '/modules'
| '/servers' | '/servers'
| '/settings' | '/settings'
| '/subModules'
| '/users' | '/users'
| '/profile' | '/profile'
| '/eom' | '/eom'
| '/ocp' | '/ocp'
| '/siloAdjustments/$hist'
| '/article/$av' | '/article/$av'
| '/materialHelper' | '/materialHelper'
| '/siloAdjustments' | '/siloAdjustments'
@@ -461,10 +502,12 @@ export interface FileRouteTypes {
| '/_admin/modules' | '/_admin/modules'
| '/_admin/servers' | '/_admin/servers'
| '/_admin/settings' | '/_admin/settings'
| '/_admin/subModules'
| '/_admin/users' | '/_admin/users'
| '/_auth/profile' | '/_auth/profile'
| '/_eom/eom' | '/_eom/eom'
| '/ocp/' | '/ocp/'
| '/(logistics)/siloAdjustments/$hist'
| '/_eom/article/$av' | '/_eom/article/$av'
| '/(logistics)/materialHelper/' | '/(logistics)/materialHelper/'
| '/(logistics)/siloAdjustments/' | '/(logistics)/siloAdjustments/'
@@ -483,6 +526,7 @@ export interface RootRouteChildren {
AboutRoute: typeof AboutRoute AboutRoute: typeof AboutRoute
LoginRoute: typeof LoginRoute LoginRoute: typeof LoginRoute
OcpIndexRoute: typeof OcpIndexRoute OcpIndexRoute: typeof OcpIndexRoute
logisticsSiloAdjustmentsHistRoute: typeof logisticsSiloAdjustmentsHistRoute
logisticsMaterialHelperIndexRoute: typeof logisticsMaterialHelperIndexRoute logisticsMaterialHelperIndexRoute: typeof logisticsMaterialHelperIndexRoute
logisticsSiloAdjustmentsIndexRoute: typeof logisticsSiloAdjustmentsIndexRoute logisticsSiloAdjustmentsIndexRoute: typeof logisticsSiloAdjustmentsIndexRoute
ocmeCyclecountIndexRoute: typeof ocmeCyclecountIndexRoute ocmeCyclecountIndexRoute: typeof ocmeCyclecountIndexRoute
@@ -499,6 +543,7 @@ const rootRouteChildren: RootRouteChildren = {
AboutRoute: AboutRoute, AboutRoute: AboutRoute,
LoginRoute: LoginRoute, LoginRoute: LoginRoute,
OcpIndexRoute: OcpIndexRoute, OcpIndexRoute: OcpIndexRoute,
logisticsSiloAdjustmentsHistRoute: logisticsSiloAdjustmentsHistRoute,
logisticsMaterialHelperIndexRoute: logisticsMaterialHelperIndexRoute, logisticsMaterialHelperIndexRoute: logisticsMaterialHelperIndexRoute,
logisticsSiloAdjustmentsIndexRoute: logisticsSiloAdjustmentsIndexRoute, logisticsSiloAdjustmentsIndexRoute: logisticsSiloAdjustmentsIndexRoute,
ocmeCyclecountIndexRoute: ocmeCyclecountIndexRoute, ocmeCyclecountIndexRoute: ocmeCyclecountIndexRoute,
@@ -527,6 +572,7 @@ export const routeTree = rootRoute
"/about", "/about",
"/login", "/login",
"/ocp/", "/ocp/",
"/(logistics)/siloAdjustments/$hist",
"/(logistics)/materialHelper/", "/(logistics)/materialHelper/",
"/(logistics)/siloAdjustments/", "/(logistics)/siloAdjustments/",
"/(ocme)/cyclecount/", "/(ocme)/cyclecount/",
@@ -544,6 +590,7 @@ export const routeTree = rootRoute
"/_admin/modules", "/_admin/modules",
"/_admin/servers", "/_admin/servers",
"/_admin/settings", "/_admin/settings",
"/_admin/subModules",
"/_admin/users" "/_admin/users"
] ]
}, },
@@ -578,6 +625,10 @@ export const routeTree = rootRoute
"filePath": "_admin/settings.tsx", "filePath": "_admin/settings.tsx",
"parent": "/_admin" "parent": "/_admin"
}, },
"/_admin/subModules": {
"filePath": "_admin/subModules.tsx",
"parent": "/_admin"
},
"/_admin/users": { "/_admin/users": {
"filePath": "_admin/users.tsx", "filePath": "_admin/users.tsx",
"parent": "/_admin" "parent": "/_admin"
@@ -593,6 +644,9 @@ export const routeTree = rootRoute
"/ocp/": { "/ocp/": {
"filePath": "ocp/index.tsx" "filePath": "ocp/index.tsx"
}, },
"/(logistics)/siloAdjustments/$hist": {
"filePath": "(logistics)/siloAdjustments/$hist.tsx"
},
"/_eom/article/$av": { "/_eom/article/$av": {
"filePath": "_eom/article/$av.tsx", "filePath": "_eom/article/$av.tsx",
"parent": "/_eom" "parent": "/_eom"

View File

@@ -0,0 +1,31 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
export const Route = createFileRoute("/(logistics)/siloAdjustments/$hist")({
component: RouteComponent,
beforeLoad: async () => {
const auth = localStorage.getItem("auth_token");
if (!auth) {
throw redirect({
to: "/login",
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: location.pathname + location.search,
},
});
}
},
// In a loader
loader: ({ params }) => params.hist,
// Or in a component
});
function RouteComponent() {
return (
<div>
Hello "/(logistics)/siloAdjustments/$hist"! where the historical
data will be shown in a table alone with a graph
</div>
);
}

View File

@@ -1,9 +1,14 @@
import { createFileRoute } from '@tanstack/react-router' import ModulesPage from "@/components/admin/modules/ModulePage";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute('/_admin/modules')({ export const Route = createFileRoute("/_admin/modules")({
component: RouteComponent, component: RouteComponent,
}) });
function RouteComponent() { function RouteComponent() {
return <div>Hello "/_admin/modules"!</div> return (
<div>
<ModulesPage />
</div>
);
} }

View File

@@ -0,0 +1,14 @@
import SubModulePage from "@/components/admin/supModules/SubModulePage";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/_admin/subModules")({
component: RouteComponent,
});
function RouteComponent() {
return (
<div>
<SubModulePage />
</div>
);
}

View File

@@ -0,0 +1,25 @@
import { queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function getModules(token: string) {
return queryOptions({
queryKey: ["modules"],
queryFn: () => fetchSettings(token),
enabled: !!token,
//staleTime: 1000,
refetchOnWindowFocus: true,
});
}
const fetchSettings = async (token: string) => {
const { data } = await axios.get("/api/server/modules", {
headers: { Authorization: `Bearer ${token}` },
});
// if we are not localhost ignore the devDir setting.
const url: string = window.location.host.split(":")[0];
let settingsData = data.data;
if (url != "localhost") {
settingsData.filter((n: any) => n.name === "devDir");
}
return settingsData;
};

View File

@@ -0,0 +1,25 @@
import { queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function getSubModules(token: string) {
return queryOptions({
queryKey: ["submodules"],
queryFn: () => fetchSettings(token),
enabled: !!token,
//staleTime: 1000,
refetchOnWindowFocus: true,
});
}
const fetchSettings = async (token: string) => {
const { data } = await axios.get("/api/server/submodules", {
headers: { Authorization: `Bearer ${token}` },
});
// if we are not localhost ignore the devDir setting.
const url: string = window.location.host.split(":")[0];
let settingsData = data.data;
if (url != "localhost") {
settingsData.filter((n: any) => n.name === "devDir");
}
return settingsData;
};

View File

@@ -0,0 +1,26 @@
import { queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function getUserRoles() {
const token = localStorage.getItem("auth_token");
return queryOptions({
queryKey: ["getUsers"],
queryFn: () => fetchUsers(token),
enabled: !!token, // Prevents query if token is null
staleTime: 1000,
//refetchInterval: 2 * 2000,
refetchOnWindowFocus: true,
});
}
const fetchUsers = async (token: string | null) => {
const { data } = await axios.get(`/api/auth/allusersroles`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
// if we are not localhost ignore the devDir setting.
//const url: string = window.location.host.split(":")[0];
return data.data ?? [];
};

View File

@@ -0,0 +1,26 @@
import { queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function getStockSilo() {
const token = localStorage.getItem("auth_token");
return queryOptions({
queryKey: ["getUsers"],
queryFn: () => fetchStockSilo(token),
enabled: !!token, // Prevents query if token is null
staleTime: 1000,
//refetchInterval: 2 * 2000,
refetchOnWindowFocus: true,
});
}
const fetchStockSilo = async (token: string | null) => {
const { data } = await axios.get(`/api/logistics/getsilosdjustment`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
// if we are not localhost ignore the devDir setting.
//const url: string = window.location.host.split(":")[0];
return data.data ?? [];
};

View File

@@ -27,7 +27,7 @@
"commit": "cz", "commit": "cz",
"prodinstall": "npm i --omit=dev && npm run db:migrate", "prodinstall": "npm i --omit=dev && npm run db:migrate",
"checkupdates": "npx npm-check-updates", "checkupdates": "npx npm-check-updates",
"testingCode": "dotenvx run -f .env -- tsx watch server/services/notifications/controller/notifications/tiIntergrationV1.ts" "testingCode": "dotenvx run -f .env -- tsx watch server/services/logistics/controller/siloAdjustments/migrateAdjustments.ts"
}, },
"config": { "config": {
"commitizen": { "commitizen": {
@@ -35,7 +35,7 @@
} }
}, },
"admConfig": { "admConfig": {
"build": 174, "build": 181,
"oldBuild": "backend-0.1.3.zip" "oldBuild": "backend-0.1.3.zip"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -9,6 +9,7 @@ import { areRolesIn } from "./utils/roleCheck.js";
import createUser from "./routes/userAdmin/createUser.js"; import createUser from "./routes/userAdmin/createUser.js";
import allUsers from "./routes/userAdmin/getUsers.js"; import allUsers from "./routes/userAdmin/getUsers.js";
import updateUser from "./routes/userAdmin/updateUser.js"; import updateUser from "./routes/userAdmin/updateUser.js";
import allUserRoles from "./routes/userAdmin/getAllUserRoles.js";
const app = new OpenAPIHono(); const app = new OpenAPIHono();
@@ -26,6 +27,7 @@ const routes = [
setAccess, setAccess,
createUser, createUser,
allUsers, allUsers,
allUserRoles,
updateUser, updateUser,
] as const; ] as const;

View File

@@ -0,0 +1,24 @@
import { db } from "../../../../../database/dbclient.js";
import { userRoles } from "../../../../../database/schema/userRoles.js";
import { returnRes } from "../../../../globalUtils/routeDefs/returnRes.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
export const getAllUsersRoles = async () => {
/**
* returns all users that are in lst
*/
createLog("info", "apiAuthedRoute", "auth", "Get all users");
const { data, error } = await tryCatch(db.select().from(userRoles));
if (error) {
returnRes(
false,
"There was an error getting users",
new Error("No user exists.")
);
}
returnRes(true, "All users.", data);
return { success: true, message: "All users", data };
};

View File

@@ -3,15 +3,27 @@ pass over a users uuid and return all modules they have permission too.
in the login route we attach it to user under roles. in the login route we attach it to user under roles.
*/ */
import {eq} from "drizzle-orm"; import { eq } from "drizzle-orm";
import {db} from "../../../../../database/dbclient.js"; import { db } from "../../../../../database/dbclient.js";
import {userRoles} from "../../../../../database/schema/userRoles.js"; import { userRoles } from "../../../../../database/schema/userRoles.js";
export const roleCheck = async (user_id: string | undefined) => { export const roleCheck = async (user_id: string | undefined) => {
if (!user_id) { if (!user_id) {
throw Error("Missing user_id"); throw Error("Missing user_id");
} }
let returnRoles: any = [];
// get the user roles by the user_id // get the user roles by the user_id
returnRoles = await db
.select({
user_id: userRoles.user_id,
role_id: userRoles.role_id,
module_id: userRoles.module_id,
role: userRoles.role,
})
.from(userRoles)
.where(eq(userRoles.user_id, user_id));
if (returnRoles[0]?.role.includes("systemAdmin")) {
const roles = await db const roles = await db
.select({ .select({
user_id: userRoles.user_id, user_id: userRoles.user_id,
@@ -23,4 +35,7 @@ export const roleCheck = async (user_id: string | undefined) => {
.where(eq(userRoles.user_id, user_id)); .where(eq(userRoles.user_id, user_id));
return roles; return roles;
}
return returnRoles;
}; };

View File

@@ -3,21 +3,38 @@ pass over a users uuid and return all modules they have permission too.
in the login route we attach it to user under roles. in the login route we attach it to user under roles.
*/ */
import {eq} from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import {db} from "../../../../../database/dbclient.js"; import { db } from "../../../../../database/dbclient.js";
import {userRoles} from "../../../../../database/schema/userRoles.js"; import { userRoles } from "../../../../../database/schema/userRoles.js";
import {users} from "../../../../../database/schema/users.js"; import { users } from "../../../../../database/schema/users.js";
import {modules} from "../../../../../database/schema/modules.js"; import { modules } from "../../../../../database/schema/modules.js";
import {roles} from "../../../../../database/schema/roles.js"; import { roles } from "../../../../../database/schema/roles.js";
import {setSysAdmin} from "./setSysAdmin.js"; import { setSysAdmin } from "./setSysAdmin.js";
export const setUserAccess = async (username: string, moduleName: string, roleName: string, override?: string) => { export const setUserAccess = async (
username: string,
moduleName: string,
roleName: string,
override?: string
) => {
// get the user roles by the user_id // get the user roles by the user_id
const user = await db.select().from(users).where(eq(users.username, username)); const user = await db
const module = await db.select().from(modules).where(eq(modules.name, moduleName)); .select()
.from(users)
.where(eq(users.username, username));
const module = await db
.select()
.from(modules)
.where(eq(modules.name, moduleName));
if (process.env.SECRETOVERRIDECODE != override && roleName === "systemAdmin") { if (
return {success: false, message: "The override code provided is invalid."}; process.env.SECRETOVERRIDECODE != override &&
roleName === "systemAdmin"
) {
return {
success: false,
message: "The override code provided is invalid.",
};
} }
const role = await db.select().from(roles).where(eq(roles.name, roleName)); const role = await db.select().from(roles).where(eq(roles.name, roleName));
@@ -37,9 +54,12 @@ export const setUserAccess = async (username: string, moduleName: string, roleNa
// set the user // set the user
try { try {
const userRole = await db const userRole = await db.insert(userRoles).values({
.insert(userRoles) user_id: user[0].user_id,
.values({user_id: user[0].user_id, role_id: role[0].role_id, module_id: module[0].module_id, role: roleName}); role_id: role[0].role_id,
module_id: module[0].module_id,
role: roleName,
});
//.returning({user: users.username, email: users.email}); //.returning({user: users.username, email: users.email});
// return c.json({message: "User Registered", user}, 200); // return c.json({message: "User Registered", user}, 200);
@@ -48,10 +68,38 @@ export const setUserAccess = async (username: string, moduleName: string, roleNa
message: `${username} has been granted access to ${moduleName} with the role ${roleName}`, message: `${username} has been granted access to ${moduleName} with the role ${roleName}`,
}; };
} catch (error) { } catch (error) {
await changeRole(
roleName,
user[0].user_id,
module[0].module_id,
role[0].role_id
);
return { return {
success: false, success: true,
message: `There was an error granting ${username} access to ${moduleName} with the role ${roleName}`, message: `${username} access on ${moduleName} has been changed to ${roleName}`,
data: error,
}; };
} }
}; };
const changeRole = async (
role: any,
userID: any,
moduleID: any,
roleID: any
) => {
await db
.delete(userRoles)
.where(
and(
eq(userRoles.user_id, userID),
eq(userRoles.module_id, moduleID)
)
);
const userRole = await db.insert(userRoles).values({
user_id: userID,
role_id: roleID,
module_id: moduleID,
role: role,
});
};

View File

@@ -0,0 +1,34 @@
import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { authMiddleware } from "../../middleware/authMiddleware.js";
import hasCorrectRole from "../../middleware/roleCheck.js";
import { getAllUsersRoles } from "../../controllers/userAdmin/getAllUserRoles.js";
const app = new OpenAPIHono();
app.openapi(
createRoute({
tags: ["Auth:admin"],
summary: "Gets Users Roles",
method: "get",
path: "/allusersroles",
middleware: [
authMiddleware,
hasCorrectRole(["admin", "systemAdmin"], "admin"),
],
responses: responses(),
}),
async (c) => {
// apit hit
//apiHit(c, {endpoint: "api/auth/register"});
const allUsers: any = await getAllUsersRoles();
return c.json({
success: allUsers?.success,
message: allUsers?.message,
data: allUsers?.data,
});
}
);
export default app;

View File

@@ -4,7 +4,6 @@ import getArticles from "./route/getActiveArticles.js";
import currentInv from "./route/getInventory.js"; import currentInv from "./route/getInventory.js";
import getCustomerInv from "./route/getCustomerInv.js"; import getCustomerInv from "./route/getCustomerInv.js";
import getOpenOrders from "./route/getOpenOrders.js"; import getOpenOrders from "./route/getOpenOrders.js";
import siloAdjustments from "./route/getSiloAdjustments.js";
const app = new OpenAPIHono(); const app = new OpenAPIHono();
@@ -14,7 +13,6 @@ const routes = [
currentInv, currentInv,
getCustomerInv, getCustomerInv,
getOpenOrders, getOpenOrders,
siloAdjustments,
] as const; ] as const;
const appRoutes = routes.forEach((route) => { const appRoutes = routes.forEach((route) => {

View File

@@ -72,7 +72,7 @@ const current: any = [
// }, // },
{ {
name: "getSiloAdjustment", name: "getSiloAdjustment",
endpoint: "/api/v1/warehouse/getSiloAdjustment", endpoint: "/api/logistics/getsilosdjustment",
// description: "Returns all inventory, by default excludes running numebrs, also excludes inv locations.", // description: "Returns all inventory, by default excludes running numebrs, also excludes inv locations.",
description: description:
"Returns all siloadjustments in selected date range IE: 1/1/2025 to 1/31/2025", "Returns all siloadjustments in selected date range IE: 1/1/2025 to 1/31/2025",

View File

@@ -0,0 +1,79 @@
import { between, desc, gte, lte } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { siloAdjustments } from "../../../../../database/schema/siloAdjustments.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
export const getSiloAdjustments = async (startDate: any, endDate: any) => {
/**
* Returns silo adjustments by date or all
*/
if (startDate && endDate) {
const { data: adjRange, error: adjRangeError } = await tryCatch(
db
.select()
.from(siloAdjustments)
.where(
between(
siloAdjustments.dateAdjusted,
new Date(startDate),
new Date(endDate)
)
)
.orderBy(desc(siloAdjustments.dateAdjusted))
);
if (adjRangeError) {
return {
success: false,
message: "Error getting silo adjustments.",
adjRangeError,
};
}
return {
success: true,
message: "Silo adjustment data.",
data: adjRange,
};
}
if (startDate) {
const { data: adjRange, error: adjRangeError } = await tryCatch(
db
.select()
.from(siloAdjustments)
.where(gte(siloAdjustments.dateAdjusted, new Date(startDate)))
.orderBy(desc(siloAdjustments.dateAdjusted))
);
if (adjRangeError)
return {
success: false,
message: "Error getting silo adjustments.",
adjRangeError,
};
return {
success: true,
message: "Silo adjustment data.",
data: adjRange,
};
}
const { data: adjRange, error: adjRangeError } = await tryCatch(
db
.select()
.from(siloAdjustments)
.orderBy(desc(siloAdjustments.dateAdjusted))
);
if (adjRangeError)
return {
success: false,
message: "Error getting silo adjustments.",
adjRangeError,
};
return {
success: true,
message: "Silo adjustment data.",
data: adjRange,
};
};

View File

@@ -0,0 +1,91 @@
/**
* 1. Get the silo adjustments from lstv1
* 2. Build the new data set to match the new system
* 3. insert the new values
*/
import axios from "axios";
import { db } from "../../../../../database/dbclient.js";
import { siloAdjustments } from "../../../../../database/schema/siloAdjustments.js";
import { createLog } from "../../../logger/logger.js";
import { delay } from "../../../../globalUtils/delay.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { settings } from "../../../../../database/schema/settings.js";
import { eq } from "drizzle-orm";
export const migrateAdjustments = async () => {
/**
* Migrates the silo adjustments from v1 to v2
*/
const { data, error } = await tryCatch(db.select().from(settings));
if (error) {
createLog("error", "silo", "logistics", "Getting settings.");
return;
}
const migrationCompleted = data?.filter(
(n) => n.name === "siloAdjMigrations"
);
const server = data?.filter((n) => n.name === "v1SysServer");
const port = data?.filter((n) => n.name === "v1SysPort");
createLog("info", "silo", "logistics", "Getting v1 silo data.");
if (migrationCompleted[0]?.value === "1") {
createLog(
"info",
"silo",
"logistics",
"Migrations have already been completed on this server."
);
return;
}
const { data: s, error: siloError } = await tryCatch(
axios.get(
`http://${server[0].value}:${port[0].value}/api/v1/warehouse/getSilosAdjustment?startDate=1/1/2020&endDate=4/1/2026`
)
);
if (siloError) {
createLog("error", "silo", "logistics", "Getting settings.");
return;
}
/**
* Migrate all the silo adjustments :D
*/
const silo: any = s?.data;
createLog("info", "silo", "logistics", "Starting migration.");
for (let i = 0; i < silo.length; i++) {
const migrate = await db.insert(siloAdjustments).values({
warehouseID: silo[0].warehouseID,
locationID: silo[0].locationID,
currentStockLevel: silo[0].currentStockLevel,
newLevel: silo[0].newLevel,
dateAdjusted: new Date(silo[0].dateAdjusted),
lastDateAdjusted: new Date(silo[0].lastDateAdjusted),
add_user: silo[0].add_user,
});
createLog(
"info",
"silo",
"logistics",
`Migrations for Date ${silo[0].dateAdjusted} on silo: ${silo[0].locationID}`
);
await delay(120);
}
/**
* change the migration setting to be completed
*/
await db
.update(settings)
.set({ value: "1" })
.where(eq(settings.name, "siloAdjMigrations"));
createLog("info", "silo", "logistics", "Migration completed.");
};
migrateAdjustments();

View File

@@ -5,6 +5,8 @@ import returnMat from "./route/returnMaterial.js";
import createSiloAdjustment from "./route/siloAdjustments/createSiloAdjustment.js"; import createSiloAdjustment from "./route/siloAdjustments/createSiloAdjustment.js";
import postComment from "./route/siloAdjustments/postComment.js"; import postComment from "./route/siloAdjustments/postComment.js";
import getStockSilo from "./route/siloAdjustments/getStockData.js"; import getStockSilo from "./route/siloAdjustments/getStockData.js";
import { migrateAdjustments } from "./controller/siloAdjustments/migrateAdjustments.js";
import getSiloAdjustments from "./route/siloAdjustments/getSiloAdjustments.js";
const app = new OpenAPIHono(); const app = new OpenAPIHono();
@@ -16,6 +18,7 @@ const routes = [
createSiloAdjustment, createSiloAdjustment,
postComment, postComment,
getStockSilo, getStockSilo,
getSiloAdjustments,
] as const; ] as const;
// app.route("/server", modules); // app.route("/server", modules);
@@ -23,4 +26,8 @@ const appRoutes = routes.forEach((route) => {
app.route("/logistics", route); app.route("/logistics", route);
}); });
setTimeout(() => {
migrateAdjustments();
}, 10 * 1000);
export default app; export default app;

View File

@@ -1,8 +1,9 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { responses } from "../../../globalUtils/routeDefs/responses.js"; import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { tryCatch } from "../../../globalUtils/tryCatch.js"; import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { getOpenOrders } from "../controller/getOpenOrders.js"; import { getOpenOrders } from "../../../dataMart/controller/getOpenOrders.js";
import axios from "axios"; import axios from "axios";
import { getSiloAdjustments } from "../../controller/siloAdjustments/getSiloAdjustments.js";
const app = new OpenAPIHono({ strict: false }); const app = new OpenAPIHono({ strict: false });
// const Body = z.object({ // const Body = z.object({
@@ -23,7 +24,7 @@ app.openapi(
// }, // },
responses: responses(), responses: responses(),
}), }),
async (c) => { async (c: any) => {
const customer: any = c.req.queries(); const customer: any = c.req.queries();
// make sure we have a vaid user being accessed thats really logged in // make sure we have a vaid user being accessed thats really logged in
@@ -45,13 +46,20 @@ app.openapi(
const dates: any = c.req.queries(); const dates: any = c.req.queries();
// const { data, error } = await tryCatch(
// axios.get(
// `/api/v1/warehouse/getSilosAdjustment?startDate=${dates.startDate[0]}&endDate=${dates.endDate[0]}`
// )
// );
const startDate = dates.startDate ? dates.startDate[0] : null;
const endDate = dates.endDate ? dates.endDate[0] : null;
const { data, error } = await tryCatch( const { data, error } = await tryCatch(
axios.get( getSiloAdjustments(startDate, endDate)
`http://localhost:4400/api/v1/warehouse/getSilosAdjustment?startDate=${dates.startDate[0]}&endDate=${dates.endDate[0]}`
)
); );
if (error) { if (error) {
console.log(error);
return c.json({ return c.json({
success: false, success: false,
message: "Error running query", message: "Error running query",
@@ -60,9 +68,9 @@ app.openapi(
} }
return c.json({ return c.json({
success: data?.data.success, success: data?.success,
message: data?.data.message, message: data?.message,
data: data?.data.data, data: data?.data,
}); });
} }
); );

View File

@@ -59,6 +59,8 @@ app.openapi(
data.key, data.key,
payload.user payload.user
); );
console.log(addComment);
return c.json( return c.json(
{ {
success: addComment.success, success: addComment.success,

View File

@@ -214,9 +214,15 @@ export const labelingProcess = async ({
// create the label // create the label
const label = await createLabel(filteredLot[0], userPrinted); const label = await createLabel(filteredLot[0], userPrinted);
// if (!label.success) { if (!label.success) {
// return { sucess: false, message: label.message, data: label.data }; createLog(
// } "error",
"labeling",
"ocp",
`There was an error creating the label: ${label.message}`
);
return { sucess: false, message: label.message, data: label.data };
}
// send over to be booked in if we can do it. // send over to be booked in if we can do it.
const bookin = settingData.filter((s) => s.name === "bookin"); const bookin = settingData.filter((s) => s.name === "bookin");

View File

@@ -6,6 +6,7 @@ import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { lstAuth } from "../../../../index.js"; import { lstAuth } from "../../../../index.js";
import { db } from "../../../../../database/dbclient.js"; import { db } from "../../../../../database/dbclient.js";
import { createLog } from "../../../logger/logger.js"; import { createLog } from "../../../logger/logger.js";
import { assignedPrinters } from "../../utils/checkAssignments.js";
export const updatePrinters = async () => { export const updatePrinters = async () => {
const currentTime = new Date(Date.now()); const currentTime = new Date(Date.now());
@@ -80,5 +81,7 @@ export const updatePrinters = async () => {
); );
} }
await assignedPrinters();
return { success: true, message: "Printers were just added or updated." }; return { success: true, message: "Printers were just added or updated." };
}; };

View File

@@ -0,0 +1,42 @@
import { eq } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { createLog } from "../../../logger/logger.js";
import { subModules } from "../../../../../database/schema/subModules.js";
type Data = {
active: boolean;
};
export const updateSubModule = async (data: Data, subModuleID: string) => {
createLog("info", "lst", "server", "Module being updated");
let module;
console.log(data);
if (typeof data.active !== "boolean") {
createLog(
"error",
"lst",
"server",
"Invalid data type: 'active' must be a boolean"
);
throw new Error("'active' must be a boolean");
}
try {
module = await db
.update(subModules)
.set({ active: data.active })
.where(eq(subModules.submodule_id, subModuleID))
.returning({ name: subModules.name });
//.where(sql`${userRole} = ANY(roles)`);
} catch (error) {
createLog(
"error",
"lst",
"server",
"There was an error updating the module"
);
throw new Error("There was an error updating the module");
}
return module;
};

View File

@@ -0,0 +1,134 @@
import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi";
import type { User } from "../../../../types/users.js";
import { verify } from "hono/jwt";
import { authMiddleware } from "../../../auth/middleware/authMiddleware.js";
import { updateSubModule } from "../../controller/module/updateSubModule.js";
// Define the response schema
const responseSchema = z.object({
message: z.string().optional(),
module_id: z
.string()
.openapi({ example: "6c922c6c-7de3-4ec4-acb0-f068abdc" })
.optional(),
name: z.string().openapi({ example: "Production" }).optional(),
active: z.boolean().openapi({ example: true }).optional(),
roles: z
.string()
.openapi({ example: `["viewer","technician"]` })
.optional(),
});
const app = new OpenAPIHono();
const ParamsSchema = z.object({
id: z
.string()
.min(3)
.openapi({
param: {
name: "id",
in: "path",
},
example: "1212121",
}),
});
app.openapi(
createRoute({
tags: ["server"],
summary: "Updates submodule",
method: "patch",
path: "/submodules/{id}",
middleware: authMiddleware,
request: {
params: ParamsSchema,
},
responses: {
200: {
content: {
"application/json": { schema: responseSchema },
},
description: "Response message",
},
400: {
content: {
"application/json": {
schema: z.object({
message: z
.string()
.optional()
.openapi({ example: "Internal Server error" }),
}),
},
},
description: "Internal Server Error",
},
401: {
content: {
"application/json": {
schema: z.object({
message: z
.string()
.optional()
.openapi({ example: "Unauthenticated" }),
}),
},
},
description: "Unauthorized",
},
500: {
content: {
"application/json": {
schema: z.object({
message: z
.string()
.optional()
.openapi({ example: "Internal Server error" }),
}),
},
},
description: "Internal Server Error",
},
},
}),
async (c) => {
const { id } = c.req.valid("param");
// make sure we have a vaid user being accessed thats really logged in
const authHeader = c.req.header("Authorization");
const token = authHeader?.split("Bearer ")[1] || "";
let user: User;
try {
const payload = await verify(token, process.env.JWT_SECRET!);
user = payload.user as User;
} catch (error) {
return c.json({ message: "Unauthorized" }, 401);
}
// now pass all the data over to update the user info
try {
const data = await c?.req.json();
await updateSubModule(data, id ?? "");
return c.json({ success: true, message: "Module Updated" }, 200);
} catch (error) {
return c.json(
{
message: "Please make sure you are not missing your data.",
error,
},
400
);
}
return c.json({
message: `Module has been updated`,
data: id,
});
}
);
export default app;

View File

@@ -16,6 +16,7 @@ import { setPerms } from "./utils/testServerPerms.js";
import serviceControl from "./route/servers/serverContorl.js"; import serviceControl from "./route/servers/serverContorl.js";
import { areSubModulesIn } from "./utils/subModuleCheck.js"; import { areSubModulesIn } from "./utils/subModuleCheck.js";
import getSubmodules from "./route/modules/getSubModules.js"; import getSubmodules from "./route/modules/getSubModules.js";
import updateSubModule from "./route/modules/updateSubModules.js";
// making sure all modules are in properly // making sure all modules are in properly
setTimeout(async () => { setTimeout(async () => {
@@ -33,6 +34,7 @@ const routes = [
updateModule, updateModule,
addModule, addModule,
getSubmodules, getSubmodules,
updateSubModule,
// settings // settings
addSetting, addSetting,
getSettings, getSettings,

View File

@@ -207,6 +207,14 @@ const newSettings = [
serviceBelowsTo: "ocp", serviceBelowsTo: "ocp",
roleToChange: "admin", roleToChange: "admin",
}, },
// temp settings can be deleted at a later date once that code is removed
{
name: "siloAdjMigrations",
value: `0`,
description: "Migrates the old silo adjustments to lst v2.",
serviceBelowsTo: "admin",
roleToChange: "admin",
},
]; ];
export const areSettingsIn = async () => { export const areSettingsIn = async () => {
// get the roles // get the roles

View File

@@ -101,7 +101,14 @@ const newSubModules = [
link: "/modules", link: "/modules",
icon: "Settings", icon: "Settings",
newWindow: false, newWindow: false,
isActive: false, isActive: true,
},
{
name: "Sub Modules",
link: "/subModules",
icon: "Settings",
newWindow: false,
isActive: true,
}, },
{ {
name: "Swagger", name: "Swagger",