Compare commits
120 Commits
6ce4d84fd0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dcfa56bdb9 | |||
| ea92422bb1 | |||
| 2111a5fdc9 | |||
| 6edd20585f | |||
| a9759795c4 | |||
| 32f26a1725 | |||
| 60533beed5 | |||
| 24ced97b6d | |||
| dc1d342799 | |||
| 44d0cb63cf | |||
| ace73fa919 | |||
| 316af4233f | |||
| 36a805c652 | |||
| 460bc3d24a | |||
| ec201fcfb5 | |||
| 914ad46c43 | |||
| b96c546ed3 | |||
| 29b3be41a1 | |||
| 16edf58025 | |||
| 775627f215 | |||
| 4e70fae69b | |||
| 24dd109a21 | |||
| 38b57a00cc | |||
| f8070db95f | |||
| 10e9dc430c | |||
| 6b669ccd9c | |||
| d9a10d98a1 | |||
| e64dc7c013 | |||
| d63138d746 | |||
| 84a28f2d01 | |||
| 9be6614972 | |||
| 9d0db71f6a | |||
| 3cc55436f3 | |||
| 124fde07e0 | |||
| b15d0d7322 | |||
| 0680f332fb | |||
| 46bf310dce | |||
| 0dda6ae744 | |||
| 1b59cdd3a4 | |||
| 56934216f7 | |||
| e8a2ef8b85 | |||
| 6cbffa4ac5 | |||
| 09f16f4e62 | |||
| 461acb2b16 | |||
| 0d05c66a2b | |||
| 096cc18477 | |||
| f3333ce020 | |||
| 8e3d2b3d95 | |||
| 501709546d | |||
| 2b5e77993b | |||
| 6efaffbb17 | |||
| 90ddbca2e7 | |||
| 7a9ea16f48 | |||
| 420826de9b | |||
| dc2d3718fa | |||
| 5013228384 | |||
| 4459742cf0 | |||
| 070c3ee975 | |||
| 8ac92888ad | |||
| 567579ef35 | |||
| 8d90f27514 | |||
| 722b23a321 | |||
| ba48c5307f | |||
| 30d2ec0477 | |||
| d3c6444491 | |||
| 12345c0b64 | |||
| 6833dfc992 | |||
| ac27a286c0 | |||
| a3dba6cc9d | |||
| 320dd47aea | |||
| 712a6eebdf | |||
| f226c5644c | |||
| d605225e48 | |||
| 8e7f1eb098 | |||
| 59c6fd0117 | |||
| 2607fd3026 | |||
| bdb4bfc53d | |||
| c1816c07ff | |||
| 7311372ba8 | |||
| cd53460bec | |||
| 7e15e5d7bc | |||
| 3193e07e47 | |||
| 40bc19aa6f | |||
| 90920e8fba | |||
| f8cf0851a8 | |||
| efdab5bafc | |||
| 700346d809 | |||
| a96b85bc53 | |||
| b23bb0db31 | |||
| 8c0f67ca35 | |||
| 7b6c9bdfbf | |||
| 7b28f4e9ef | |||
| a30eebf5d3 | |||
| 9aa0b31278 | |||
| 33cbb17a0e | |||
| 242ff6277a | |||
| 566754bf2e | |||
| 50b7c9cac5 | |||
| b0ac326752 | |||
| 9572b71592 | |||
| 96c3e4c24a | |||
| aba1668d2c | |||
| 20fc286069 | |||
| 75c0659658 | |||
| 564f0b5add | |||
| 7b630d5c0b | |||
| eb6b9ce388 | |||
| c777395b03 | |||
| c509c7fe28 | |||
| 6f632ecd68 | |||
| fef0303cd6 | |||
| 314ab049bb | |||
| 5277ddfc51 | |||
| b6030de4f4 | |||
| 1084cede04 | |||
| ca866bf8c6 | |||
| 9d793d2205 | |||
| 247010d48f | |||
| 7c40f028c8 | |||
| 65304f61ce |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@ controllerBuilds
|
||||
# ignoring the old app that will be built into this one to make deploying faster and more easy as we do the migration
|
||||
lstV2/frontend/.tanstack
|
||||
|
||||
keys
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
1
.include
1
.include
@@ -1,5 +1,6 @@
|
||||
dist
|
||||
frontend/dist
|
||||
mobileLst/dist
|
||||
lstDocs/build
|
||||
migrations
|
||||
Dockerfile
|
||||
|
||||
48
.vscode/settings copy.json.bak
vendored
48
.vscode/settings copy.json.bak
vendored
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"workbench.colorTheme": "Default Dark+",
|
||||
"prettier.tabWidth": 4,
|
||||
"terminal.integrated.env.windows": {},
|
||||
"editor.formatOnSave": true,
|
||||
"[javascript]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[json]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[graphql]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[handlebars]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[go]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "golang.go"
|
||||
},
|
||||
"[powershell]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "ms-vscode.powershell" // requires PowerShell extension
|
||||
},
|
||||
"[bat]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format" // supports .sh, .bat, .cmd
|
||||
},
|
||||
"[cmd]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||
},
|
||||
|
||||
// Optional: Configure goimports instead of gofmt
|
||||
"go.formatTool": "goimports",
|
||||
"cSpell.words": ["alpla", "alplamart", "alplaprod", "ppoo"]
|
||||
}
|
||||
51
.vscode/settings.json
vendored
51
.vscode/settings.json
vendored
@@ -7,13 +7,60 @@
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[json]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[graphql]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[handlebars]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[go]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "golang.go"
|
||||
},
|
||||
"[powershell]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "ms-vscode.powershell" // requires PowerShell extension
|
||||
},
|
||||
"[bat]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format" // supports .sh, .bat, .cmd
|
||||
},
|
||||
"[cmd]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||
},
|
||||
|
||||
// Optional: Configure goimports instead of gofmt
|
||||
"go.formatTool": "goimports",
|
||||
"cSpell.words": [
|
||||
"acitve",
|
||||
"actaully",
|
||||
"alpla",
|
||||
"alplamart",
|
||||
"alplaprod",
|
||||
"autoconsume",
|
||||
"intiallally",
|
||||
"ppoo",
|
||||
"prodlabels"
|
||||
]
|
||||
"prodlabels",
|
||||
"rfid"
|
||||
],
|
||||
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",
|
||||
"gitea.instanceURL": "https://git.tuffraid.net",
|
||||
"gitea.owner": "cowch",
|
||||
"gitea.repo": "lst"
|
||||
}
|
||||
|
||||
133
CHANGELOG.md
133
CHANGELOG.md
@@ -1,5 +1,138 @@
|
||||
# All Changes to LST can be found below.
|
||||
|
||||
## [1.9.0](https://git.tuffraid.net/cowch/lst/compare/v1.8.0...v1.9.0) (2025-12-03)
|
||||
|
||||
|
||||
### 📝 Chore
|
||||
|
||||
* **module updates:** just updated all the modules ([bdb4bfc](https://git.tuffraid.net/cowch/lst/commits/bdb4bfc53d24f37f0e7098ea828cf418d58d5224))
|
||||
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **datamart:** active article moved over to the new version ([8e7f1eb](https://git.tuffraid.net/cowch/lst/commits/8e7f1eb09811fcf3ea49b95b0ba9a8f55b9c4184))
|
||||
* **dm:** added article description into the historical data ([7311372](https://git.tuffraid.net/cowch/lst/commits/7311372ba8eb901b51972ca216152bcfc2b009af))
|
||||
* **swagger:** added in the start of swagger where all the common and useable endpoints will be ([2607fd3](https://git.tuffraid.net/cowch/lst/commits/2607fd3026ed0b5777a5598aa3498ffc67baa012))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **contorller:** only install npm production modules dont install everything ([c1816c0](https://git.tuffraid.net/cowch/lst/commits/c1816c07ff5ac939b0997d314a9da624a4a66b7a))
|
||||
* **helpercommands:** removed the remove as reusabele ([a3dba6c](https://git.tuffraid.net/cowch/lst/commits/a3dba6cc9db147ff4765fef648867e50878a6ac8))
|
||||
* **quality:** added a check to monior [#7](https://git.tuffraid.net/cowch/lst/issues/7) as well ([6833dfc](https://git.tuffraid.net/cowch/lst/commits/6833dfc9929741203083b01726b83a6c8d61d308))
|
||||
* **sql:** some changes to help with sql connection on random disconnect ([320dd47](https://git.tuffraid.net/cowch/lst/commits/320dd47aea017b4ff219b07e363ef87ec8523b82))
|
||||
* **swagger:** corrected the name displaced ([d605225](https://git.tuffraid.net/cowch/lst/commits/d605225e48bca66f915ce0db448aa61933891986))
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **commands:** corrections to allow external labels to be consumed and transfered ([12345c0](https://git.tuffraid.net/cowch/lst/commits/12345c0b6442c3abd309f660bb43216def9abb89))
|
||||
* **dm:** type in customer article number ([f226c56](https://git.tuffraid.net/cowch/lst/commits/f226c5644cc2b93b9d967962bd6f82b3e506c8c0))
|
||||
* **labeling:** added in a catch to avoid rouge lots ([59c6fd0](https://git.tuffraid.net/cowch/lst/commits/59c6fd011728dff50bfa3233d6095c396d0b1999))
|
||||
* **lot transfer:** changes to make it so the reprint and return do not happen instantly ([ac27a28](https://git.tuffraid.net/cowch/lst/commits/ac27a286c07733333703d8421cfa525691363e54))
|
||||
* **lstv2:** added in a close function to stop crashing the server ([712a6ee](https://git.tuffraid.net/cowch/lst/commits/712a6eebdfef0ce2b99155d23422ddc7e5e0daad))
|
||||
|
||||
## [1.8.0](https://git.tuffraid.net/cowch/lst/compare/v1.7.0...v1.8.0) (2025-11-25)
|
||||
|
||||
|
||||
### 📝 Chore
|
||||
|
||||
* **misc:** api doc dates and db changes ([52089ec](https://git.tuffraid.net/cowch/lst/commits/52089ecdf06d9e5983afb6a24953d066a0456949))
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **settings:** changes to vs code as it was being way to slow ([5277ddf](https://git.tuffraid.net/cowch/lst/commits/5277ddfc51632651059e35b799ddf2a63d70cb70))
|
||||
|
||||
|
||||
### 📝 Testing Code
|
||||
|
||||
* **android app:** this is the start to the android app ([7b630d5](https://git.tuffraid.net/cowch/lst/commits/7b630d5c0b1175f06c866b1238754e455d3a27c9))
|
||||
* **controller:** added in server port that might night be needed but oh well ([efdab5b](https://git.tuffraid.net/cowch/lst/commits/efdab5bafca2a46d73de7ab1d776d283d5294aa3))
|
||||
* **materials per day:** work on getting this running better ([a30eebf](https://git.tuffraid.net/cowch/lst/commits/a30eebf5d34c77c6c7118faf01776651f8888547))
|
||||
* **mobile:** more testing on ota ([75c0659](https://git.tuffraid.net/cowch/lst/commits/75c0659658cbbb7e983df5538538d6429a325379))
|
||||
* **mobile:** stallion intergration starts ([20fc286](https://git.tuffraid.net/cowch/lst/commits/20fc2860690f59851ca1e0251df84854c9d2ba01))
|
||||
* **mobile:** testing for ota updated on android scanner ([314ab04](https://git.tuffraid.net/cowch/lst/commits/314ab049bb650120489259e920e52fd530f0ce41))
|
||||
* **ti intergration:** added the place holder for intergration of comments for customer ([f8cf085](https://git.tuffraid.net/cowch/lst/commits/f8cf0851a86a6923a8941774efe17f93cb92e984))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **all server stats:** added a 5 second time out if it dose not reach it just stops ([2133b94](https://git.tuffraid.net/cowch/lst/commits/2133b94a1dd84e16bb5a4b8fe215739a4b355223))
|
||||
* **app:** refactored how we have the pkg.json so we can reduce on size of the app ([90920e8](https://git.tuffraid.net/cowch/lst/commits/90920e8fba4757297e0e42e86f80d5f14434a48e))
|
||||
* **base modules:** removed the log spam ([6d27a7a](https://git.tuffraid.net/cowch/lst/commits/6d27a7aa6395a094d8763ba3fde0bdb81a7e3082))
|
||||
* **cards:** trying to make the cards fit the odd shaped screens better ([33cbb17](https://git.tuffraid.net/cowch/lst/commits/33cbb17a0ede136a9be96e47ba0a7a66468b1ebc))
|
||||
* **forklifts:** more refactoring to improve during production ([8c0f67c](https://git.tuffraid.net/cowch/lst/commits/8c0f67ca351778405279f7e225ee8dae654033f9))
|
||||
* **inv with rn:** now includes batch number for tetra ([1084ced](https://git.tuffraid.net/cowch/lst/commits/1084cede04d43ec2b2c22c43c6e701bad4701981))
|
||||
* **leases:** removed main server until i have a better way to sync them ([6ce4d84](https://git.tuffraid.net/cowch/lst/commits/6ce4d84fd00fa446ccb7d1bbad28680f045fae52))
|
||||
* **ocp page:** using the name of the url now vs the settings ([d406a92](https://git.tuffraid.net/cowch/lst/commits/d406a92f3d5d6a8902164e9182717912debae804))
|
||||
* **ocp:** work around for zechetti 2 until we can monitor more closing ([700346d](https://git.tuffraid.net/cowch/lst/commits/700346d80972e464d0a9ba62bba4dc0ed949cdee))
|
||||
* **quality:** added some new options plus cancel button ([242ff62](https://git.tuffraid.net/cowch/lst/commits/242ff6277a1f407fbed2951d30dd6cf1ee32dd60))
|
||||
* **quality:** more changes to the system to perfect it ([7b28f4e](https://git.tuffraid.net/cowch/lst/commits/7b28f4e9ef32a4fc90a0b4b16953b6cead096cac))
|
||||
* **serverlist:** refactored to also show uptime and other info about the server ([e1e659f](https://git.tuffraid.net/cowch/lst/commits/e1e659f9b14f22474f919350f07b02b45141aa63))
|
||||
* **types:** moved the item type to the sidebar to keep it more clean ([5023d4d](https://git.tuffraid.net/cowch/lst/commits/5023d4d129737cf6e0609592e5606a20a0f3728b))
|
||||
* **wrapper:** removed the logs so its not spamming the server ([b8a9aa5](https://git.tuffraid.net/cowch/lst/commits/b8a9aa5132c7606fcccae8f058a77a11a8ed552a))
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **comments:** added new role to put comments in ([1283a63](https://git.tuffraid.net/cowch/lst/commits/1283a63b5fd71fb44f7ec7789f670f8af7eafbb8))
|
||||
* **correction:** the name needed to be correct to have a proper tempalte ([b1c56ee](https://git.tuffraid.net/cowch/lst/commits/b1c56ee4bb32c0dbf86e0164614fb3f1ecaf262d))
|
||||
* **dm:** correction to passing the username over for the importing of the file ([a7a9aa2](https://git.tuffraid.net/cowch/lst/commits/a7a9aa2874ddd1391b56983db51cfabd8e789213))
|
||||
* **dm:** fixes to validate auth before submiting incase someone stays on the page to long ([fef0303](https://git.tuffraid.net/cowch/lst/commits/fef0303cd6fdc9cc8cf9f9f4ad674a8b725691f3))
|
||||
* **file name wrong:** fixed the name of the file for getCompanies ([8996da7](https://git.tuffraid.net/cowch/lst/commits/8996da7eb46a8b5bdfe44ee74a676e701d64fdbf))
|
||||
* **forecast table:** correction to the customer article number ([ebe5c0b](https://git.tuffraid.net/cowch/lst/commits/ebe5c0bd5a883b1cbe87f7f9932fd803e80e7fae))
|
||||
* **historical date:** added so we can have all dates ([9d793d2](https://git.tuffraid.net/cowch/lst/commits/9d793d22051c585ed224bfaf16e2a9b60bb02635))
|
||||
* **historical inv:** corrected the way the date can come over to allow for yyyy-mm-dd or with / ([7c40f02](https://git.tuffraid.net/cowch/lst/commits/7c40f028c88d7fd78ac8ab75c172d808783fc641)), closes [#1](https://git.tuffraid.net/cowch/lst/issues/1)
|
||||
* **historicalinv:** removed the second running one that caused duplicates ([a6cc17c](https://git.tuffraid.net/cowch/lst/commits/a6cc17ccb12b0d99ffdb1d371c5daf3bbb91f7ba))
|
||||
* **label ratio:** correction to the endpoint ([50b7c9c](https://git.tuffraid.net/cowch/lst/commits/50b7c9cac5cd6923b08a8705fc8cb41530ec5b02))
|
||||
* **manual print:** fixed so the print disables ([d3e8e94](https://git.tuffraid.net/cowch/lst/commits/d3e8e941103dc0118066e8790e7c27e5f035a6c5))
|
||||
* **nav:** added missing add card button ([8fca201](https://git.tuffraid.net/cowch/lst/commits/8fca201e0463aba7ecace61f8dfb737e2acf4140))
|
||||
* **ocp:** made corrections to the ocp page in dayton ([92af726](https://git.tuffraid.net/cowch/lst/commits/92af7262f60514501b903f5307d34e9154cc9034))
|
||||
* **plc zeccetti:** changes to improve the timing on the zecetti more to be done ([247010d](https://git.tuffraid.net/cowch/lst/commits/247010d48f10ebb02a1b98c5df101134e8dab250))
|
||||
* **preprint:** added the correct to string for the preprint stuff ([360c016](https://git.tuffraid.net/cowch/lst/commits/360c0163f1d3135d9c1c3788ac53dc8e0757c441))
|
||||
* **prodendpoint:** if we have a real error just report it dont actually crash ([3193e07](https://git.tuffraid.net/cowch/lst/commits/3193e07e4707d055517b15f77ac117fefe07de12))
|
||||
* **quality request:** bug fixes ([6f632ec](https://git.tuffraid.net/cowch/lst/commits/6f632ecd6831456c6e3c9973bc0ce7feb229aeec))
|
||||
* **quality:** corrected url and perms for quality link ([eb6b9ce](https://git.tuffraid.net/cowch/lst/commits/eb6b9ce388c5dea35f95a9403765e7d330b664f9))
|
||||
* **quality:** request was missing the forced selection ([96c3e4c](https://git.tuffraid.net/cowch/lst/commits/96c3e4c24adbdc59d11f7ea43888e1c47d061f90))
|
||||
* **register:** added the ability to put in _ for user name ([aba1668](https://git.tuffraid.net/cowch/lst/commits/aba1668d2cab63a031657fb7c9f2bfb9777fa72a))
|
||||
* **servers:** changed the server name to be unique ([a7bde5e](https://git.tuffraid.net/cowch/lst/commits/a7bde5e4eb41c597f94302dd2d119f7048c18a6f))
|
||||
* **silo adjustments:** added supervisor to see the actual page so it matches the sidbard ([9aa0b31](https://git.tuffraid.net/cowch/lst/commits/9aa0b31278e5f8201acd21774f19ba69709a654d))
|
||||
* **silo commits:** added in email that was now missing due to new authj ([25a958d](https://git.tuffraid.net/cowch/lst/commits/25a958d592d189f896ae0b5f7608d80a6ee2b1e7))
|
||||
* **uom:** correction to how we do the uom check so we dont just look for the space ([ca866bf](https://git.tuffraid.net/cowch/lst/commits/ca866bf8c63e0576e890367d24a47c7ab46cc864))
|
||||
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **added in swagger:** added the base for swagger to implement fully later ([9d9ca63](https://git.tuffraid.net/cowch/lst/commits/9d9ca63d7c9ab3e3ea168cf2add9c7baf2b9ed15))
|
||||
* **articles:** moved articles over to the main server ([2a6eafa](https://git.tuffraid.net/cowch/lst/commits/2a6eafa19a97f0be01f63c68b63b4abfc4de1409))
|
||||
* **barcode gen:** added the missing link ([b4064e8](https://git.tuffraid.net/cowch/lst/commits/b4064e87691937ad9f99441767b556a167b91055))
|
||||
* **db manual fixes:** added a way to fix manual db changes as needed ([0b02984](https://git.tuffraid.net/cowch/lst/commits/0b0298423ed75eed6d112a04dda998b8a23b20ea))
|
||||
* **dm:** new endpoint to get the forecast data ([a96b85b](https://git.tuffraid.net/cowch/lst/commits/a96b85bc536809d223dd7a29150d1a4d632e80da))
|
||||
* **forecast data:** added in a historical forecast data set ([c2ae445](https://git.tuffraid.net/cowch/lst/commits/c2ae445ea4d26b047a2ee5d16041ed230f7b2061))
|
||||
* **forklifts:** added backend forklift stuff and frontend companies ([50cde2d](https://git.tuffraid.net/cowch/lst/commits/50cde2d8d2aa24796db1f1c0126ef8c373614d5d))
|
||||
* **forklifts:** added the ability to add new forklifts in ([7b6c9bd](https://git.tuffraid.net/cowch/lst/commits/7b6c9bdfbf2cf9d97c8e23d8ebd6523e32284963))
|
||||
* **forklifts:** added the crud ([577584e](https://git.tuffraid.net/cowch/lst/commits/577584ef4dd10ee7f57ab0ad0d6261adddaf8966))
|
||||
* **form stuff:** added in a searchable dropdown and added to new forklifts ([b23bb0d](https://git.tuffraid.net/cowch/lst/commits/b23bb0db31f78f46ffc556577cadb62e0bfa3b83))
|
||||
* **invoice form:** added new invoice form ([65304f6](https://git.tuffraid.net/cowch/lst/commits/65304f61ceb3ad4655757aa5c291ac4ed77db048))
|
||||
* **invoices:** added invoice + linking to forklift ([2e05f6e](https://git.tuffraid.net/cowch/lst/commits/2e05f6eeee052a92095098c73ace0bd331c43b22))
|
||||
* **leases:** added in leases and move table to reuseable component ([bd7bea8](https://git.tuffraid.net/cowch/lst/commits/bd7bea8db697f5b025b8d93f86677a9a69cdf2b4))
|
||||
* **listeners:** added in a new feature to auto add new listeners ([f9cfada](https://git.tuffraid.net/cowch/lst/commits/f9cfada8409b3a88323dafa80730c5565c067da8))
|
||||
* **materials per day:** more work on materials per day ([564f0b5](https://git.tuffraid.net/cowch/lst/commits/564f0b5addd109018a806edd6a1fed4399ea63aa))
|
||||
* **migration:** settings migration from old app all is now in the new app ([40bc19a](https://git.tuffraid.net/cowch/lst/commits/40bc19aa6f952a7a60b5ee8281fa159ca114161f))
|
||||
* **missing inv:** adding a way to check for missing data in case it dose pull on the correct days ([d17edb1](https://git.tuffraid.net/cowch/lst/commits/d17edb1f9c830a2c17d28bd9180d264607d66fa2))
|
||||
* **mobile:** ota updates added ([b6030de](https://git.tuffraid.net/cowch/lst/commits/b6030de4f44e73ce8bb9152886d384b9d7f2edff))
|
||||
* **notify:** material per day for the next 90 days ([c509c7f](https://git.tuffraid.net/cowch/lst/commits/c509c7fe286a43ab0ffbf86635631477237632b5))
|
||||
* **quality:** added in comments ([566754b](https://git.tuffraid.net/cowch/lst/commits/566754bf2ecfc390bc927b48aadb2fa934353769))
|
||||
* **quality:** added location moved to to the table ([9572b71](https://git.tuffraid.net/cowch/lst/commits/9572b7159235c18617ff46058c94dfd9cfab8abc))
|
||||
* **quality:** priority ranking added ([c777395](https://git.tuffraid.net/cowch/lst/commits/c777395b0350f60bd457c3164ed1ae478249df3a))
|
||||
* **scroll view:** added in a scroll view to for quality so it dose not go over the end of the page ([b0ac326](https://git.tuffraid.net/cowch/lst/commits/b0ac326752331ab01ad981fa7b1022e82beab143))
|
||||
* **servers:** added a link to the server by clicking on the name and the gp code ([00ef72d](https://git.tuffraid.net/cowch/lst/commits/00ef72de90e43c12bd3fecdc08dfa1e3a4f881fb))
|
||||
* **settings:** added in dyco printing settings ([2ed6bf4](https://git.tuffraid.net/cowch/lst/commits/2ed6bf4d1f32f9a92712ccb36d4a4146ca112e85))
|
||||
* **settings:** final migration of settings and edits added ([7e15e5d](https://git.tuffraid.net/cowch/lst/commits/7e15e5d7bcdf58f31bd96564be1f213d01d37cda))
|
||||
* **start of server:** added the start of server data ([d60c08a](https://git.tuffraid.net/cowch/lst/commits/d60c08a281cd63f2183381a1a19c5e196b41fbc5))
|
||||
* **templates:** added bug repot template ([79f4121](https://git.tuffraid.net/cowch/lst/commits/79f4121311df733f5dc59b32a6b32c1b4a32f97b))
|
||||
|
||||
## [1.7.0](https://git.tuffraid.net/cowch/lst/compare/v1.6.0...v1.7.0) (2025-10-30)
|
||||
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/old/api/eom/histinv?month=2025/11/01
|
||||
url: {{url}}/lst/old/api/eom/histinv?month=2025/11/1
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
month: 2025/11/01
|
||||
month: 2025/11/1
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Error logging
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{urlv2}}/api/notify/toomanyerrors
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: materialPerDay
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{urlv2}}/api/notify/materialperday
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: ti Intergration
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{urlv2}}/api/notify/tiTrigger
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
25
LogisticsSupportTool_API_DOCS/LstV2/Quality/Add pallet.bru
Normal file
25
LogisticsSupportTool_API_DOCS/LstV2/Quality/Add pallet.bru
Normal file
@@ -0,0 +1,25 @@
|
||||
meta {
|
||||
name: Add pallet
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/lst/old/api/quality/newrequest
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"username": "matthes01",
|
||||
"runningNr": 618302,
|
||||
"palletStatusText":"return" // returned will be the only allowed key
|
||||
//"moveTo": "hold area" //hold area, rework, inspection
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
LogisticsSupportTool_API_DOCS/LstV2/Quality/Get Pallets.bru
Normal file
16
LogisticsSupportTool_API_DOCS/LstV2/Quality/Get Pallets.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Get Pallets
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/old/api/quality/getrequest
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
LogisticsSupportTool_API_DOCS/LstV2/Quality/folder.bru
Normal file
8
LogisticsSupportTool_API_DOCS/LstV2/Quality/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Quality
|
||||
seq: 7
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
22
LogisticsSupportTool_API_DOCS/LstV2/Warehouse/sscc.bru
Normal file
22
LogisticsSupportTool_API_DOCS/LstV2/Warehouse/sscc.bru
Normal file
@@ -0,0 +1,22 @@
|
||||
meta {
|
||||
name: sscc
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/lst/old/api/logistics/getsscc
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"runningNr": ""
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: PSI - Forecast data
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/old/api/datamart/psiforecastdata?customer=8
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
customer: 8
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
meta {
|
||||
name: PSI -planning data
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/old/api/datamart/psiplanningdata?avs=118,120&startDate=12/1/2025&endDate=12/31/2026
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
avs: 118,120
|
||||
startDate: 12/1/2025
|
||||
endDate: 12/31/2026
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
LogisticsSupportTool_API_DOCS/LstV2/datamart/folder.bru
Normal file
8
LogisticsSupportTool_API_DOCS/LstV2/datamart/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: datamart
|
||||
seq: 8
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
26
LogisticsSupportTool_API_DOCS/LstV2/ocp/Logs.bru
Normal file
26
LogisticsSupportTool_API_DOCS/LstV2/ocp/Logs.bru
Normal file
@@ -0,0 +1,26 @@
|
||||
meta {
|
||||
name: Logs
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/old/api/logger/logs?service=ocp&service=rfid&service=dyco&level=error&level=info&level=warn&hours=12
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
service: ocp
|
||||
service: rfid
|
||||
service: dyco
|
||||
level: error
|
||||
level: info
|
||||
level: warn
|
||||
hours: 12
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -5,11 +5,15 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/api/forklifts/leases
|
||||
url: {{url}}/lst/api/forklifts/leases?companyId=b34c6684-ec35-4364-acef-0c1570faf123
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
companyId: b34c6684-ec35-4364-acef-0c1570faf123
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"name":"Delage DLL"
|
||||
|
||||
@@ -12,10 +12,10 @@ post {
|
||||
|
||||
body:json {
|
||||
{
|
||||
"leaseNumber":"500-50489192",
|
||||
"leaseNumber":"40829107-1",
|
||||
"startDate": "11/08/2023",
|
||||
"endDate": "11/12/2025",
|
||||
"companyId": "b34c6684-ec35-4364-acef-0c1570faf123"
|
||||
"companyId": "59c4eaa3-55db-4348-a033-f2fcd91a91d1"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
meta {
|
||||
name: Consume
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/lst/old/api/logistics/consume
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"lotNum":283559,
|
||||
"runningNr":19302907
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Consume
|
||||
seq: 5
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: demandmgt
|
||||
seq: 4
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: get forecast data
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/api/logistics/dm/forecastData
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: SSCC
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/api/logistics/getsscc
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
20
LogisticsSupportTool_API_DOCS/app/system/Update Setting.bru
Normal file
20
LogisticsSupportTool_API_DOCS/app/system/Update Setting.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Update Setting
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/lst/api/system/settings/:token
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:path {
|
||||
token: test3
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "LogisticsSupportTool_API_DOCS",
|
||||
"name": "lstv2",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
vars {
|
||||
url: http://localhost:5500
|
||||
session_cookie:
|
||||
urlv2: http://localhost:3000
|
||||
urlv2: http://usbow1vms006:3000
|
||||
jwtV2:
|
||||
userID:
|
||||
}
|
||||
|
||||
24
LogisticsSupportTool_API_DOCS/logistics/bookout.bru
Normal file
24
LogisticsSupportTool_API_DOCS/logistics/bookout.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: bookout
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/lst/old/api/logistics/bookout
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
|
||||
"runningNr": "1865027",
|
||||
"reason": "packer printed premature"
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
LogisticsSupportTool_API_DOCS/logistics/folder.bru
Normal file
8
LogisticsSupportTool_API_DOCS/logistics/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: logistics
|
||||
seq: 7
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
24
LogisticsSupportTool_API_DOCS/logistics/relocate.bru
Normal file
24
LogisticsSupportTool_API_DOCS/logistics/relocate.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: relocate
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/lst/old/api/logistics/relocate
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
|
||||
"runningNr": "56121541",
|
||||
"laneID": "30006"
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
24
LogisticsSupportTool_API_DOCS/logistics/removeAsWaste.bru
Normal file
24
LogisticsSupportTool_API_DOCS/logistics/removeAsWaste.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: removeAsWaste
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{url}}/lst/old/api/logistics/removeasreusable
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
|
||||
"runningNr": "1865018",
|
||||
"reason": "validating stockout"
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
LogisticsSupportTool_API_DOCS/mobile/folder.bru
Normal file
8
LogisticsSupportTool_API_DOCS/mobile/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: mobile
|
||||
seq: 4
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
20
LogisticsSupportTool_API_DOCS/mobile/getsession.bru
Normal file
20
LogisticsSupportTool_API_DOCS/mobile/getsession.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: getsession
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/api/user/me
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: jpHHbLNGJRpUMvfrVOYmhbJL2Ux0arse
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
15
LogisticsSupportTool_API_DOCS/mobile/ota system check.bru
Normal file
15
LogisticsSupportTool_API_DOCS/mobile/ota system check.bru
Normal file
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: ota system check
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/lst/api/mobile
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
20
LogisticsSupportTool_API_DOCS/mobile/otacheck.bru
Normal file
20
LogisticsSupportTool_API_DOCS/mobile/otacheck.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: otacheck
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://10.193.0.56:4000/api/mobile/updates
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
headers {
|
||||
expo-runtime-version: 1.0.0
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
LogisticsSupportTool_API_DOCS/v3endpoints/folder.bru
Normal file
8
LogisticsSupportTool_API_DOCS/v3endpoints/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: v3endpoints
|
||||
seq: 5
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
16
LogisticsSupportTool_API_DOCS/v3endpoints/tester.bru
Normal file
16
LogisticsSupportTool_API_DOCS/v3endpoints/tester.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: tester
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/lst/api/system/prodsql/start
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
45
app/main.ts
45
app/main.ts
@@ -4,15 +4,15 @@ import { toNodeHandler } from "better-auth/node";
|
||||
import cors from "cors";
|
||||
import express from "express";
|
||||
import { createServer } from "http";
|
||||
import { createProxyMiddleware, fixRequestBody } from "http-proxy-middleware";
|
||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||
import morgan from "morgan";
|
||||
import os from "os";
|
||||
import { dirname, join } from "path";
|
||||
import swaggerJsdoc from "swagger-jsdoc";
|
||||
import swaggerUi from "swagger-ui-express";
|
||||
import { fileURLToPath } from "url";
|
||||
import { userMigrate } from "./src/internal/auth/controller/userMigrate.js";
|
||||
import { schedulerManager } from "./src/internal/logistics/controller/schedulerManager.js";
|
||||
import { setupMobileRoutes } from "./src/internal/mobile/route.js";
|
||||
import { printers } from "./src/internal/ocp/printers/printers.js";
|
||||
import { setupRoutes } from "./src/internal/routerHandler/routeHandler.js";
|
||||
import { baseModules } from "./src/internal/system/controller/modules/baseModules.js";
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
addListeners,
|
||||
manualFixes,
|
||||
} from "./src/internal/system/utlis/addListeners.js";
|
||||
import { swaggerOptions } from "./src/pkg/apiDocs/swaggerOptions.js";
|
||||
import { auth } from "./src/pkg/auth/auth.js";
|
||||
import { db } from "./src/pkg/db/db.js";
|
||||
import { settings } from "./src/pkg/db/schema/settings.js";
|
||||
@@ -34,6 +33,9 @@ import { sendNotify } from "./src/pkg/utils/notify.js";
|
||||
import { returnFunc } from "./src/pkg/utils/return.js";
|
||||
import { tryCatch } from "./src/pkg/utils/tryCatch.js";
|
||||
import { setupIoServer } from "./src/ws/server.js";
|
||||
import { swaggerConfig, swaggerUiOptions } from "./src/internal/swagger/config.js";
|
||||
import { setupSwagger } from "./src/internal/swagger/swagger.js";
|
||||
|
||||
|
||||
const main = async () => {
|
||||
const env = validateEnv(process.env);
|
||||
@@ -75,7 +77,8 @@ const main = async () => {
|
||||
}
|
||||
|
||||
// connect to the prod sql
|
||||
await initializeProdPool();
|
||||
console.log("Connecting to the sql server");
|
||||
|
||||
|
||||
// express app
|
||||
const app = express();
|
||||
@@ -156,24 +159,33 @@ const main = async () => {
|
||||
},
|
||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
||||
credentials: true,
|
||||
exposedHeaders: ["set-cookie"],
|
||||
exposedHeaders: [
|
||||
"set-cookie",
|
||||
"expo-protocol-version",
|
||||
"expo-sfv-version",
|
||||
],
|
||||
allowedHeaders: [
|
||||
"Content-Type",
|
||||
"Authorization",
|
||||
"X-Requested-With",
|
||||
"XMLHttpRequest",
|
||||
"expo-runtime-version",
|
||||
"expo-platform",
|
||||
"expo-channel-name",
|
||||
"*",
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
// docs and routes
|
||||
const openapiSpec: any = swaggerJsdoc(swaggerOptions);
|
||||
app.use(
|
||||
basePath + "/api/docs",
|
||||
swaggerUi.serve,
|
||||
swaggerUi.setup(openapiSpec),
|
||||
);
|
||||
|
||||
// const openapiSpec: any = swaggerJsdoc(swaggerConfig);
|
||||
// app.use(
|
||||
// basePath + "/api/docs",
|
||||
// swaggerUi.serve,
|
||||
// swaggerUi.setup(openapiSpec, swaggerUiOptions),
|
||||
// );
|
||||
initializeProdPool();
|
||||
setupSwagger(app, basePath)
|
||||
app.use(basePath + "/d", express.static(join(__dirname, "../lstDocs/build")));
|
||||
app.use(
|
||||
basePath + "/app",
|
||||
@@ -207,12 +219,17 @@ const main = async () => {
|
||||
// start up the v1listener
|
||||
v1Listener();
|
||||
addListeners();
|
||||
userMigrate();
|
||||
|
||||
//userMigrate();
|
||||
// some temp fixes
|
||||
// above 235 remove these
|
||||
manualFixes();
|
||||
//settingsMigrate();
|
||||
}, 5 * 1000);
|
||||
|
||||
// setTimeout(() => {
|
||||
// startHonoServer();
|
||||
// }, 8 * 1000);
|
||||
|
||||
// start the server up
|
||||
server.listen(PORT, "0.0.0.0", () =>
|
||||
log.info(
|
||||
|
||||
@@ -24,6 +24,13 @@ router.post("/", async (req: Request, res: Response) => {
|
||||
.from(user)
|
||||
.where(eq(user.username, validated.username));
|
||||
|
||||
if(userLogin.length === 0 ){
|
||||
return res.status(200).json({
|
||||
success: false,
|
||||
message: `It appears you do not have a user yet please head over to the register page and create a user then try again.`,
|
||||
|
||||
});
|
||||
}
|
||||
if (
|
||||
!userLogin[0].lastLogin ||
|
||||
differenceInDays(userLogin[0].lastLogin, new Date(Date.now())) > 120
|
||||
|
||||
@@ -18,7 +18,7 @@ const registerSchema = z.object({
|
||||
.string()
|
||||
.min(3)
|
||||
.max(32)
|
||||
.regex(/^[a-zA-Z0-9.]+$/, "Only alphanumeric + dots allowed"),
|
||||
.regex(/^[a-zA-Z0-9._]+$/, "Only alphanumeric + dots allowed"),
|
||||
displayUsername: z.string().min(2).max(100).optional(), // optional in your API, but supported
|
||||
});
|
||||
|
||||
|
||||
89
app/src/internal/datamart/routes/getActiveAv.ts
Normal file
89
app/src/internal/datamart/routes/getActiveAv.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Router, type Request, type Response } from "express";
|
||||
import { prodQuery } from "../../../pkg/prodSql/prodQuery.js";
|
||||
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
|
||||
import { db } from "../../../pkg/db/db.js";
|
||||
import { settings } from "../../../pkg/db/schema/settings.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { activeArticle } from "../../../pkg/prodSql/querys/datamart/article.js";
|
||||
|
||||
type Articles = {
|
||||
article: string
|
||||
description: string
|
||||
articleType: string
|
||||
pricePoint:string
|
||||
salesPrice:string
|
||||
typeOfMaterial:string
|
||||
articleIdType:string
|
||||
articleWeight:string
|
||||
idAddress:string
|
||||
addressDescription:string
|
||||
addressType:string
|
||||
profitCenter:String
|
||||
fg: string
|
||||
num_of_cycles:string
|
||||
costsCenterId:string
|
||||
costCenterDescription:string
|
||||
customerArticleNumber:string
|
||||
customerArticleDescription:String
|
||||
cycleTime:string
|
||||
salesAgreement:string
|
||||
productFamily:string
|
||||
uom:string
|
||||
}
|
||||
|
||||
|
||||
const router = Router();
|
||||
|
||||
// GET /health
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
|
||||
const includePlantToken = req.params.includePlantToken
|
||||
|
||||
let articles:Articles[] = [];
|
||||
try {
|
||||
const res = await prodQuery(activeArticle, "Get active articles");
|
||||
articles = res?.data;
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message:"Error getting articles",
|
||||
error: error
|
||||
};
|
||||
}
|
||||
if (includePlantToken) {
|
||||
const { data, error } = await tryCatch(db.select().from(settings).where(eq(settings.name, "plantToken")))
|
||||
if (error) {
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message:"Error getting settings",
|
||||
error: error
|
||||
};
|
||||
}
|
||||
// return articles.map((n) => {
|
||||
// return {
|
||||
// success: true,
|
||||
// message: "Active articles including plant token",
|
||||
// data:{ plantToken: data[0].value, ...n }};
|
||||
// });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Active articles including plant token",
|
||||
data: articles.map((n) => {
|
||||
return { plantToken: data[0].value, ...n }
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
success: true,
|
||||
message: "Active articles including plant token",
|
||||
data:articles};
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
export default router;
|
||||
10
app/src/internal/datamart/routes/routes.ts
Normal file
10
app/src/internal/datamart/routes/routes.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { Express, Request, Response } from "express";
|
||||
|
||||
//datamart Routes
|
||||
import getActiveAv from './getActiveAv.js'
|
||||
export const setupDataMartRoutes = (app: Express, basePath: string) => {
|
||||
const route = basePath + "/api/datamart"
|
||||
app.use(route + '/activeArticle', getActiveAv);
|
||||
//app.use(basePath + "/api/user/me", requireAuth(), me);
|
||||
|
||||
};
|
||||
114
app/src/internal/forklifts/routes/hours/addHours.ts
Normal file
114
app/src/internal/forklifts/routes/hours/addHours.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import axios from "axios";
|
||||
import { type DrizzleError, sql } from "drizzle-orm";
|
||||
import type { Request, Response } from "express";
|
||||
import { Router } from "express";
|
||||
import https from "https";
|
||||
import { db } from "../../../../pkg/db/db.js";
|
||||
import {
|
||||
insertLeasesCompanySchema,
|
||||
leases,
|
||||
} from "../../../../pkg/db/schema/forkliftLeases.js";
|
||||
import { createLogger } from "../../../../pkg/logger/logger.js";
|
||||
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post("/", async (req: Request, res: Response) => {
|
||||
// when a new server is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there
|
||||
//res.status(200).json({ message: "Server added", ip: req.hostname });
|
||||
const log = createLogger({ module: "forklift", subModule: "add lease" });
|
||||
const parsed = insertLeasesCompanySchema.safeParse(req.body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ errors: parsed.error.flatten() });
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.insert(leases)
|
||||
.values({
|
||||
...parsed.data,
|
||||
add_user: req.user?.username,
|
||||
add_date: sql`NOW()`,
|
||||
upd_user: req.user?.username,
|
||||
upd_date: sql`NOW()`,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: leases.leaseNumber,
|
||||
set: {
|
||||
...parsed.data,
|
||||
add_user: req.user?.username,
|
||||
add_date: sql`NOW()`,
|
||||
upd_user: req.user?.username,
|
||||
upd_date: sql`NOW()`,
|
||||
},
|
||||
})
|
||||
.returning({
|
||||
leaseNumber: leases.leaseNumber,
|
||||
}),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
const err: DrizzleError = error;
|
||||
return res.status(400).json({
|
||||
message: `Error adding lease`,
|
||||
error: err.cause,
|
||||
});
|
||||
}
|
||||
|
||||
// if (req.hostname === "localhost" && process.env.MAIN_SERVER) {
|
||||
// log.info({}, "Running in dev server about to add in a new server");
|
||||
// const axiosInstance = axios.create({
|
||||
// httpsAgent: new https.Agent({ rejectUnauthorized: false }),
|
||||
// baseURL: process.env.MAIN_SERVER, // e.g. "https://example.com"
|
||||
// withCredentials: true,
|
||||
// });
|
||||
|
||||
// const loginRes = (await axiosInstance.post(
|
||||
// `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`,
|
||||
// {
|
||||
// username: process.env.MAIN_SERVER_USERNAME,
|
||||
// password: process.env.MAIN_SERVER_PASSWORD,
|
||||
// },
|
||||
// {
|
||||
// headers: { "Content-Type": "application/json" },
|
||||
// },
|
||||
// )) as any;
|
||||
// const setCookie = loginRes.headers["set-cookie"][0];
|
||||
|
||||
// if (!setCookie) {
|
||||
// throw new Error("Did not receive a Set-Cookie header from login");
|
||||
// }
|
||||
|
||||
// const { data, error } = await tryCatch(
|
||||
// axios.post(
|
||||
// `${process.env.MAIN_SERVER}/lst/api/forklifts/leases`,
|
||||
// parsed.data,
|
||||
// {
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// Cookie: setCookie.split(";")[0],
|
||||
// },
|
||||
// withCredentials: true,
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
|
||||
// if (error) {
|
||||
// log.error(
|
||||
// { stack: error },
|
||||
// "There was an error adding the company to Main Server",
|
||||
// );
|
||||
// }
|
||||
// log.info(
|
||||
// { stack: data?.data },
|
||||
// "A new Company was just added to the server.",
|
||||
// );
|
||||
// }
|
||||
|
||||
return res
|
||||
.status(201)
|
||||
.json({ message: `lease ${data[0]?.leaseNumber} added`, data: data });
|
||||
});
|
||||
|
||||
export default router;
|
||||
59
app/src/internal/forklifts/routes/hours/getHours.ts
Normal file
59
app/src/internal/forklifts/routes/hours/getHours.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { and, asc, eq } from "drizzle-orm";
|
||||
import type { Request, Response } from "express";
|
||||
import { Router } from "express";
|
||||
import { db } from "../../../../pkg/db/db.js";
|
||||
import { forkliftCompanies } from "../../../../pkg/db/schema/forkliftLeaseCompanys.js";
|
||||
import { leases } from "../../../../pkg/db/schema/forkliftLeases.js";
|
||||
import { forklifts } from "../../../../pkg/db/schema/forklifts.js";
|
||||
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
const conditions = [];
|
||||
|
||||
if (req.query.lease !== undefined) {
|
||||
conditions.push(eq(leases.leaseNumber, `${req.query.lease}`));
|
||||
}
|
||||
|
||||
if (req.query.companyId !== undefined) {
|
||||
conditions.push(eq(leases.companyId, `${req.query.companyId}`));
|
||||
}
|
||||
|
||||
//conditions.push(eq(forkliftCompanies.active, true));
|
||||
|
||||
const { data, error } = (await tryCatch(
|
||||
db
|
||||
.select({
|
||||
id: leases.id,
|
||||
leaseNumber: leases.leaseNumber,
|
||||
startDate: leases.startDate,
|
||||
endDate: leases.endDate,
|
||||
leaseLink: leases.leaseLink,
|
||||
companyName: forkliftCompanies.name,
|
||||
add_user: leases.add_user,
|
||||
add_date: leases.add_date,
|
||||
upd_user: leases.upd_user,
|
||||
upd_date: leases.upd_date,
|
||||
})
|
||||
.from(leases)
|
||||
.innerJoin(forkliftCompanies, eq(forkliftCompanies.id, leases.companyId))
|
||||
.where(and(...conditions))
|
||||
.orderBy(asc(leases.leaseNumber)),
|
||||
)) as any;
|
||||
|
||||
// add the forklifts that are in this lease
|
||||
const forkliftData = await db.select().from(forklifts);
|
||||
|
||||
if (error) {
|
||||
return res.status(400).json({ error: error });
|
||||
}
|
||||
|
||||
const leaseData = data.map((i: any) => ({
|
||||
...i,
|
||||
forklifts: forkliftData.filter((x) => x.leaseId === i.id),
|
||||
}));
|
||||
res.status(200).json({ message: "Current Leases", data: leaseData });
|
||||
});
|
||||
|
||||
export default router;
|
||||
20
app/src/internal/forklifts/routes/hours/leaseRoutes.ts
Normal file
20
app/src/internal/forklifts/routes/hours/leaseRoutes.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Router } from "express";
|
||||
import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js";
|
||||
|
||||
import addHours from "./addHours.js";
|
||||
import gethours from "./getHours.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use(
|
||||
"/",
|
||||
requireAuth("forklifts", ["systemAdmin", "admin", "manager", "supervisor"]),
|
||||
addHours,
|
||||
);
|
||||
router.use(
|
||||
"/",
|
||||
requireAuth("forklifts", ["systemAdmin", "admin", "manager", "supervisor"]),
|
||||
gethours,
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -28,6 +28,7 @@ router.post("/", async (req: Request, res: Response) => {
|
||||
companyId: req.body.companyId,
|
||||
invoiceNumber: req.body.invoiceNumber,
|
||||
invoiceDate: req.body.invoiceDate,
|
||||
comment: req.body.comment,
|
||||
uploadedBy: req.body.uploadedBy,
|
||||
totalAmount: req.body.totalAmount,
|
||||
});
|
||||
@@ -53,26 +54,28 @@ router.post("/", async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// this will be the total invoice amount minus each forklift this way we can keep the total amount in here plus forklifts seperated
|
||||
const totalAmount = (
|
||||
validatedForklifts.reduce((sum, f) => sum + Number(f.amount || 0), 0) -
|
||||
req.body.totalInvoice
|
||||
).toString();
|
||||
// const totalAmount = (
|
||||
// validatedForklifts.reduce((sum, f) => sum + Number(f.amount || 0), 0) -
|
||||
// req.body.totalInvoice
|
||||
// ).toString();
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.insert(leaseInvoices)
|
||||
.values({
|
||||
...invoiceData,
|
||||
add_date: sql`NOW()`,
|
||||
totalAmount: req.body.totalAmount,
|
||||
uploadedBy: req.user!.username || "lst_user",
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: leaseInvoices.invoiceNumber,
|
||||
set: {
|
||||
totalAmount,
|
||||
invoiceDate: invoiceData.invoiceDate,
|
||||
uploadedBy: req.user!.username || "lst_user",
|
||||
},
|
||||
})
|
||||
// .onConflictDoUpdate({
|
||||
// target: leaseInvoices.invoiceNumber,
|
||||
// set: {
|
||||
// totalAmount,
|
||||
// invoiceDate: invoiceData.invoiceDate,
|
||||
// uploadedBy: req.user!.username || "lst_user",
|
||||
// },
|
||||
// })
|
||||
.returning(),
|
||||
);
|
||||
|
||||
@@ -80,18 +83,21 @@ router.post("/", async (req: Request, res: Response) => {
|
||||
const err: DrizzleError = error;
|
||||
return res.status(400).json({
|
||||
message: `Error adding lease`,
|
||||
error: err.cause,
|
||||
// @ts-ignore
|
||||
error: err.cause.detail,
|
||||
});
|
||||
}
|
||||
|
||||
const invoiceId = data[0]?.id;
|
||||
console.log(validatedForklifts);
|
||||
const forkliftInvoices = validatedForklifts.map((f) => {
|
||||
return {
|
||||
invoiceId,
|
||||
forkliftId: f.forklift_id,
|
||||
amount: f.amount,
|
||||
};
|
||||
});
|
||||
|
||||
const forkliftInvoices = validatedForklifts.map((f) => ({
|
||||
invoiceId,
|
||||
forkliftId: f.forklift_Id,
|
||||
amount: f.amount,
|
||||
}));
|
||||
console.log(forkliftInvoices);
|
||||
if (validatedForklifts.length > 0) {
|
||||
await db.insert(leaseInvoiceForklifts).values(forkliftInvoices);
|
||||
// .onConflictDoUpdate({
|
||||
|
||||
@@ -10,12 +10,14 @@ import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
|
||||
const router = Router();
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
const lease = req.query.lease;
|
||||
|
||||
const conditions = [];
|
||||
|
||||
if (lease !== undefined) {
|
||||
conditions.push(eq(leases.leaseNumber, `${lease}`));
|
||||
if (req.query.lease !== undefined) {
|
||||
conditions.push(eq(leases.leaseNumber, `${req.query.lease}`));
|
||||
}
|
||||
|
||||
if (req.query.companyId !== undefined) {
|
||||
conditions.push(eq(leases.companyId, `${req.query.companyId}`));
|
||||
}
|
||||
|
||||
//conditions.push(eq(forkliftCompanies.active, true));
|
||||
|
||||
@@ -12,7 +12,6 @@ import { db } from "../../../../pkg/db/db.js";
|
||||
import {
|
||||
type ForecastData,
|
||||
forecastData,
|
||||
forecastDataSchema,
|
||||
} from "../../../../pkg/db/schema/forecastEDIData.js";
|
||||
import { prodQuery } from "../../../../pkg/prodSql/prodQuery.js";
|
||||
import { activeArticle } from "../../../../pkg/prodSql/querys/datamart/article.js";
|
||||
@@ -36,19 +35,21 @@ export const forecastEdiData = async (data: ForecastData[]) => {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const activeAV = article?.data.filter(
|
||||
(c: any) =>
|
||||
c?.CustomerArticleNumber === data[i].customerArticleNo?.toString(),
|
||||
c?.customerArticleNumber === data[i].customerArticleNo?.toString(),
|
||||
);
|
||||
const newData = data[i];
|
||||
//console.log(activeAV[0].IdArtikelvarianten);
|
||||
|
||||
forecaseEDIDATA.push({
|
||||
...newData,
|
||||
article: activeAV[0].IdArtikelvarianten,
|
||||
article: activeAV.length > 0 ? activeAV[0].article : 0,
|
||||
description:
|
||||
activeAV.length > 0 ? activeAV[0].description : "No Av Created",
|
||||
requirementDate: new Date(newData.requirementDate),
|
||||
});
|
||||
}
|
||||
|
||||
console.log(forecaseEDIDATA[0]);
|
||||
//console.log(forecaseEDIDATA[0]);
|
||||
const { data: f, error: ef } = await tryCatch(
|
||||
db.insert(forecastData).values(forecaseEDIDATA),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { Request, Response } from "express";
|
||||
import { Router } from "express";
|
||||
import z from "zod";
|
||||
import z, { success } from "zod";
|
||||
import { db } from "../../../../pkg/db/db.js";
|
||||
import { forecastData } from "../../../../pkg/db/schema/forecastEDIData.js";
|
||||
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
|
||||
import { forecastEdiData } from "../../controller/demandManagement/forecastEDIData.js";
|
||||
|
||||
export const Preprint = z.object({
|
||||
@@ -21,4 +24,19 @@ router.post("/forecastData", async (req: Request, res: Response) => {
|
||||
res.status(200).json({ success: true, message: "Forecast Data", data: [] });
|
||||
});
|
||||
|
||||
// quick fix for getting the data
|
||||
router.get("/forecastData", async (req: Request, res: Response) => {
|
||||
const { data, error } = await tryCatch(db.select().from(forecastData));
|
||||
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Error getting forecast data",
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({ success: true, message: "Forecast Data", data: data });
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
211
app/src/internal/mobile/route.ts
Normal file
211
app/src/internal/mobile/route.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import type { Express, Request, Response } from "express";
|
||||
import express, { Router } from "express";
|
||||
import { readdirSync, readFileSync, statSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import crypto from "crypto";
|
||||
import fs from "fs";
|
||||
|
||||
export const setupMobileRoutes = (app: Express, basePath: string) => {
|
||||
const router = Router();
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const distPath = join(__dirname, "../../../../mobileLst/dist");
|
||||
|
||||
function generateAssetManifest(baseUrl: string) {
|
||||
const assets: any[] = [];
|
||||
const assetsDir = join(distPath, "assets");
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(assetsDir)) {
|
||||
return assets;
|
||||
}
|
||||
|
||||
const files = readdirSync(assetsDir);
|
||||
files.forEach((file) => {
|
||||
const filePath = join(assetsDir, file);
|
||||
const stats = statSync(filePath);
|
||||
|
||||
if (stats.isFile()) {
|
||||
const content = readFileSync(filePath);
|
||||
const hash = crypto
|
||||
.createHash("sha256")
|
||||
.update(content)
|
||||
.digest("hex");
|
||||
|
||||
assets.push({
|
||||
hash: hash,
|
||||
key: file,
|
||||
fileExtension: `.${file.split(".").pop()}`,
|
||||
contentType: getContentType(file),
|
||||
url: `${baseUrl}/assets/${file}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("Error reading assets:", err);
|
||||
}
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
function getContentType(filename: string): string {
|
||||
const ext = filename.split(".").pop()?.toLowerCase();
|
||||
const contentTypes: { [key: string]: string } = {
|
||||
hbc: "application/javascript",
|
||||
bundle: "application/javascript",
|
||||
js: "application/javascript",
|
||||
json: "application/json",
|
||||
png: "image/png",
|
||||
jpg: "image/jpeg",
|
||||
jpeg: "image/jpeg",
|
||||
gif: "image/gif",
|
||||
ttf: "font/ttf",
|
||||
otf: "font/otf",
|
||||
woff: "font/woff",
|
||||
woff2: "font/woff2",
|
||||
};
|
||||
return contentTypes[ext || ""] || "application/octet-stream";
|
||||
}
|
||||
|
||||
app.get(basePath + "/api/mobile/updates", (req, res) => {
|
||||
console.log("=== OTA Update Request ===");
|
||||
console.log("Headers:", JSON.stringify(req.headers, null, 2));
|
||||
|
||||
const runtimeVersion = req.headers["expo-runtime-version"];
|
||||
const platform = req.headers["expo-platform"] || "android";
|
||||
const expectedRuntimeVersion = "1.0.0";
|
||||
|
||||
if (runtimeVersion !== expectedRuntimeVersion) {
|
||||
console.log(
|
||||
`Runtime mismatch: got ${runtimeVersion}, expected ${expectedRuntimeVersion}`
|
||||
);
|
||||
return res.status(404).json({
|
||||
error: "No update available for this runtime version",
|
||||
requestedVersion: runtimeVersion,
|
||||
availableVersion: expectedRuntimeVersion,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// const host = req.get('host');
|
||||
// // If it's the production domain, force https
|
||||
// const protocol = host.includes('alpla.net') ? 'https' : req.protocol;
|
||||
|
||||
// const baseUrl = `${protocol}://${host}/lst/api/mobile/updates`
|
||||
|
||||
const host = req.get('host'); // Should be "usmcd1vms036:4000"
|
||||
const protocol = 'http';
|
||||
const baseUrl = `${protocol}://${host}/api/mobile/updates`;
|
||||
|
||||
// Find the .hbc file
|
||||
const bundleDir = join(distPath, "_expo/static/js/android");
|
||||
|
||||
if (!fs.existsSync(bundleDir)) {
|
||||
console.error("Bundle directory does not exist:", bundleDir);
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: "Bundle directory not found" });
|
||||
}
|
||||
|
||||
const bundleFiles = readdirSync(bundleDir);
|
||||
console.log("Available bundle files:", bundleFiles);
|
||||
|
||||
const bundleFile = bundleFiles.find((f) => f.endsWith(".hbc"));
|
||||
|
||||
if (!bundleFile) {
|
||||
console.error("No .hbc file found in:", bundleDir);
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: "Hermes bundle (.hbc) not found" });
|
||||
}
|
||||
|
||||
console.log("Using bundle file:", bundleFile);
|
||||
|
||||
const bundlePath = join(bundleDir, bundleFile);
|
||||
const bundleContent = readFileSync(bundlePath);
|
||||
const bundleHash = crypto
|
||||
.createHash("sha256")
|
||||
.update(bundleContent)
|
||||
.digest("hex");
|
||||
|
||||
const updateId = crypto.randomUUID();
|
||||
const createdAt = new Date().toISOString();
|
||||
|
||||
// This is the NEW manifest format for Expo SDK 50+
|
||||
const manifest = {
|
||||
id: updateId,
|
||||
createdAt: createdAt,
|
||||
runtimeVersion: expectedRuntimeVersion,
|
||||
launchAsset: {
|
||||
hash: bundleHash,
|
||||
key: bundleFile,
|
||||
contentType: "application/javascript",
|
||||
fileExtension: ".hbc",
|
||||
url: `${baseUrl}/_expo/static/js/android/${bundleFile}`,
|
||||
},
|
||||
assets: generateAssetManifest(baseUrl),
|
||||
metadata: {},
|
||||
extra: {
|
||||
expoClient: {
|
||||
name: "LSTScanner",
|
||||
slug: "lst-scanner-app",
|
||||
version: "1.0.0",
|
||||
runtimeVersion: expectedRuntimeVersion,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
console.log(
|
||||
"Returning manifest:",
|
||||
JSON.stringify(manifest, null, 2)
|
||||
);
|
||||
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("expo-protocol-version", "1");
|
||||
res.setHeader("expo-sfv-version", "0");
|
||||
res.json(manifest);
|
||||
} catch (error: any) {
|
||||
console.error("Error generating manifest:", error);
|
||||
res.status(500).json({
|
||||
error: "Failed to generate manifest",
|
||||
details: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Serve static files
|
||||
app.use(
|
||||
basePath + "/api/mobile/updates",
|
||||
express.static(distPath, {
|
||||
setHeaders(res, path) {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Cache-Control", "public, max-age=31536000");
|
||||
|
||||
if (path.endsWith(".hbc")) {
|
||||
res.setHeader("Content-Type", "application/javascript");
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
// app.use(
|
||||
// basePath + "/api/mobile/updates",
|
||||
// express.static(join(__dirname, mobileDir), {
|
||||
// setHeaders(res) {
|
||||
// // OTA runtime needs to fetch these from the device
|
||||
// console.log("OTA check called");
|
||||
// res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
// },
|
||||
// })
|
||||
// );
|
||||
|
||||
// app.get(basePath + "/api/mobile/updates", (req, res) => {
|
||||
// res.redirect(basePath + "/api/mobile/updates/metadata.json");
|
||||
// });
|
||||
|
||||
app.get(basePath + "/api/mobile", (_, res) =>
|
||||
res.status(200).json({ message: "LST OTA server is up." })
|
||||
);
|
||||
};
|
||||
@@ -4,22 +4,26 @@ import { setupAuthRoutes } from "../auth/routes/routes.js";
|
||||
import { setupForkliftRoutes } from "../forklifts/routes/routes.js";
|
||||
import { setupLogisticsRoutes } from "../logistics/routes.js";
|
||||
import { setupSystemRoutes } from "../system/routes.js";
|
||||
import { setupMobileRoutes } from "../mobile/route.js";
|
||||
import { setupDataMartRoutes } from "../datamart/routes/routes.js";
|
||||
|
||||
export const setupRoutes = (app: Express, basePath: string) => {
|
||||
// all routes
|
||||
setupAuthRoutes(app, basePath);
|
||||
setupAdminRoutes(app, basePath);
|
||||
setupSystemRoutes(app, basePath);
|
||||
setupLogisticsRoutes(app, basePath);
|
||||
setupForkliftRoutes(app, basePath);
|
||||
// all routes
|
||||
setupAuthRoutes(app, basePath);
|
||||
setupAdminRoutes(app, basePath);
|
||||
setupSystemRoutes(app, basePath);
|
||||
setupLogisticsRoutes(app, basePath);
|
||||
setupForkliftRoutes(app, basePath);
|
||||
setupMobileRoutes(app, basePath);
|
||||
setupDataMartRoutes(app, basePath)
|
||||
|
||||
// always try to go to the app weather we are in dev or in production.
|
||||
app.get(basePath + "/", (req: Request, res: Response) => {
|
||||
res.redirect(basePath + "/app");
|
||||
});
|
||||
// always try to go to the app weather we are in dev or in production.
|
||||
app.get(basePath + "/", (req: Request, res: Response) => {
|
||||
res.redirect(basePath + "/app");
|
||||
});
|
||||
|
||||
// Fallback 404 handler
|
||||
app.use((req: Request, res: Response) => {
|
||||
res.status(404).json({ error: "Not Found" });
|
||||
});
|
||||
// Fallback 404 handler
|
||||
app.use((req: Request, res: Response) => {
|
||||
res.status(404).json({ error: "Not Found" });
|
||||
});
|
||||
};
|
||||
|
||||
59
app/src/internal/swagger/config.ts
Normal file
59
app/src/internal/swagger/config.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export const swaggerUiOptions = {
|
||||
explorer: true,
|
||||
customCss: ".swagger-ui .topbar { display: none }",
|
||||
customSiteTitle: "LST API Documentation",
|
||||
swaggerOptions: {
|
||||
persistAuthorization: true,
|
||||
displayRequestDuration: true,
|
||||
filter: true,
|
||||
syntaxHighlight: {
|
||||
activate: true,
|
||||
theme: "monokai",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const swaggerConfig = {
|
||||
definition: {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: "Logistics Support Tool",
|
||||
version: "1.8.0",
|
||||
description: "Complete API documentation for lst",
|
||||
contact: {
|
||||
name: "API Support",
|
||||
email: "blake.matthes@alpla.com",
|
||||
},
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: "http://localhost:4200",
|
||||
description: "Development server",
|
||||
},
|
||||
{
|
||||
url: "https://api.yourapp.com",
|
||||
description: "Production server",
|
||||
},
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
bearerFormat: "JWT",
|
||||
},
|
||||
apiKey: {
|
||||
type: "apiKey",
|
||||
in: "header",
|
||||
name: "X-API-Key",
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
apis: [], // We'll populate this dynamically
|
||||
};
|
||||
129
app/src/internal/swagger/endpoints/auth/login.ts
Normal file
129
app/src/internal/swagger/endpoints/auth/login.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
const loginEndpoint = {
|
||||
'/lst/api/user/login': {
|
||||
post: {
|
||||
tags: ['Authentication'],
|
||||
summary: 'Login to get a token',
|
||||
description: 'User enters username and password, gets back a JWT token and session data',
|
||||
|
||||
// What the user sends you
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['username', 'password'],
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
example: 'smith01'
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
example: 'MyPassword123'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// What you send back to the user
|
||||
responses: {
|
||||
// SUCCESS - Login worked
|
||||
200: {
|
||||
description: 'Login successful',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
example: true
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
example: 'Login successful'
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
token: {
|
||||
type: 'string',
|
||||
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
example: '12345'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
example: 'user@example.com'
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
example: 'johndoe'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ERROR - Wrong password or email
|
||||
401: {
|
||||
description: 'Wrong email or password',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
example: false
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
example: 'Invalid credentials'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ERROR - Missing fields
|
||||
400: {
|
||||
description: 'Missing email or password',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
example: false
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
example: 'Email and password are required'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default loginEndpoint;
|
||||
0
app/src/internal/swagger/schemas/auth.schema.ts
Normal file
0
app/src/internal/swagger/schemas/auth.schema.ts
Normal file
0
app/src/internal/swagger/schemas/common.schema.ts
Normal file
0
app/src/internal/swagger/schemas/common.schema.ts
Normal file
31
app/src/internal/swagger/swagger.ts
Normal file
31
app/src/internal/swagger/swagger.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import swaggerJsdoc from 'swagger-jsdoc';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import { swaggerConfig, swaggerUiOptions } from './config.js';
|
||||
import { type Express } from 'express';
|
||||
|
||||
import loginEndpoint from './endpoints/auth/login.js';
|
||||
|
||||
const allPaths = {
|
||||
...loginEndpoint,
|
||||
// When you add more endpoints, add them here:
|
||||
// ...registerEndpoint,
|
||||
// ...logoutEndpoint,
|
||||
};
|
||||
|
||||
|
||||
const swaggerSpec = {
|
||||
...swaggerConfig.definition,
|
||||
paths: allPaths
|
||||
};
|
||||
|
||||
const specs = swaggerJsdoc({
|
||||
...swaggerConfig,
|
||||
definition: swaggerSpec
|
||||
});
|
||||
|
||||
export function setupSwagger(app: Express, basePath: string): void {
|
||||
// Swagger UI at /api-docs
|
||||
app.use(basePath + "/api/docs", swaggerUi.serve, swaggerUi.setup(specs, swaggerUiOptions));
|
||||
|
||||
//console.log('📚 Swagger docs at http://localhost:3000/api-docs');
|
||||
}
|
||||
@@ -100,8 +100,8 @@
|
||||
"category": "quality",
|
||||
"active": false,
|
||||
"icon": "",
|
||||
"link": "",
|
||||
"roles": ["admin", "systemAdmin", "manager", "viewer", "tester"]
|
||||
"link": "/lst/app/old/quality",
|
||||
"roles": ["admin", "systemAdmin", "manager", "supervisor", "tester"]
|
||||
},
|
||||
{
|
||||
"name": "eom",
|
||||
|
||||
@@ -5,66 +5,31 @@ import { Router } from "express";
|
||||
import https from "https";
|
||||
import { db } from "../../../../pkg/db/db.js";
|
||||
import { serverData } from "../../../../pkg/db/schema/servers.js";
|
||||
import { settings } from "../../../../pkg/db/schema/settings.js";
|
||||
import { createLogger } from "../../../../pkg/logger/logger.js";
|
||||
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.patch("/:token", async (req: Request, res: Response) => {
|
||||
const log = createLogger({ module: "admin", subModule: "update server" });
|
||||
router.patch("/:id", async (req: Request, res: Response) => {
|
||||
const log = createLogger({ module: "admin", subModule: "update setting" });
|
||||
|
||||
// when a server is updated and is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there, we want to insert with update on conflict.
|
||||
const token = req.params.token;
|
||||
const id = req.params.id;
|
||||
const updates: Record<string, any> = {};
|
||||
|
||||
if (req.body?.name !== undefined) {
|
||||
updates.name = req.body.name;
|
||||
}
|
||||
if (req.body?.serverDNS !== undefined) {
|
||||
updates.serverDNS = req.body.serverDNS;
|
||||
if (req.body?.value !== undefined) {
|
||||
updates.value = req.body.value;
|
||||
}
|
||||
if (req.body?.ipAddress !== undefined) {
|
||||
updates.ipAddress = req.body.ipAddress;
|
||||
if (req.body?.description !== undefined) {
|
||||
updates.description = req.body.description;
|
||||
}
|
||||
|
||||
if (req.body?.greatPlainsPlantCode !== undefined) {
|
||||
updates.greatPlainsPlantCode = req.body.greatPlainsPlantCode;
|
||||
}
|
||||
|
||||
if (req.body?.lstServerPort !== undefined) {
|
||||
updates.lstServerPort = req.body.lstServerPort;
|
||||
}
|
||||
|
||||
if (req.body?.serverLoc !== undefined) {
|
||||
updates.serverLoc = req.body.serverLoc;
|
||||
}
|
||||
|
||||
if (req.body?.streetAddress !== undefined) {
|
||||
updates.streetAddress = req.body.streetAddress;
|
||||
}
|
||||
|
||||
if (req.body?.cityState !== undefined) {
|
||||
updates.cityState = req.body.cityState;
|
||||
}
|
||||
|
||||
if (req.body?.zipcode !== undefined) {
|
||||
updates.zipcode = req.body.zipcode;
|
||||
}
|
||||
|
||||
if (req.body?.contactEmail !== undefined) {
|
||||
updates.contactEmail = req.body.contactEmail;
|
||||
}
|
||||
|
||||
if (req.body?.contactPhone !== undefined) {
|
||||
updates.contactPhone = req.body.contactPhone;
|
||||
}
|
||||
|
||||
if (req.body?.customerTiAcc !== undefined) {
|
||||
updates.customerTiAcc = req.body.customerTiAcc;
|
||||
}
|
||||
|
||||
if (req.body?.active !== undefined) {
|
||||
updates.active = req.body.active;
|
||||
if (req.body?.moduleName !== undefined) {
|
||||
updates.moduleName = req.body.moduleName;
|
||||
}
|
||||
|
||||
updates.upd_user = req.user!.username || "lst_user";
|
||||
@@ -73,65 +38,12 @@ router.patch("/:token", async (req: Request, res: Response) => {
|
||||
try {
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await db
|
||||
.update(serverData)
|
||||
.update(settings)
|
||||
.set(updates)
|
||||
.where(eq(serverData.plantToken, token));
|
||||
.where(eq(settings.settings_id, id));
|
||||
}
|
||||
|
||||
if (req.hostname === "localhost" && process.env.MAIN_SERVER) {
|
||||
log.info({}, "Running in dev server about to add in a new server");
|
||||
const axiosInstance = axios.create({
|
||||
httpsAgent: new https.Agent({ rejectUnauthorized: false }),
|
||||
baseURL: process.env.MAIN_SERVER,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
const loginRes = (await axiosInstance.post(
|
||||
`${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`,
|
||||
{
|
||||
username: process.env.MAIN_SERVER_USERNAME,
|
||||
password: process.env.MAIN_SERVER_PASSWORD,
|
||||
},
|
||||
{
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
)) as any;
|
||||
|
||||
const setCookie = loginRes?.headers["set-cookie"][0];
|
||||
|
||||
//console.log(setCookie.split(";")[0].replace("__Secure-", ""));
|
||||
|
||||
if (!setCookie) {
|
||||
throw new Error("Did not receive a Set-Cookie header from login");
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
axios.patch(
|
||||
`${process.env.MAIN_SERVER}/lst/api/admin/server/${token}`,
|
||||
updates,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Cookie: setCookie.split(";")[0],
|
||||
},
|
||||
withCredentials: true,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
console.log(error);
|
||||
log.error(
|
||||
{ stack: error },
|
||||
"There was an error adding the server to Main Server",
|
||||
);
|
||||
}
|
||||
log.info(
|
||||
{ stack: data?.data },
|
||||
"A new Server was just added to the server.",
|
||||
);
|
||||
}
|
||||
res.status(200).json({ message: `${token} Server was just updated` });
|
||||
res.status(200).json({ message: `Setting was just updated` });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(400).json({ message: "Error Server updated", error });
|
||||
|
||||
@@ -58,6 +58,8 @@ router.get("/", async (req, res) => {
|
||||
memoryUsage: `Heap: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB / RSS: ${(
|
||||
used.rss / 1024 / 1024
|
||||
).toFixed(2)} MB`,
|
||||
eomFGPkgSheetVersion: 1, // this is the excel file version when we have a change to the macro we want to grab this
|
||||
masterMacroFile: 1, // this is the excel file version when we have a change to the macro we want to grab this
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Client } from "pg";
|
||||
import { db } from "../../../pkg/db/db.js";
|
||||
import { type NewSetting, settings } from "../../../pkg/db/schema/settings.js";
|
||||
import { createLogger } from "../../../pkg/logger/logger.js";
|
||||
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
|
||||
|
||||
export const addListeners = async () => {
|
||||
const log = createLogger({ module: "utils", subModule: "listeners" });
|
||||
@@ -60,9 +63,8 @@ export const addListeners = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// all the migration stuff that will need to be moved later build 230 and above will need to remove
|
||||
export const manualFixes = async () => {
|
||||
const fixQuery = `ALTER TABLE "serverData" ADD CONSTRAINT "serverData_name_unique" UNIQUE("name");`;
|
||||
|
||||
const log = createLogger({ module: "utils", subModule: "manual fixes" });
|
||||
const client = new Client({
|
||||
connectionString: `postgresql://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`,
|
||||
@@ -70,10 +72,59 @@ export const manualFixes = async () => {
|
||||
|
||||
await client.connect();
|
||||
|
||||
/**
|
||||
* The fix to correct the constraint on the server data
|
||||
*/
|
||||
// const fixQuery = `ALTER TABLE "serverData" ADD CONSTRAINT "serverData_name_unique" UNIQUE("name");`;
|
||||
// try {
|
||||
// log.info({}, "Running the manual fix");
|
||||
// await client.query(fixQuery);
|
||||
// } catch (e) {
|
||||
// log.info({ error: e }, "Fix was not completed");
|
||||
// }
|
||||
};
|
||||
|
||||
export const settingsMigrate = async () => {
|
||||
const log = createLogger({ module: "utils", subModule: "v1Migration" });
|
||||
const client = new Client({
|
||||
connectionString: process.env.DATABASE_URL_V1,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
let settingsV1: NewSetting[] = [];
|
||||
|
||||
try {
|
||||
log.info({}, "Running the manual fix");
|
||||
await client.query(fixQuery);
|
||||
const s = await client.query("SELECT * FROM settings");
|
||||
|
||||
settingsV1 = s.rows.map((i) => {
|
||||
return {
|
||||
name: i.name,
|
||||
value: i.value,
|
||||
description: i.description,
|
||||
moduleName: i.moduleName,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
log.info({ error: e }, "Fix was not completed");
|
||||
log.error({ error: e }, "There was an error getting the settings.");
|
||||
}
|
||||
|
||||
const { data, error } = await tryCatch(
|
||||
db
|
||||
.insert(settings)
|
||||
.values(settingsV1)
|
||||
.onConflictDoNothing()
|
||||
.returning({ name: settings.name }),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
log.error({ error }, "There was an error adding new settings");
|
||||
}
|
||||
|
||||
if (data) {
|
||||
log.info({ newSettingsAdded: data }, "New settings added");
|
||||
}
|
||||
};
|
||||
|
||||
// migrations after 230 go below here so we can keep this inline.
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
export const swaggerOptions = {
|
||||
definition: {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: "Logistics Support Tool",
|
||||
version: "1.0.0",
|
||||
},
|
||||
},
|
||||
// globs where swagger-jsdoc should look for annotations:
|
||||
apis: ["../../src/**/*.ts"],
|
||||
};
|
||||
@@ -18,6 +18,7 @@ export const forecastData = pgTable("forecast_Data", {
|
||||
quantity: real("quantity"),
|
||||
requirementDate: timestamp("requirement_date").notNull(),
|
||||
article: integer("article"),
|
||||
description: text("description"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { numeric, pgTable, serial, uuid } from "drizzle-orm/pg-core";
|
||||
import { numeric, pgTable, serial, text, uuid } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { forklifts } from "./forklifts.js";
|
||||
import { leaseInvoices } from "./leaseInvoices.js";
|
||||
|
||||
@@ -17,6 +17,7 @@ export const leaseInvoices = pgTable("lease_invoices", {
|
||||
invoiceNumber: text("invoice_number").unique().notNull(),
|
||||
invoiceDate: date("invoice_date").notNull(),
|
||||
totalAmount: numeric("total_amount"),
|
||||
comment: text("comment"),
|
||||
add_date: timestamp("add_date"),
|
||||
uploadedBy: text("uploaded_by"),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { returnFunc } from "../utils/return.js";
|
||||
import { connected, pool } from "./prodSqlConnect.js";
|
||||
import { validateEnv } from "../utils/envValidator.js";
|
||||
import { returnFunc } from "../utils/return.js";
|
||||
import {
|
||||
closePool,
|
||||
connected,
|
||||
pool,
|
||||
reconnecting,
|
||||
reconnectToSql,
|
||||
} from "./prodSqlConnect.js";
|
||||
|
||||
const env = validateEnv(process.env);
|
||||
/**
|
||||
@@ -11,48 +17,65 @@ const env = validateEnv(process.env);
|
||||
* You must use test1 always as it will be changed via query
|
||||
*/
|
||||
export async function prodQuery(queryToRun: string, name: string) {
|
||||
if (!connected) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
subModule: "query",
|
||||
level: "error",
|
||||
message: `The sql ${env.PROD_PLANT_TOKEN} is not connected`,
|
||||
notify: false,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
const query = queryToRun.replaceAll("test1", env.PROD_PLANT_TOKEN);
|
||||
try {
|
||||
const result = await pool.request().query(query);
|
||||
return {
|
||||
success: true,
|
||||
message: `Query results for: ${name}`,
|
||||
data: result.recordset,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
if (error.code === "ETIMEOUT") {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
subModule: "query",
|
||||
level: "error",
|
||||
message: `${name} did not run due to a timeout.`,
|
||||
notify: false,
|
||||
data: [error],
|
||||
});
|
||||
}
|
||||
if (!connected) {
|
||||
reconnectToSql();
|
||||
|
||||
if (error.code === "EREQUEST") {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
subModule: "query",
|
||||
level: "error",
|
||||
message: `${name} encountered an error ${error.originalError.info.message}`,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
if (reconnecting) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
subModule: "query",
|
||||
level: "error",
|
||||
message: `The sql ${env.PROD_PLANT_TOKEN} is trying to reconnect already`,
|
||||
notify: false,
|
||||
data: [],
|
||||
});
|
||||
} else {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
subModule: "query",
|
||||
level: "error",
|
||||
message: `The sql ${env.PROD_PLANT_TOKEN} is not connected`,
|
||||
notify: false,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const query = queryToRun.replaceAll("test1", env.PROD_PLANT_TOKEN);
|
||||
try {
|
||||
const result = await pool.request().query(query);
|
||||
return {
|
||||
success: true,
|
||||
message: `Query results for: ${name}`,
|
||||
data: result.recordset,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
if (error.code === "ETIMEOUT") {
|
||||
closePool();
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
subModule: "query",
|
||||
level: "error",
|
||||
message: `${name} did not run due to a timeout.`,
|
||||
notify: false,
|
||||
data: [error],
|
||||
});
|
||||
}
|
||||
|
||||
if (error.code === "EREQUEST") {
|
||||
closePool();
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
subModule: "query",
|
||||
level: "error",
|
||||
message: `${name} encountered an error ${error.originalError.info.message}`,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +1,134 @@
|
||||
import sql from "mssql";
|
||||
import { checkHostnamePort } from "../utils/checkHostNamePort.js";
|
||||
import { sqlConfig } from "./prodSqlConfig.js";
|
||||
import { createLogger } from "../logger/logger.js";
|
||||
import { returnFunc } from "../utils/return.js";
|
||||
import { checkHostnamePort } from "../utils/checkHostNamePort.js";
|
||||
import { validateEnv } from "../utils/envValidator.js";
|
||||
import { returnFunc } from "../utils/return.js";
|
||||
import { sqlConfig } from "./prodSqlConfig.js";
|
||||
|
||||
const env = validateEnv(process.env);
|
||||
|
||||
export let pool: any;
|
||||
export let connected: boolean = false;
|
||||
let reconnecting = false;
|
||||
export let reconnecting = false;
|
||||
|
||||
export const initializeProdPool = async () => {
|
||||
const log = createLogger({ module: "prodSql" });
|
||||
const log = createLogger({ module: "prodSql" });
|
||||
|
||||
const serverUp = await checkHostnamePort(`${env.PROD_SERVER}:1433`);
|
||||
const serverUp = await checkHostnamePort(`${env.PROD_SERVER}:1433`);
|
||||
|
||||
if (!serverUp) {
|
||||
reconnectToSql();
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
level: "fatal",
|
||||
message: `The sql ${env.PROD_SERVER} is not reachable`,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
if (!serverUp) {
|
||||
reconnectToSql();
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
level: "fatal",
|
||||
message: `The sql ${env.PROD_SERVER} is not reachable`,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
|
||||
// if you were restarting from the endpoint you get this lovely error
|
||||
if (connected) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
level: "error",
|
||||
message: `There is already a connection to ${env.PROD_PLANT_TOKEN}`,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
try {
|
||||
pool = await sql.connect(sqlConfig);
|
||||
// if you were restarting from the endpoint you get this lovely error
|
||||
if (connected) {
|
||||
return returnFunc({
|
||||
success: false,
|
||||
module: "prodSql",
|
||||
level: "error",
|
||||
message: `There is already a connection to ${env.PROD_PLANT_TOKEN}`,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
try {
|
||||
pool = await sql.connect(sqlConfig);
|
||||
|
||||
log.info(
|
||||
`Connected to ${sqlConfig?.server}, using DB: ${sqlConfig?.database}`
|
||||
);
|
||||
connected = true;
|
||||
} catch (error) {
|
||||
log.fatal(
|
||||
`${JSON.stringify(
|
||||
error
|
||||
)}, "There was an error connecting to the pool."`
|
||||
);
|
||||
reconnectToSql();
|
||||
// throw new Error("There was an error closing the sql connection");
|
||||
}
|
||||
log.info(
|
||||
`Connected to ${sqlConfig?.server}, using DB: ${sqlConfig?.database}`,
|
||||
);
|
||||
connected = true;
|
||||
} catch (error) {
|
||||
log.fatal(
|
||||
`${JSON.stringify(error)}, "There was an error connecting to the pool."`,
|
||||
);
|
||||
reconnectToSql();
|
||||
// throw new Error("There was an error closing the sql connection");
|
||||
}
|
||||
};
|
||||
|
||||
const reconnectToSql = async () => {
|
||||
const log = createLogger({ module: "prodSql" });
|
||||
if (reconnecting) return;
|
||||
reconnecting = true;
|
||||
export const reconnectToSql = async () => {
|
||||
const log = createLogger({ module: "prodSql" });
|
||||
if (reconnecting) return;
|
||||
reconnecting = true;
|
||||
|
||||
let delay = 2000; // start at 2s
|
||||
let attempts = 0;
|
||||
const maxAttempts = 10; // or limit by time, e.g. 2 min total
|
||||
let delay = 2000; // start at 2s
|
||||
let attempts = 0;
|
||||
const maxAttempts = 10; // or limit by time, e.g. 2 min total
|
||||
|
||||
while (!connected && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
log.info(
|
||||
`Reconnect attempt ${attempts}/${maxAttempts} in ${
|
||||
delay / 1000
|
||||
}s...`
|
||||
);
|
||||
while (!connected && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
log.info(
|
||||
`Reconnect attempt ${attempts}/${maxAttempts} in ${delay / 1000}s...`,
|
||||
);
|
||||
|
||||
await new Promise((res) => setTimeout(res, delay));
|
||||
await new Promise((res) => setTimeout(res, delay));
|
||||
|
||||
const serverUp = await checkHostnamePort(`${env.PROD_SERVER}:1433`);
|
||||
const serverUp = await checkHostnamePort(`${env.PROD_SERVER}:1433`);
|
||||
|
||||
if (!serverUp) {
|
||||
delay = Math.min(delay * 2, 30000); // exponential backoff up to 30s
|
||||
continue;
|
||||
}
|
||||
if (!serverUp) {
|
||||
delay = Math.min(delay * 2, 30000); // exponential backoff up to 30s
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
pool = sql.connect(sqlConfig);
|
||||
try {
|
||||
pool = sql.connect(sqlConfig);
|
||||
|
||||
log.info(
|
||||
`Connected to ${sqlConfig?.server}, and looking at ${sqlConfig?.database}`
|
||||
);
|
||||
reconnecting = false;
|
||||
connected = true;
|
||||
} catch (error) {
|
||||
log.fatal(
|
||||
`${JSON.stringify(
|
||||
error
|
||||
)}, "There was an error connecting to the pool."`
|
||||
);
|
||||
delay = Math.min(delay * 2, 30000); // exponential backoff up to 30s
|
||||
// throw new Error("There was an error closing the sql connection");
|
||||
}
|
||||
}
|
||||
log.info(
|
||||
`Connected to ${sqlConfig?.server}, and looking at ${sqlConfig?.database}`,
|
||||
);
|
||||
reconnecting = false;
|
||||
connected = true;
|
||||
} catch (error) {
|
||||
log.fatal(
|
||||
`${JSON.stringify(
|
||||
error,
|
||||
)}, "There was an error connecting to the pool."`,
|
||||
);
|
||||
delay = Math.min(delay * 2, 30000); // exponential backoff up to 30s
|
||||
// throw new Error("There was an error closing the sql connection");
|
||||
}
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
log.fatal(
|
||||
{ notify: true },
|
||||
"Max reconnect attempts reached on the prodSql server. Stopping retries."
|
||||
);
|
||||
reconnecting = false;
|
||||
// optional: exit process or alert someone here
|
||||
// process.exit(1);
|
||||
}
|
||||
if (!connected) {
|
||||
log.fatal(
|
||||
{ notify: true },
|
||||
"Max reconnect attempts reached on the prodSql server. Stopping retries.",
|
||||
);
|
||||
reconnecting = false;
|
||||
// exit process or alert someone here
|
||||
// process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
export const closePool = async () => {
|
||||
const log = createLogger({ module: "prodSql" });
|
||||
if (!connected) {
|
||||
log.error("There is no connection a connection.");
|
||||
return { success: false, message: "There is already a connection." };
|
||||
}
|
||||
try {
|
||||
await pool.close();
|
||||
log.info("Connection pool closed");
|
||||
connected = false;
|
||||
return {
|
||||
success: true,
|
||||
message: "The sql server connection has been closed",
|
||||
};
|
||||
} catch (error) {
|
||||
log.fatal(
|
||||
{ notify: true },
|
||||
`${JSON.stringify(
|
||||
error
|
||||
)}, "There was an error closing the sql connection"`
|
||||
);
|
||||
}
|
||||
const log = createLogger({ module: "prodSql" });
|
||||
if (!connected) {
|
||||
log.error("There is no connection a connection.");
|
||||
return { success: false, message: "There is already a connection." };
|
||||
}
|
||||
try {
|
||||
await pool.close();
|
||||
log.info("Connection pool closed");
|
||||
connected = false;
|
||||
return {
|
||||
success: true,
|
||||
message: "The sql server connection has been closed",
|
||||
};
|
||||
} catch (error) {
|
||||
connected = false;
|
||||
log.info(
|
||||
//{ notify: true },
|
||||
{ error: error },
|
||||
`${JSON.stringify(
|
||||
error,
|
||||
)}, "There was an error closing the sql connection"`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
export const activeArticle = `
|
||||
use AlplaPROD_test1
|
||||
|
||||
SELECT V_Artikel.IdArtikelvarianten,
|
||||
V_Artikel.Bezeichnung,
|
||||
V_Artikel.ArtikelvariantenTypBez,
|
||||
V_Artikel.PreisEinheitBez,
|
||||
SELECT V_Artikel.IdArtikelvarianten as article,
|
||||
V_Artikel.Bezeichnung as description,
|
||||
V_Artikel.ArtikelvariantenTypBez as articleType,
|
||||
V_Artikel.PreisEinheitBez as pricePoint,
|
||||
case when sales.price is null then 0 else sales.price end as salesPrice,
|
||||
TypeOfMaterial=CASE
|
||||
CASE
|
||||
WHEN
|
||||
V_Artikel.ArtikelvariantenTypBez LIKE'%Additive'
|
||||
Then 'AD'
|
||||
@@ -90,14 +90,15 @@ THEN 'Caps'
|
||||
When
|
||||
V_Artikel.ArtikelvariantenTypBez = 'Dummy'
|
||||
THEN 'Not used'
|
||||
ELSE 'Item not defined' END
|
||||
,V_Artikel.IdArtikelvariantenTyp,
|
||||
Round(V_Artikel.ArtikelGewicht, 3) as Article_Weight,
|
||||
IdAdresse,
|
||||
AdressBez,
|
||||
AdressTypBez,
|
||||
ProdBereichBez,
|
||||
FG=case when
|
||||
ELSE 'Item not defined' END as typeOfMaterial
|
||||
|
||||
,V_Artikel.IdArtikelvariantenTyp as articleIdType,
|
||||
Round(V_Artikel.ArtikelGewicht, 3) as articleWeight,
|
||||
IdAdresse as idAddress,
|
||||
AdressBez as addressDescription,
|
||||
AdressTypBez as addressType,
|
||||
ProdBereichBez as profitCenter,
|
||||
case when
|
||||
V_Artikel.ProdBereichBez = 'SBM' or
|
||||
V_Artikel.ProdBereichBez = 'IM-Caps' or
|
||||
V_Artikel.ProdBereichBez = 'IM-PET' or
|
||||
@@ -107,15 +108,16 @@ V_Artikel.ProdBereichBez = 'ISBM' or
|
||||
V_Artikel.ProdBereichBez = 'IM-Finishing'
|
||||
Then 'FG'
|
||||
Else 'not Defined Profit Center'
|
||||
end,
|
||||
end as fg,
|
||||
|
||||
V_Artikel.Umlaeufe as num_of_cycles,
|
||||
V_FibuKonten_BASIS.FibuKontoNr as CostsCenterId,
|
||||
V_FibuKonten_BASIS.Bezeichnung as CostCenterDescription,
|
||||
sales.[KdArtNr] as CustomerArticleNumber,
|
||||
sales.[KdArtBez] as CustomerArticleDescription,
|
||||
round(V_Artikel.Zyklus, 2) as CycleTime,
|
||||
V_FibuKonten_BASIS.FibuKontoNr as costsCenterId,
|
||||
V_FibuKonten_BASIS.Bezeichnung as costCenterDescription,
|
||||
sales.[KdArtNr] as customerArticleNumber,
|
||||
sales.[KdArtBez] as customerArticleDescription,
|
||||
round(V_Artikel.Zyklus, 2) as cycleTime,
|
||||
Sypronummer as salesAgreement,
|
||||
V_Artikel.ProdArtikelBez as ProductFamily
|
||||
V_Artikel.ProdArtikelBez as productFamily
|
||||
--,REPLACE(pur.UOM,'UOM:','')
|
||||
,Case when LEFT(
|
||||
LTRIM(REPLACE(pur.UOM,'UOM:','')),
|
||||
@@ -123,7 +125,7 @@ V_Artikel.ProdArtikelBez as ProductFamily
|
||||
) is null then '1' else LEFT(
|
||||
LTRIM(REPLACE(pur.UOM,'UOM:','')),
|
||||
CHARINDEX(' ', LTRIM(REPLACE(REPLACE(pur.UOM,'UOM:',''), CHAR(13)+CHAR(10), ' ')) + ' ') - 1
|
||||
) end AS UOM
|
||||
) end AS uom
|
||||
--,*
|
||||
FROM dbo.V_Artikel (nolock)
|
||||
|
||||
|
||||
@@ -1,123 +1,125 @@
|
||||
import type { Address } from "nodemailer/lib/mailer/index.js";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import type SMTPTransport from "nodemailer/lib/smtp-transport/index.js";
|
||||
import type Mail from "nodemailer/lib/mailer/index.js";
|
||||
import os from "os";
|
||||
import nodemailer from "nodemailer";
|
||||
import type Mail from "nodemailer/lib/mailer/index.js";
|
||||
import type { Address } from "nodemailer/lib/mailer/index.js";
|
||||
import type SMTPTransport from "nodemailer/lib/smtp-transport/index.js";
|
||||
import hbs from "nodemailer-express-handlebars";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { promisify } from "util";
|
||||
import hbs from "nodemailer-express-handlebars";
|
||||
import { createLogger } from "../../logger/logger.js";
|
||||
|
||||
interface HandlebarsMailOptions extends Mail.Options {
|
||||
template: string;
|
||||
context: Record<string, unknown>;
|
||||
template: string;
|
||||
context: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface EmailData {
|
||||
email: string;
|
||||
subject: string;
|
||||
template: string;
|
||||
context: Record<string, unknown>;
|
||||
email: string;
|
||||
subject: string;
|
||||
template: string;
|
||||
context: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export const sendEmail = async (data: EmailData): Promise<any> => {
|
||||
const log = createLogger({ module: "pkg", subModule: "sendMail" });
|
||||
let transporter: Transporter;
|
||||
let fromEmail: string | Address;
|
||||
const log = createLogger({ module: "pkg", subModule: "sendMail" });
|
||||
let transporter: Transporter;
|
||||
let fromEmail: string | Address;
|
||||
|
||||
if (
|
||||
os.hostname().includes("OLP") &&
|
||||
process.env.EMAIL_USER &&
|
||||
process.env.EMAIL_PASSWORD
|
||||
) {
|
||||
transporter = nodemailer.createTransport({
|
||||
service: "gmail",
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASSWORD,
|
||||
},
|
||||
//debug: true,
|
||||
});
|
||||
// if (
|
||||
// os.hostname().includes("OLP") &&
|
||||
// process.env.EMAIL_USER &&
|
||||
// process.env.EMAIL_PASSWORD
|
||||
// ) {
|
||||
// transporter = nodemailer.createTransport({
|
||||
// service: "gmail",
|
||||
// auth: {
|
||||
// user: process.env.EMAIL_USER,
|
||||
// pass: process.env.EMAIL_PASSWORD,
|
||||
// },
|
||||
// //debug: true,
|
||||
// });
|
||||
|
||||
// update the from email
|
||||
fromEmail = process.env.EMAIL_USER;
|
||||
} else {
|
||||
// convert to the correct plant token.
|
||||
// // update the from email
|
||||
// fromEmail = process.env.EMAIL_USER;
|
||||
// } else {
|
||||
// // convert to the correct plant token.
|
||||
|
||||
let host = `${os.hostname().replace("VMS006", "")}-smtp.alpla.net`;
|
||||
//let host = `${os.hostname().replace("VMS006", "")}-smtp.alpla.net`;
|
||||
|
||||
//const testServers = ["vms036", "VMS036"];
|
||||
//const testServers = ["vms036", "VMS036"];
|
||||
|
||||
if (os.hostname().includes("VMS036")) {
|
||||
host = "USMCD1-smtp.alpla.net";
|
||||
}
|
||||
// if (os.hostname().includes("VMS036")) {
|
||||
// host = "USMCD1-smtp.alpla.net";
|
||||
// }
|
||||
|
||||
// if (plantToken[0].value === "usiow2") {
|
||||
// host = "USIOW1-smtp.alpla.net";
|
||||
// }
|
||||
// if (plantToken[0].value === "usiow2") {
|
||||
// host = "USIOW1-smtp.alpla.net";
|
||||
// }
|
||||
|
||||
transporter = nodemailer.createTransport({
|
||||
host: host,
|
||||
port: 25,
|
||||
rejectUnauthorized: false,
|
||||
//secure: false,
|
||||
// auth: {
|
||||
// user: "alplaprod",
|
||||
// pass: "obelix",
|
||||
// },
|
||||
debug: true,
|
||||
} as SMTPTransport.Options);
|
||||
transporter = nodemailer.createTransport({
|
||||
host: "smtp.azurecomm.net",
|
||||
port: 587,
|
||||
//rejectUnauthorized: false,
|
||||
tls: {
|
||||
minVersion: "TLSv1.2",
|
||||
},
|
||||
auth: {
|
||||
user: "donotreply@mail.alpla.com",
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
},
|
||||
debug: true,
|
||||
} as SMTPTransport.Options);
|
||||
|
||||
// update the from email
|
||||
fromEmail = `noreply@alpla.com`;
|
||||
}
|
||||
// update the from email
|
||||
fromEmail = `DoNotReply@mail.alpla.com`;
|
||||
//}
|
||||
|
||||
// creating the handlbar options
|
||||
const viewPath = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"./views/"
|
||||
);
|
||||
// creating the handlbar options
|
||||
const viewPath = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"./views/",
|
||||
);
|
||||
|
||||
const handlebarOptions = {
|
||||
viewEngine: {
|
||||
extname: ".hbs",
|
||||
//layoutsDir: path.resolve(viewPath, "layouts"), // Path to layouts directory
|
||||
defaultLayout: "", // Specify the default layout
|
||||
partialsDir: viewPath,
|
||||
},
|
||||
viewPath: viewPath,
|
||||
extName: ".hbs", // File extension for Handlebars templates
|
||||
};
|
||||
const handlebarOptions = {
|
||||
viewEngine: {
|
||||
extname: ".hbs",
|
||||
//layoutsDir: path.resolve(viewPath, "layouts"), // Path to layouts directory
|
||||
defaultLayout: "", // Specify the default layout
|
||||
partialsDir: viewPath,
|
||||
},
|
||||
viewPath: viewPath,
|
||||
extName: ".hbs", // File extension for Handlebars templates
|
||||
};
|
||||
|
||||
transporter.use("compile", hbs(handlebarOptions));
|
||||
transporter.use("compile", hbs(handlebarOptions));
|
||||
|
||||
const mailOptions: HandlebarsMailOptions = {
|
||||
from: fromEmail,
|
||||
to: data.email,
|
||||
subject: data.subject,
|
||||
//text: "You will have a reset token here and only have 30min to click the link before it expires.",
|
||||
//html: emailTemplate("BlakesTest", "This is an example with css"),
|
||||
template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs')
|
||||
context: data.context,
|
||||
};
|
||||
const mailOptions: HandlebarsMailOptions = {
|
||||
from: fromEmail,
|
||||
to: data.email,
|
||||
subject: data.subject,
|
||||
//text: "You will have a reset token here and only have 30min to click the link before it expires.",
|
||||
//html: emailTemplate("BlakesTest", "This is an example with css"),
|
||||
template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs')
|
||||
context: data.context,
|
||||
};
|
||||
|
||||
// now verify and send the email
|
||||
const sendMailPromise = promisify(transporter.sendMail).bind(transporter);
|
||||
// now verify and send the email
|
||||
const sendMailPromise = promisify(transporter.sendMail).bind(transporter);
|
||||
|
||||
try {
|
||||
// Send email and await the result
|
||||
const info = await sendMailPromise(mailOptions);
|
||||
log.info(null, `Email was sent to: ${data.email}`);
|
||||
return { success: true, message: "Email sent.", data: info };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
log.error(
|
||||
{ error: err },
|
||||
try {
|
||||
// Send email and await the result
|
||||
const info = await sendMailPromise(mailOptions);
|
||||
log.info(null, `Email was sent to: ${data.email}`);
|
||||
return { success: true, message: "Email sent.", data: info };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
log.error(
|
||||
{ error: err },
|
||||
|
||||
`Error sending Email to : ${data.email}`
|
||||
);
|
||||
return { success: false, message: "Error sending email.", error: err };
|
||||
}
|
||||
`Error sending Email to : ${data.email}`,
|
||||
);
|
||||
return { success: false, message: "Error sending email.", error: err };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,7 +76,9 @@ export const prodEndpoint = async <T>(
|
||||
return {
|
||||
success: false,
|
||||
message: "There was an error processing the endpoint",
|
||||
data: apiError.response.data,
|
||||
data: apiError.response
|
||||
? apiError.response.data
|
||||
: [{ error: "There was an internal error." }],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ func runNPMInstall(rootDir string, folder string) error {
|
||||
} else {
|
||||
folderDir = filepath.Join(rootDir, folder)
|
||||
}
|
||||
cmd := exec.Command("npm", "install")
|
||||
cmd := exec.Command("npm", "install", "--production")
|
||||
cmd.Dir = folderDir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
1959
frontend/package-lock.json
generated
1959
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,70 +11,70 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@radix-ui/react-avatar": "^1.1.10",
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@react-pdf/renderer": "^4.3.1",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@tanstack/react-form": "^1.23.0",
|
||||
"@tanstack/react-query": "^5.89.0",
|
||||
"@tanstack/react-query-devtools": "^5.90.2",
|
||||
"@tanstack/react-router": "^1.131.36",
|
||||
"@tanstack/react-router-devtools": "^1.131.36",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@tanstack/react-form": "^1.26.0",
|
||||
"@tanstack/react-query": "^5.90.11",
|
||||
"@tanstack/react-query-devtools": "^5.91.1",
|
||||
"@tanstack/react-router": "^1.139.6",
|
||||
"@tanstack/react-router-devtools": "^1.139.6",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@types/react-calendar-timeline": "^0.28.6",
|
||||
"axios": "^1.12.2",
|
||||
"better-auth": "^1.3.11",
|
||||
"axios": "^1.13.2",
|
||||
"better-auth": "^1.4.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"is-mobile": "^5.0.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsbarcode": "^3.12.1",
|
||||
"lucide-react": "^0.542.0",
|
||||
"marked": "^16.4.1",
|
||||
"lucide-react": "^0.554.0",
|
||||
"marked": "^17.0.1",
|
||||
"moment": "^2.30.1",
|
||||
"r": "^0.0.5",
|
||||
"react": "^19.1.1",
|
||||
"react": "^19.2.0",
|
||||
"react-barcode": "^1.6.1",
|
||||
"react-calendar-timeline": "^0.30.0-beta.3",
|
||||
"react-day-picker": "^9.11.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-hook-form": "^7.65.0",
|
||||
"react-calendar-timeline": "^0.30.0-beta.4",
|
||||
"react-day-picker": "^9.11.2",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-hook-form": "^7.66.1",
|
||||
"react-resizable-panels": "^3.0.6",
|
||||
"recharts": "^2.15.4",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@tanstack/router-plugin": "^1.131.36",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@tanstack/router-plugin": "^1.139.6",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^24.3.1",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@vitejs/plugin-react-swc": "^4.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"tw-animate-css": "^1.3.8",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite": "^7.1.2"
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react-swc": "^4.2.2",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.48.0",
|
||||
"vite": "^7.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function Admin() {
|
||||
const items: Items[] = [
|
||||
{
|
||||
title: "Users",
|
||||
url: "/lst/app/admin/users",
|
||||
url: "/admin/users",
|
||||
icon: User,
|
||||
role: ["systemAdmin", "admin"],
|
||||
module: "admin",
|
||||
@@ -23,7 +23,7 @@ export default function Admin() {
|
||||
},
|
||||
{
|
||||
title: "System",
|
||||
url: "/lst/app/admin/settings",
|
||||
url: "/admin/settings",
|
||||
icon: Settings,
|
||||
role: ["systemAdmin", "admin"],
|
||||
module: "admin",
|
||||
@@ -31,7 +31,7 @@ export default function Admin() {
|
||||
},
|
||||
{
|
||||
title: "Modules",
|
||||
url: "/lst/app/admin/modules",
|
||||
url: "/admin/modules",
|
||||
icon: Settings,
|
||||
role: ["systemAdmin", "admin"],
|
||||
module: "admin",
|
||||
@@ -39,7 +39,7 @@ export default function Admin() {
|
||||
},
|
||||
{
|
||||
title: "Servers",
|
||||
url: "/lst/app/admin/servers",
|
||||
url: "/admin/servers",
|
||||
icon: Server,
|
||||
role: ["systemAdmin", "admin"],
|
||||
module: "admin",
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function ForkliftSideBar() {
|
||||
const items: Items[] = [
|
||||
{
|
||||
title: "Lease Companies",
|
||||
url: "/lst/app/forklifts/companies",
|
||||
url: "/forklifts/companies",
|
||||
icon: Building2,
|
||||
role: ["systemAdmin", "admin"],
|
||||
module: "forklifts",
|
||||
@@ -29,7 +29,7 @@ export default function ForkliftSideBar() {
|
||||
},
|
||||
{
|
||||
title: "Leases",
|
||||
url: "/lst/app/forklifts/leases",
|
||||
url: "/forklifts/leases",
|
||||
icon: ReceiptText,
|
||||
role: ["systemAdmin", "admin"],
|
||||
module: "forklifts",
|
||||
@@ -37,7 +37,7 @@ export default function ForkliftSideBar() {
|
||||
},
|
||||
{
|
||||
title: "Invoices",
|
||||
url: "/lst/app/admin/settings",
|
||||
url: "/forklifts/invoices",
|
||||
icon: ReceiptText,
|
||||
role: ["systemAdmin", "admin", "manager"],
|
||||
module: "forklifts",
|
||||
@@ -45,7 +45,7 @@ export default function ForkliftSideBar() {
|
||||
},
|
||||
{
|
||||
title: "Repairs",
|
||||
url: "/lst/app/admin/settings",
|
||||
url: "/admin/settings",
|
||||
icon: Wrench,
|
||||
role: ["systemAdmin", "admin", "manager"],
|
||||
module: "forklifts",
|
||||
@@ -53,7 +53,7 @@ export default function ForkliftSideBar() {
|
||||
},
|
||||
{
|
||||
title: "Hours",
|
||||
url: "/lst/app/admin/settings",
|
||||
url: "/admin/settings",
|
||||
icon: Hourglass,
|
||||
role: ["systemAdmin", "admin", "manager", "supervisor"],
|
||||
module: "forklifts",
|
||||
@@ -61,7 +61,7 @@ export default function ForkliftSideBar() {
|
||||
},
|
||||
{
|
||||
title: "Forklifts",
|
||||
url: "/lst/app/admin/modules",
|
||||
url: "/forklifts/forklifts",
|
||||
icon: Forklift,
|
||||
role: ["systemAdmin", "admin", "manager", "supervisor"],
|
||||
module: "forklifts",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Link, useRouterState } from "@tanstack/react-router";
|
||||
import { useState } from "react";
|
||||
import NewCompanyForm from "@/routes/_app/_forklifts/-components/NewCompany";
|
||||
import NewForklift from "@/routes/_app/_forklifts/-components/NewForklift";
|
||||
import NewInvoice from "@/routes/_app/_forklifts/-components/NewInvoice";
|
||||
import NewLeaseForm from "@/routes/_app/_forklifts/-components/NewLease";
|
||||
import { useAuth, useLogout } from "../../lib/authClient";
|
||||
import { ModeToggle } from "../mode-toggle";
|
||||
@@ -23,6 +25,8 @@ export default function Nav() {
|
||||
const currentPath = router.location.href;
|
||||
const [openDialog, setOpenDialog] = useState(false);
|
||||
const [openLeaseDialog, setOpenLeaseDialog] = useState(false);
|
||||
const [openInvoiceDialog, setOpenInvoiceDialog] = useState(false);
|
||||
const [openForkliftDialog, setOpenForkliftDialog] = useState(false);
|
||||
return (
|
||||
<nav className="flex justify-end w-full shadow ">
|
||||
<div className="m-2 flex flex-row gap-1">
|
||||
@@ -65,6 +69,22 @@ export default function Nav() {
|
||||
>
|
||||
New Lease
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
// just open the dialog when clicked
|
||||
setOpenInvoiceDialog(true);
|
||||
}}
|
||||
>
|
||||
New Invoice
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
// just open the dialog when clicked
|
||||
setOpenForkliftDialog(true);
|
||||
}}
|
||||
>
|
||||
New Forklift
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{/* Company */}
|
||||
@@ -85,6 +105,26 @@ export default function Nav() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
{openInvoiceDialog && (
|
||||
<Dialog
|
||||
open={openInvoiceDialog}
|
||||
onOpenChange={setOpenInvoiceDialog}
|
||||
>
|
||||
<DialogContent className="sm:max-w-fit">
|
||||
<NewInvoice setOpenInvoiceDialog={setOpenInvoiceDialog} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
{openForkliftDialog && (
|
||||
<Dialog
|
||||
open={openForkliftDialog}
|
||||
onOpenChange={setOpenForkliftDialog}
|
||||
>
|
||||
<DialogContent className="sm:max-w-fit">
|
||||
<NewForklift setOpenDialog={setOpenForkliftDialog} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
184
frontend/src/components/ui/command.tsx
Normal file
184
frontend/src/components/ui/command.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Command as CommandPrimitive } from "cmdk"
|
||||
import { SearchIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
function Command({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive>) {
|
||||
return (
|
||||
<CommandPrimitive
|
||||
data-slot="command"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandDialog({
|
||||
title = "Command Palette",
|
||||
description = "Search for a command to run...",
|
||||
children,
|
||||
className,
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Dialog> & {
|
||||
title?: string
|
||||
description?: string
|
||||
className?: string
|
||||
showCloseButton?: boolean
|
||||
}) {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogHeader className="sr-only">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent
|
||||
className={cn("overflow-hidden p-0", className)}
|
||||
showCloseButton={showCloseButton}
|
||||
>
|
||||
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandInput({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="command-input-wrapper"
|
||||
className="flex h-9 items-center gap-2 border-b px-3"
|
||||
>
|
||||
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
className={cn(
|
||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||
return (
|
||||
<CommandPrimitive.List
|
||||
data-slot="command-list"
|
||||
className={cn(
|
||||
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandEmpty({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||
return (
|
||||
<CommandPrimitive.Empty
|
||||
data-slot="command-empty"
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||
return (
|
||||
<CommandPrimitive.Group
|
||||
data-slot="command-group"
|
||||
className={cn(
|
||||
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||
return (
|
||||
<CommandPrimitive.Separator
|
||||
data-slot="command-separator"
|
||||
className={cn("bg-border -mx-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||
return (
|
||||
<CommandPrimitive.Item
|
||||
data-slot="command-item"
|
||||
className={cn(
|
||||
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="command-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
}
|
||||
@@ -26,7 +26,8 @@ export type UserRoles = {
|
||||
| "supervisor"
|
||||
| "manager"
|
||||
| "admin"
|
||||
| "systemAdmin";
|
||||
| "systemAdmin"
|
||||
| "user";
|
||||
};
|
||||
|
||||
type UserRoleState = {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FieldErrors } from "./FieldErrors";
|
||||
|
||||
type DateFieldProps = {
|
||||
label: string;
|
||||
required: boolean;
|
||||
};
|
||||
export const DateField = ({ label }: DateFieldProps) => {
|
||||
const field = useFieldContext<any>();
|
||||
@@ -37,6 +38,7 @@ export const DateField = ({ label }: DateFieldProps) => {
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
//required={required}
|
||||
captionLayout="dropdown"
|
||||
startMonth={new Date(new Date().getFullYear() - 10, 0)}
|
||||
endMonth={new Date(new Date().getFullYear() + 20, 0)}
|
||||
|
||||
98
frontend/src/lib/formStuff/components/Searchable.tsx
Normal file
98
frontend/src/lib/formStuff/components/Searchable.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Check, ChevronsUpDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label } from "../../../components/ui/label";
|
||||
import { useFieldContext } from "..";
|
||||
import { FieldErrors } from "./FieldErrors";
|
||||
|
||||
type SelectOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type SelectFieldProps = {
|
||||
label: string;
|
||||
options: SelectOption[];
|
||||
searchFor?: string;
|
||||
};
|
||||
|
||||
export const Searchable = ({ label, options, searchFor }: SelectFieldProps) => {
|
||||
const field = useFieldContext<string>();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<div className="grid gap-3">
|
||||
<Label htmlFor={field.name}>{label}</Label>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-[200px] justify-between"
|
||||
>
|
||||
{field.state.value
|
||||
? options.find((options) => options.value === field.state.value)
|
||||
?.label
|
||||
: `Search ${searchFor}...`}
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder={`Search ${searchFor}...`}
|
||||
className="h-9"
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>{`No ${searchFor} found.`}</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{options.map((options) => (
|
||||
<CommandItem
|
||||
key={options.value}
|
||||
value={options.value}
|
||||
onSelect={(currentValue) => {
|
||||
field.handleChange(
|
||||
currentValue === field.state.value
|
||||
? ""
|
||||
: currentValue,
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{options.label}
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto",
|
||||
field.state.value === options.value
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<FieldErrors meta={field.state.meta} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
28
frontend/src/lib/formStuff/components/TextArea.tsx
Normal file
28
frontend/src/lib/formStuff/components/TextArea.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Label } from "../../../components/ui/label";
|
||||
import { useFieldContext } from "..";
|
||||
import { FieldErrors } from "./FieldErrors";
|
||||
|
||||
type InputFieldProps = {
|
||||
label: string;
|
||||
placeHolder: string;
|
||||
required: boolean;
|
||||
};
|
||||
export const TextArea = ({ label, placeHolder, required }: InputFieldProps) => {
|
||||
const field = useFieldContext<any>();
|
||||
|
||||
return (
|
||||
<div className="grid gap-3 w-64">
|
||||
<Label htmlFor={field.name}>{label}</Label>
|
||||
<Textarea
|
||||
id={field.name}
|
||||
value={field.state.value}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
placeholder={placeHolder}
|
||||
onBlur={field.handleBlur}
|
||||
required={required}
|
||||
/>
|
||||
<FieldErrors meta={field.state.meta} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,8 +3,10 @@ import { DateField } from "./components/CalenderSelect";
|
||||
import { CheckboxField } from "./components/CheckBox";
|
||||
import { InputField } from "./components/InputField";
|
||||
import { InputPasswordField } from "./components/InputPasswordField";
|
||||
import { Searchable } from "./components/Searchable";
|
||||
import { SelectField } from "./components/SelectField";
|
||||
import { SubmitButton } from "./components/SubmitButton";
|
||||
import { TextArea } from "./components/TextArea";
|
||||
|
||||
export const { fieldContext, useFieldContext, formContext, useFormContext } =
|
||||
createFormHookContexts();
|
||||
@@ -16,6 +18,8 @@ export const { useAppForm } = createFormHook({
|
||||
SelectField,
|
||||
CheckboxField,
|
||||
DateField,
|
||||
TextArea,
|
||||
Searchable,
|
||||
},
|
||||
formComponents: { SubmitButton },
|
||||
fieldContext,
|
||||
|
||||
18
frontend/src/lib/querys/admin/getSettings.ts
Normal file
18
frontend/src/lib/querys/admin/getSettings.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
|
||||
export function getSettings() {
|
||||
return queryOptions({
|
||||
queryKey: ["getSettings"],
|
||||
queryFn: () => fetchSession(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
const fetchSession = async () => {
|
||||
const { data } = await axios.get("/lst/api/system/settings");
|
||||
|
||||
return data.data;
|
||||
};
|
||||
17
frontend/src/lib/querys/forklifts/getForklifts.ts
Normal file
17
frontend/src/lib/querys/forklifts/getForklifts.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
|
||||
export function getForklifts() {
|
||||
return queryOptions({
|
||||
queryKey: ["getForklifts"],
|
||||
queryFn: () => fetch(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
}
|
||||
|
||||
const fetch = async () => {
|
||||
const { data } = await axios.get("/lst/api/forklifts");
|
||||
|
||||
return data.data;
|
||||
};
|
||||
17
frontend/src/lib/querys/forklifts/getInvoices.ts
Normal file
17
frontend/src/lib/querys/forklifts/getInvoices.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
|
||||
export function getInvoices() {
|
||||
return queryOptions({
|
||||
queryKey: ["getInvoices"],
|
||||
queryFn: () => fetch(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
}
|
||||
|
||||
const fetch = async () => {
|
||||
const { data } = await axios.get("/lst/api/forklifts/invoices");
|
||||
|
||||
return data.data;
|
||||
};
|
||||
26
frontend/src/lib/tableStuff/GenericColumn.tsx
Normal file
26
frontend/src/lib/tableStuff/GenericColumn.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export const GenericColumn = ({ columnName }: { columnName: string }) => {
|
||||
const columnHelper = createColumnHelper();
|
||||
|
||||
return columnHelper.accessor(`${columnName}`, {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">{`${columnName.toUpperCase()}`}</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: (i) => i.getValue(),
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
type SortingState,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
} from "@tanstack/react-table";
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -25,6 +27,9 @@ export default function TableNoExpand({
|
||||
columns: any;
|
||||
}) {
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
// const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
// []
|
||||
// )
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
@@ -32,16 +37,20 @@ export default function TableNoExpand({
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
onSortingChange: setSorting,
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
|
||||
//renderSubComponent: ({ row }: { row: any }) => <ExpandedRow row={row} />,
|
||||
//getRowCanExpand: () => true,
|
||||
filterFns: {},
|
||||
state: {
|
||||
sorting,
|
||||
//columnFilters
|
||||
},
|
||||
});
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="w-fit">
|
||||
<Table>
|
||||
<Table className="table-fixed w-full">
|
||||
<ScrollArea className="w-full rounded-md border whitespace-nowrap">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
@@ -87,25 +96,27 @@ export default function TableNoExpand({
|
||||
</React.Fragment>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
</Table>
|
||||
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Route as AppAdminLayoutRouteRouteImport } from './routes/_app/_adminLay
|
||||
import { Route as OldOldIndexRouteImport } from './routes/_old/old/index'
|
||||
import { Route as AppauthLoginRouteImport } from './routes/_app/(auth)/login'
|
||||
import { Route as OldOldRfidIndexRouteImport } from './routes/_old/old/rfid/index'
|
||||
import { Route as OldOldQualityIndexRouteImport } from './routes/_old/old/quality/index'
|
||||
import { Route as OldOldOcpIndexRouteImport } from './routes/_old/old/ocp/index'
|
||||
import { Route as MobileMobileLayoutMIndexRouteImport } from './routes/_mobile/_mobileLayout/m/index'
|
||||
import { Route as AppForkliftsForkliftsIndexRouteImport } from './routes/_app/_forklifts/forklifts/index'
|
||||
@@ -29,6 +30,8 @@ import { Route as MobileMobileLayoutMRelocateRouteImport } from './routes/_mobil
|
||||
import { Route as MobileMobileLayoutMDeliveryRouteImport } from './routes/_mobile/_mobileLayout/m/delivery'
|
||||
import { Route as MobileMobileLayoutMCyclecountsRouteImport } from './routes/_mobile/_mobileLayout/m/cyclecounts'
|
||||
import { Route as AppForkliftsForkliftsLeasesRouteImport } from './routes/_app/_forklifts/forklifts/leases'
|
||||
import { Route as AppForkliftsForkliftsInvoicesRouteImport } from './routes/_app/_forklifts/forklifts/invoices'
|
||||
import { Route as AppForkliftsForkliftsForkliftsRouteImport } from './routes/_app/_forklifts/forklifts/forklifts'
|
||||
import { Route as AppForkliftsForkliftsCompaniesRouteImport } from './routes/_app/_forklifts/forklifts/companies'
|
||||
import { Route as AppAdminLayoutAdminServersRouteImport } from './routes/_app/_adminLayout/admin/servers'
|
||||
import { Route as ApplogisticsLogisticsDeliveryScheduleRouteImport } from './routes/_app/(logistics)/logistics/deliverySchedule'
|
||||
@@ -106,6 +109,11 @@ const OldOldRfidIndexRoute = OldOldRfidIndexRouteImport.update({
|
||||
path: '/rfid/',
|
||||
getParentRoute: () => OldOldRouteRoute,
|
||||
} as any)
|
||||
const OldOldQualityIndexRoute = OldOldQualityIndexRouteImport.update({
|
||||
id: '/quality/',
|
||||
path: '/quality/',
|
||||
getParentRoute: () => OldOldRouteRoute,
|
||||
} as any)
|
||||
const OldOldOcpIndexRoute = OldOldOcpIndexRouteImport.update({
|
||||
id: '/ocp/',
|
||||
path: '/ocp/',
|
||||
@@ -152,6 +160,18 @@ const AppForkliftsForkliftsLeasesRoute =
|
||||
path: '/forklifts/leases',
|
||||
getParentRoute: () => AppForkliftsRouteRoute,
|
||||
} as any)
|
||||
const AppForkliftsForkliftsInvoicesRoute =
|
||||
AppForkliftsForkliftsInvoicesRouteImport.update({
|
||||
id: '/forklifts/invoices',
|
||||
path: '/forklifts/invoices',
|
||||
getParentRoute: () => AppForkliftsRouteRoute,
|
||||
} as any)
|
||||
const AppForkliftsForkliftsForkliftsRoute =
|
||||
AppForkliftsForkliftsForkliftsRouteImport.update({
|
||||
id: '/forklifts/forklifts',
|
||||
path: '/forklifts/forklifts',
|
||||
getParentRoute: () => AppForkliftsRouteRoute,
|
||||
} as any)
|
||||
const AppForkliftsForkliftsCompaniesRoute =
|
||||
AppForkliftsForkliftsCompaniesRouteImport.update({
|
||||
id: '/forklifts/companies',
|
||||
@@ -287,6 +307,8 @@ export interface FileRoutesByFullPath {
|
||||
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
||||
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
||||
'/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
||||
'/forklifts/forklifts': typeof AppForkliftsForkliftsForkliftsRoute
|
||||
'/forklifts/invoices': typeof AppForkliftsForkliftsInvoicesRoute
|
||||
'/forklifts/leases': typeof AppForkliftsForkliftsLeasesRoute
|
||||
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
||||
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
||||
@@ -295,6 +317,7 @@ export interface FileRoutesByFullPath {
|
||||
'/forklifts': typeof AppForkliftsForkliftsIndexRoute
|
||||
'/m': typeof MobileMobileLayoutMIndexRoute
|
||||
'/old/ocp': typeof OldOldOcpIndexRoute
|
||||
'/old/quality': typeof OldOldQualityIndexRoute
|
||||
'/old/rfid': typeof OldOldRfidIndexRoute
|
||||
'/admin/modules': typeof AppAdminLayoutAdminSystemModulesRoute
|
||||
'/admin/settings': typeof AppAdminLayoutAdminSystemSettingsRoute
|
||||
@@ -322,6 +345,8 @@ export interface FileRoutesByTo {
|
||||
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
||||
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
||||
'/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
||||
'/forklifts/forklifts': typeof AppForkliftsForkliftsForkliftsRoute
|
||||
'/forklifts/invoices': typeof AppForkliftsForkliftsInvoicesRoute
|
||||
'/forklifts/leases': typeof AppForkliftsForkliftsLeasesRoute
|
||||
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
||||
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
||||
@@ -330,6 +355,7 @@ export interface FileRoutesByTo {
|
||||
'/forklifts': typeof AppForkliftsForkliftsIndexRoute
|
||||
'/m': typeof MobileMobileLayoutMIndexRoute
|
||||
'/old/ocp': typeof OldOldOcpIndexRoute
|
||||
'/old/quality': typeof OldOldQualityIndexRoute
|
||||
'/old/rfid': typeof OldOldRfidIndexRoute
|
||||
'/admin/modules': typeof AppAdminLayoutAdminSystemModulesRoute
|
||||
'/admin/settings': typeof AppAdminLayoutAdminSystemSettingsRoute
|
||||
@@ -365,6 +391,8 @@ export interface FileRoutesById {
|
||||
'/_app/(logistics)/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
||||
'/_app/_adminLayout/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
||||
'/_app/_forklifts/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
||||
'/_app/_forklifts/forklifts/forklifts': typeof AppForkliftsForkliftsForkliftsRoute
|
||||
'/_app/_forklifts/forklifts/invoices': typeof AppForkliftsForkliftsInvoicesRoute
|
||||
'/_app/_forklifts/forklifts/leases': typeof AppForkliftsForkliftsLeasesRoute
|
||||
'/_mobile/_mobileLayout/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
||||
'/_mobile/_mobileLayout/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
||||
@@ -373,6 +401,7 @@ export interface FileRoutesById {
|
||||
'/_app/_forklifts/forklifts/': typeof AppForkliftsForkliftsIndexRoute
|
||||
'/_mobile/_mobileLayout/m/': typeof MobileMobileLayoutMIndexRoute
|
||||
'/_old/old/ocp/': typeof OldOldOcpIndexRoute
|
||||
'/_old/old/quality/': typeof OldOldQualityIndexRoute
|
||||
'/_old/old/rfid/': typeof OldOldRfidIndexRoute
|
||||
'/_app/_adminLayout/admin/_system/modules': typeof AppAdminLayoutAdminSystemModulesRoute
|
||||
'/_app/_adminLayout/admin/_system/settings': typeof AppAdminLayoutAdminSystemSettingsRoute
|
||||
@@ -403,6 +432,8 @@ export interface FileRouteTypes {
|
||||
| '/logistics/deliverySchedule'
|
||||
| '/admin/servers'
|
||||
| '/forklifts/companies'
|
||||
| '/forklifts/forklifts'
|
||||
| '/forklifts/invoices'
|
||||
| '/forklifts/leases'
|
||||
| '/m/cyclecounts'
|
||||
| '/m/delivery'
|
||||
@@ -411,6 +442,7 @@ export interface FileRouteTypes {
|
||||
| '/forklifts'
|
||||
| '/m'
|
||||
| '/old/ocp'
|
||||
| '/old/quality'
|
||||
| '/old/rfid'
|
||||
| '/admin/modules'
|
||||
| '/admin/settings'
|
||||
@@ -438,6 +470,8 @@ export interface FileRouteTypes {
|
||||
| '/logistics/deliverySchedule'
|
||||
| '/admin/servers'
|
||||
| '/forklifts/companies'
|
||||
| '/forklifts/forklifts'
|
||||
| '/forklifts/invoices'
|
||||
| '/forklifts/leases'
|
||||
| '/m/cyclecounts'
|
||||
| '/m/delivery'
|
||||
@@ -446,6 +480,7 @@ export interface FileRouteTypes {
|
||||
| '/forklifts'
|
||||
| '/m'
|
||||
| '/old/ocp'
|
||||
| '/old/quality'
|
||||
| '/old/rfid'
|
||||
| '/admin/modules'
|
||||
| '/admin/settings'
|
||||
@@ -480,6 +515,8 @@ export interface FileRouteTypes {
|
||||
| '/_app/(logistics)/logistics/deliverySchedule'
|
||||
| '/_app/_adminLayout/admin/servers'
|
||||
| '/_app/_forklifts/forklifts/companies'
|
||||
| '/_app/_forklifts/forklifts/forklifts'
|
||||
| '/_app/_forklifts/forklifts/invoices'
|
||||
| '/_app/_forklifts/forklifts/leases'
|
||||
| '/_mobile/_mobileLayout/m/cyclecounts'
|
||||
| '/_mobile/_mobileLayout/m/delivery'
|
||||
@@ -488,6 +525,7 @@ export interface FileRouteTypes {
|
||||
| '/_app/_forklifts/forklifts/'
|
||||
| '/_mobile/_mobileLayout/m/'
|
||||
| '/_old/old/ocp/'
|
||||
| '/_old/old/quality/'
|
||||
| '/_old/old/rfid/'
|
||||
| '/_app/_adminLayout/admin/_system/modules'
|
||||
| '/_app/_adminLayout/admin/_system/settings'
|
||||
@@ -589,6 +627,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof OldOldRfidIndexRouteImport
|
||||
parentRoute: typeof OldOldRouteRoute
|
||||
}
|
||||
'/_old/old/quality/': {
|
||||
id: '/_old/old/quality/'
|
||||
path: '/quality'
|
||||
fullPath: '/old/quality'
|
||||
preLoaderRoute: typeof OldOldQualityIndexRouteImport
|
||||
parentRoute: typeof OldOldRouteRoute
|
||||
}
|
||||
'/_old/old/ocp/': {
|
||||
id: '/_old/old/ocp/'
|
||||
path: '/ocp'
|
||||
@@ -645,6 +690,20 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AppForkliftsForkliftsLeasesRouteImport
|
||||
parentRoute: typeof AppForkliftsRouteRoute
|
||||
}
|
||||
'/_app/_forklifts/forklifts/invoices': {
|
||||
id: '/_app/_forklifts/forklifts/invoices'
|
||||
path: '/forklifts/invoices'
|
||||
fullPath: '/forklifts/invoices'
|
||||
preLoaderRoute: typeof AppForkliftsForkliftsInvoicesRouteImport
|
||||
parentRoute: typeof AppForkliftsRouteRoute
|
||||
}
|
||||
'/_app/_forklifts/forklifts/forklifts': {
|
||||
id: '/_app/_forklifts/forklifts/forklifts'
|
||||
path: '/forklifts/forklifts'
|
||||
fullPath: '/forklifts/forklifts'
|
||||
preLoaderRoute: typeof AppForkliftsForkliftsForkliftsRouteImport
|
||||
parentRoute: typeof AppForkliftsRouteRoute
|
||||
}
|
||||
'/_app/_forklifts/forklifts/companies': {
|
||||
id: '/_app/_forklifts/forklifts/companies'
|
||||
path: '/forklifts/companies'
|
||||
@@ -860,12 +919,16 @@ const AppAdminLayoutRouteRouteWithChildren =
|
||||
|
||||
interface AppForkliftsRouteRouteChildren {
|
||||
AppForkliftsForkliftsCompaniesRoute: typeof AppForkliftsForkliftsCompaniesRoute
|
||||
AppForkliftsForkliftsForkliftsRoute: typeof AppForkliftsForkliftsForkliftsRoute
|
||||
AppForkliftsForkliftsInvoicesRoute: typeof AppForkliftsForkliftsInvoicesRoute
|
||||
AppForkliftsForkliftsLeasesRoute: typeof AppForkliftsForkliftsLeasesRoute
|
||||
AppForkliftsForkliftsIndexRoute: typeof AppForkliftsForkliftsIndexRoute
|
||||
}
|
||||
|
||||
const AppForkliftsRouteRouteChildren: AppForkliftsRouteRouteChildren = {
|
||||
AppForkliftsForkliftsCompaniesRoute: AppForkliftsForkliftsCompaniesRoute,
|
||||
AppForkliftsForkliftsForkliftsRoute: AppForkliftsForkliftsForkliftsRoute,
|
||||
AppForkliftsForkliftsInvoicesRoute: AppForkliftsForkliftsInvoicesRoute,
|
||||
AppForkliftsForkliftsLeasesRoute: AppForkliftsForkliftsLeasesRoute,
|
||||
AppForkliftsForkliftsIndexRoute: AppForkliftsForkliftsIndexRoute,
|
||||
}
|
||||
@@ -927,6 +990,7 @@ const MobileMobileLayoutRouteRouteWithChildren =
|
||||
interface OldOldRouteRouteChildren {
|
||||
OldOldIndexRoute: typeof OldOldIndexRoute
|
||||
OldOldOcpIndexRoute: typeof OldOldOcpIndexRoute
|
||||
OldOldQualityIndexRoute: typeof OldOldQualityIndexRoute
|
||||
OldOldRfidIndexRoute: typeof OldOldRfidIndexRoute
|
||||
OldOldlogisticsSiloAdjustmentsHistRoute: typeof OldOldlogisticsSiloAdjustmentsHistRoute
|
||||
OldOldlogisticsBarcodegenIndexRoute: typeof OldOldlogisticsBarcodegenIndexRoute
|
||||
@@ -942,6 +1006,7 @@ interface OldOldRouteRouteChildren {
|
||||
const OldOldRouteRouteChildren: OldOldRouteRouteChildren = {
|
||||
OldOldIndexRoute: OldOldIndexRoute,
|
||||
OldOldOcpIndexRoute: OldOldOcpIndexRoute,
|
||||
OldOldQualityIndexRoute: OldOldQualityIndexRoute,
|
||||
OldOldRfidIndexRoute: OldOldRfidIndexRoute,
|
||||
OldOldlogisticsSiloAdjustmentsHistRoute:
|
||||
OldOldlogisticsSiloAdjustmentsHistRoute,
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
import { createFileRoute, Link, Outlet } from "@tanstack/react-router";
|
||||
import {
|
||||
createFileRoute,
|
||||
Link,
|
||||
Outlet,
|
||||
redirect,
|
||||
} from "@tanstack/react-router";
|
||||
import { checkUserAccess } from "@/lib/authClient";
|
||||
|
||||
export const Route = createFileRoute("/_app/_adminLayout/admin/_system")({
|
||||
component: RouteComponent,
|
||||
beforeLoad: async () => {
|
||||
const auth = await checkUserAccess({
|
||||
allowedRoles: ["systemAdmin", "admin"],
|
||||
moduleName: "system", // optional
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
|
||||
@@ -1,11 +1,230 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import axios from "axios";
|
||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { getSettings } from "@/lib/querys/admin/getSettings";
|
||||
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
|
||||
|
||||
type Settings = {
|
||||
settings_id: string;
|
||||
name: string;
|
||||
active: boolean;
|
||||
value: string;
|
||||
description: string;
|
||||
moduleName: string;
|
||||
roles: string[];
|
||||
};
|
||||
|
||||
const updateSettings = async (
|
||||
id: string,
|
||||
data: Record<string, string | number | boolean | null>,
|
||||
) => {
|
||||
console.log(id, data);
|
||||
try {
|
||||
const res = await axios.patch(`/lst/api/system/settings/${id}`, data, {
|
||||
withCredentials: true,
|
||||
});
|
||||
toast.success(`Setting just updated`);
|
||||
return res;
|
||||
} catch (err) {
|
||||
toast.error("Error in updating the settings");
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_app/_adminLayout/admin/_system/settings',
|
||||
"/_app/_adminLayout/admin/_system/settings",
|
||||
)({
|
||||
component: RouteComponent,
|
||||
})
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/_app/_adminLayout/admin/_system/settings"!</div>
|
||||
const { data, isLoading, refetch } = useQuery(getSettings());
|
||||
const columnHelper = createColumnHelper<Settings>();
|
||||
const submitting = useRef(false);
|
||||
|
||||
const updateSetting = useMutation({
|
||||
mutationFn: ({
|
||||
id,
|
||||
field,
|
||||
value,
|
||||
}: {
|
||||
id: string;
|
||||
field: string;
|
||||
value: string | number | boolean | null;
|
||||
}) => updateSettings(id, { [field]: value }),
|
||||
|
||||
onSuccess: () => {
|
||||
// refetch or update cache
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("name", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Name</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("description", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Description</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("value", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Value</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row, getValue }) => {
|
||||
const initialValue = String(getValue() ?? "");
|
||||
const [localValue, setLocalValue] = useState(initialValue);
|
||||
|
||||
const id = row.original.settings_id;
|
||||
const field = "value";
|
||||
|
||||
useEffect(() => setLocalValue(initialValue), [initialValue]);
|
||||
|
||||
const handleSubmit = (newValue: string) => {
|
||||
if (newValue !== initialValue) {
|
||||
setLocalValue(newValue);
|
||||
updateSetting.mutate({ id, field, value: newValue });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Input
|
||||
value={localValue}
|
||||
onChange={(e) => setLocalValue(e.currentTarget.value)}
|
||||
onBlur={(e) => {
|
||||
if (!submitting.current) {
|
||||
submitting.current = true;
|
||||
handleSubmit(e.currentTarget.value.trim());
|
||||
setTimeout(() => (submitting.current = false), 100); // reset after slight delay
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
submitting.current = true;
|
||||
handleSubmit(e.currentTarget.value.trim());
|
||||
e.currentTarget.blur(); // will trigger blur, but we ignore it
|
||||
setTimeout(() => (submitting.current = false), 100);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("moduleName", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Module Name</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row, getValue }) => {
|
||||
const initialValue = String(getValue() ?? "");
|
||||
const [localValue, setLocalValue] = useState(initialValue);
|
||||
|
||||
const id = row.original.settings_id;
|
||||
const field = "moduleName";
|
||||
|
||||
useEffect(() => setLocalValue(initialValue), [initialValue]);
|
||||
|
||||
const handleSubmit = (newValue: string) => {
|
||||
if (newValue !== initialValue) {
|
||||
setLocalValue(newValue);
|
||||
updateSetting.mutate({ id, field, value: newValue });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Input
|
||||
value={localValue}
|
||||
onChange={(e) => setLocalValue(e.currentTarget.value)}
|
||||
onBlur={(e) => {
|
||||
if (!submitting.current) {
|
||||
submitting.current = true;
|
||||
handleSubmit(e.currentTarget.value.trim());
|
||||
setTimeout(() => (submitting.current = false), 100); // reset after slight delay
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
submitting.current = true;
|
||||
handleSubmit(e.currentTarget.value.trim());
|
||||
e.currentTarget.blur(); // will trigger blur, but we ignore it
|
||||
setTimeout(() => (submitting.current = false), 100);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<div>
|
||||
<span>Loading settings data</span>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="m-2">
|
||||
<TableNoExpand data={data} columns={columns} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
247
frontend/src/routes/_app/_forklifts/-components/NewForklift.tsx
Normal file
247
frontend/src/routes/_app/_forklifts/-components/NewForklift.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DialogClose,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { getServers } from "@/lib/querys/admin/getServers";
|
||||
import { getForklifts } from "@/lib/querys/forklifts/getForklifts";
|
||||
import { getLeases } from "@/lib/querys/forklifts/getLeases";
|
||||
import { useAppForm } from "../../../../lib/formStuff";
|
||||
|
||||
const forkliftManufacturer: any = [
|
||||
{ value: "Jungheinrich", label: "Jungheinrich" },
|
||||
{ value: "Toyota", label: "Toyota" },
|
||||
{ value: "Caterpillar", label: "Caterpillar" },
|
||||
{ value: "Mitsubishi", label: "Mitsubishi" },
|
||||
{ value: "BigJoe", label: "BigJoe" },
|
||||
{ value: "Yale", label: "Yale" },
|
||||
{ value: "Hyster", label: "Hyster" },
|
||||
{ value: "Global", label: "Global" },
|
||||
{ value: "Wesco", label: "Wesco" },
|
||||
{ value: "JLG", label: "JLG" },
|
||||
{ value: "Genie", label: "Genie" },
|
||||
{ value: "Linde", label: "Linde" },
|
||||
{ value: "Skyjack", label: "Skyjack" },
|
||||
{ value: "NilFisk", label: "NilFisk" },
|
||||
{ value: "Boomer", label: "Boomer" },
|
||||
{ value: "Crown", label: "Crown" },
|
||||
];
|
||||
|
||||
const batteryType: any = [
|
||||
{ value: "leadAcid", label: "Lead Acid" },
|
||||
{ value: "lithium", label: "Lithium" },
|
||||
{ value: "12v", label: "12v" },
|
||||
];
|
||||
|
||||
const engineType: any = [
|
||||
{ value: "electric", label: "Electric" },
|
||||
{ value: "propane", label: "Propane" },
|
||||
];
|
||||
|
||||
const pfc: any = [
|
||||
{ value: "20", label: "EBM" },
|
||||
{ value: "30", label: "SBM" },
|
||||
{ value: "40", label: "PET" },
|
||||
{ value: "50", label: "CAPS" },
|
||||
];
|
||||
export default function NewForklift({ setOpenDialog }: { setOpenDialog: any }) {
|
||||
//const search = useSearch({ from: "/_app/(auth)/login" });
|
||||
const { data: s, isLoading: es } = useQuery(getServers());
|
||||
const { refetch: refetchLifts } = useQuery(getForklifts());
|
||||
const {
|
||||
data: leases,
|
||||
isLoading: leaseError,
|
||||
refetch,
|
||||
} = useQuery(getLeases());
|
||||
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
serialNumber: "",
|
||||
model: "",
|
||||
plant: "",
|
||||
profitCenter: "",
|
||||
manufacturer: "",
|
||||
manufacturerYear: "",
|
||||
engine: "",
|
||||
batteryType: "lead acid",
|
||||
leaseId: "",
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
// get the glcode
|
||||
const glCode = s.filter((n: any) => n.name === value.plant);
|
||||
const data = {
|
||||
serialNumber: value.serialNumber,
|
||||
model: value.model.toUpperCase(),
|
||||
plant: value.plant,
|
||||
glCode: Number(glCode[0].greatPlainsPlantCode),
|
||||
profitCenter: Number(value.profitCenter),
|
||||
manufacturer: value.manufacturer,
|
||||
manufacturerYear: value.manufacturerYear,
|
||||
engine: value.engine,
|
||||
batteryType: value.batteryType,
|
||||
leaseId: value.leaseId.trimStart().trimEnd(),
|
||||
};
|
||||
console.log(data);
|
||||
try {
|
||||
await axios.post("/lst/api/forklifts", data);
|
||||
form.reset();
|
||||
setOpenDialog(false);
|
||||
refetch();
|
||||
refetchLifts();
|
||||
toast.success(`${value.serialNumber} was just created `);
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
if (!error.response.data.success) {
|
||||
// @ts-ignore
|
||||
toast.error(error?.response?.data.message);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
toast.error(error?.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
//const currentYear = new Date().getFullYear();
|
||||
|
||||
if (es) return <div>...Loading Servers</div>;
|
||||
if (leaseError) return <div>...Loading Leases</div>;
|
||||
|
||||
const serverMap = s.map((i: any) => {
|
||||
return { value: i.name, label: i.name };
|
||||
});
|
||||
|
||||
const leaseMap = leases.map((i: any) => {
|
||||
return { value: i.id, label: i.leaseNumber };
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Forklift</DialogTitle>
|
||||
<DialogDescription>Create a new forklift</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
form.handleSubmit();
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row gap-2 mt-2 mb-2">
|
||||
<form.AppField
|
||||
name="serialNumber"
|
||||
children={(field) => (
|
||||
<field.InputField
|
||||
label="Forklift SN"
|
||||
inputType="string"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="model"
|
||||
children={(field) => (
|
||||
<field.InputField
|
||||
label="Model"
|
||||
inputType="string"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 mt-2 mb-2">
|
||||
<form.AppField
|
||||
name="manufacturer"
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select Forklift Manufacturer"
|
||||
placeholder="Manufacturer"
|
||||
options={forkliftManufacturer}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="manufacturerYear"
|
||||
children={(field) => (
|
||||
<field.InputField
|
||||
label="Year Made"
|
||||
inputType="string"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 mt-2 mb-2">
|
||||
<form.AppField
|
||||
name="engine"
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select Engine Type"
|
||||
placeholder="Engine Type"
|
||||
options={engineType}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="batteryType"
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select battery type"
|
||||
placeholder="Battery type"
|
||||
options={batteryType}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<form.AppField
|
||||
name="profitCenter"
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select profit center"
|
||||
placeholder="Profit Center"
|
||||
options={pfc}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2 mt-2 mb-2">
|
||||
<form.AppField
|
||||
name="plant"
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select Plant"
|
||||
placeholder="Plant"
|
||||
options={serverMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="leaseId"
|
||||
children={(field) => (
|
||||
<field.Searchable
|
||||
label="Select Lease"
|
||||
searchFor="Lease"
|
||||
options={leaseMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">Submit</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
275
frontend/src/routes/_app/_forklifts/-components/NewInvoice.tsx
Normal file
275
frontend/src/routes/_app/_forklifts/-components/NewInvoice.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import { format } from "date-fns";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DialogClose,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useAppForm } from "@/lib/formStuff";
|
||||
import { getCompanies } from "@/lib/querys/forklifts/getCompanies";
|
||||
import { getInvoices } from "@/lib/querys/forklifts/getInvoices";
|
||||
|
||||
type CompanyData = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function NewInvoice({
|
||||
setOpenInvoiceDialog,
|
||||
}: {
|
||||
setOpenInvoiceDialog: any;
|
||||
}) {
|
||||
const { refetch } = useQuery(getInvoices());
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
companyName: "",
|
||||
leaseId: "",
|
||||
invoiceNumber: "",
|
||||
invoiceDate: "",
|
||||
totalAmount: "",
|
||||
comment: "",
|
||||
forklifts: [{ forklift_id: "", serialNumber: "", amount: "" }],
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
const updatedForklifts = value.forklifts.map(
|
||||
({ serialNumber, ...rest }) => rest,
|
||||
);
|
||||
const postData = {
|
||||
leaseId: value.leaseId,
|
||||
invoiceNumber: value.invoiceNumber,
|
||||
invoiceDate: format(value.invoiceDate, "MM/dd/yyyy"),
|
||||
totalAmount: value.totalAmount,
|
||||
forklifts: updatedForklifts,
|
||||
};
|
||||
console.log(postData);
|
||||
try {
|
||||
await axios.post("/lst/api/forklifts/invoices", postData);
|
||||
form.reset();
|
||||
setOpenInvoiceDialog(false);
|
||||
refetch();
|
||||
toast.success(`${value.invoiceNumber} was just created `);
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
console.log(error);
|
||||
// @ts-ignore
|
||||
if (!error.response.data.success) {
|
||||
// @ts-ignore
|
||||
toast.error(<span>{error?.response?.data.error}</span>);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
toast.error(error?.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const { data: c, isLoading: ce } = useQuery(getCompanies());
|
||||
|
||||
let companyName = form.getFieldValue("companyName");
|
||||
|
||||
const { data: l = [], refetch: lf } = useQuery({
|
||||
queryKey: ["lease", companyName],
|
||||
queryFn: async () => {
|
||||
//if (!companyName) return [];
|
||||
const { data } = await axios.get(
|
||||
`/lst/api/forklifts/leases?companyId=${companyName}`,
|
||||
);
|
||||
return data.data;
|
||||
},
|
||||
enabled: !!companyName, // only run if nameId has value
|
||||
});
|
||||
|
||||
if (ce) return <div>Loading Companies</div>;
|
||||
|
||||
// remap the companies to fit out select field
|
||||
const companyMap = c.map((i: CompanyData) => {
|
||||
return { value: i.id, label: i.name };
|
||||
});
|
||||
|
||||
const leaseMap = l.map((i: any) => {
|
||||
return { value: i.id, label: i.leaseNumber };
|
||||
});
|
||||
|
||||
const onValueChange = (value: string) => {
|
||||
companyName = value;
|
||||
lf();
|
||||
form.setFieldValue("leaseId", "");
|
||||
};
|
||||
|
||||
let forkliftArray = [];
|
||||
const onLeaseChange = (value: string) => {
|
||||
const selectedLease = l.find((lease: any) => lease.id === value);
|
||||
|
||||
forkliftArray =
|
||||
selectedLease?.forklifts.length > 0
|
||||
? selectedLease.forklifts.map((f: any) => ({
|
||||
forklift_id: f.forklift_id,
|
||||
amount: 0,
|
||||
serialNumber: f.serialNumber,
|
||||
}))
|
||||
: [
|
||||
// {
|
||||
// forklift_id: "missing",
|
||||
// amount: 0,
|
||||
// serialNumber: "Missing forklift",
|
||||
// },
|
||||
];
|
||||
form.setFieldValue("forklifts", forkliftArray);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Lease</DialogTitle>
|
||||
<DialogDescription>
|
||||
Select the company this lease will be for, lease number, start and end
|
||||
date
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
form.handleSubmit();
|
||||
}}
|
||||
>
|
||||
<form.AppField
|
||||
name="companyName"
|
||||
listeners={{
|
||||
onChange: ({ value }) => {
|
||||
onValueChange(value);
|
||||
},
|
||||
}}
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select Company"
|
||||
placeholder="Companies"
|
||||
options={companyMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="leaseId"
|
||||
listeners={{
|
||||
onChange: ({ value }) => {
|
||||
onLeaseChange(value);
|
||||
},
|
||||
}}
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select Lease"
|
||||
placeholder="LeaseNumber"
|
||||
options={leaseMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="w-5/6 m-2">
|
||||
<form.AppField
|
||||
name="invoiceNumber"
|
||||
children={(field) => (
|
||||
<field.InputField
|
||||
label="Enter Invoice Number"
|
||||
inputType="string"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-5/6 m-2">
|
||||
<form.AppField
|
||||
name="totalAmount"
|
||||
children={(field) => (
|
||||
<field.InputField
|
||||
label="Enter Invoice Amount"
|
||||
inputType="decimal"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<form.AppField
|
||||
name="invoiceDate"
|
||||
children={(field) => (
|
||||
<field.DateField label="Invoice Date" required={true} />
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="comment"
|
||||
children={(field) => (
|
||||
<field.TextArea
|
||||
label="Comment"
|
||||
placeHolder="Enter your comment if needed."
|
||||
required={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<hr className="mt-2 mb-2" />
|
||||
{/* Dynamic forklift section */}
|
||||
<div className="space-y-3 mt-4">
|
||||
<form.Field
|
||||
name="forklifts"
|
||||
mode="array"
|
||||
children={(field) => (
|
||||
<>
|
||||
<Label>Forklifts</Label>
|
||||
{field.state.value.map((fx, index) => (
|
||||
<form.AppField
|
||||
key={fx.forklift_id}
|
||||
name={`forklifts[${index}].amount`}
|
||||
children={(subField) => (
|
||||
<div className="flex flex-row gap-2">
|
||||
<Label htmlFor={subField.name}>{fx.serialNumber}</Label>
|
||||
<Input
|
||||
className="w-1/4"
|
||||
id={subField.name}
|
||||
value={subField.state.value ?? ""}
|
||||
onChange={(e) => {
|
||||
// update this subfield’s amount
|
||||
subField.handleChange(e.target.value);
|
||||
|
||||
// if you also want to store the forklift_id with it
|
||||
field.handleChange(
|
||||
field.state.value.map((val, i) =>
|
||||
i === index
|
||||
? { ...val, amount: e.target.value }
|
||||
: val,
|
||||
),
|
||||
);
|
||||
}}
|
||||
onBlur={subField.handleBlur}
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Map out the input filed based on the forklift id */}
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">Submit</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -108,11 +108,13 @@ export default function NewLeaseForm({
|
||||
<div className="flex flex-row gap-2 mt-2 mb-2">
|
||||
<form.AppField
|
||||
name="startDate"
|
||||
children={(field) => <field.DateField label="Start Date" />}
|
||||
children={(field) => (
|
||||
<field.DateField label="Start Date" required />
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="endDate"
|
||||
children={(field) => <field.DateField label="End Date" />}
|
||||
children={(field) => <field.DateField label="End Date" required />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
213
frontend/src/routes/_app/_forklifts/forklifts/forklifts.tsx
Normal file
213
frontend/src/routes/_app/_forklifts/forklifts/forklifts.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||
//import { useRef } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getForklifts } from "@/lib/querys/forklifts/getForklifts";
|
||||
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
|
||||
|
||||
type Forklifts = {
|
||||
forklift_id: string;
|
||||
forkliftNumber: number;
|
||||
serialNumber: string;
|
||||
model: string;
|
||||
plant: string;
|
||||
forkliftStatus: boolean;
|
||||
glCode: number;
|
||||
profitCenter: number;
|
||||
manufacturer: string;
|
||||
manufacturerYear: string;
|
||||
engine: string;
|
||||
batteryType: string;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/_app/_forklifts/forklifts/forklifts")({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: leaseData = [], isLoading } = useQuery(getForklifts());
|
||||
|
||||
const columnHelper = createColumnHelper<Forklifts>();
|
||||
//const submitting = useRef(false);
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("plant", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Plant</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("profitCenter", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">PFC</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("forkliftNumber", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Forklift Number</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("serialNumber", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">SN Number</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
return <span>{getValue()}</span>;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("model", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Model</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
return <span>{getValue()}</span>;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("manufacturer", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Manufacturer</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
return <span>{getValue()}</span>;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("manufacturerYear", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Year</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
return <span>{getValue()}</span>;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("engine", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Engine</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
return <span>{getValue()}</span>;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("batteryType", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Battery Type</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
return <span>{getValue()}</span>;
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="m-auto">Loading user data</div>;
|
||||
}
|
||||
return <TableNoExpand data={leaseData} columns={columns} />;
|
||||
}
|
||||
136
frontend/src/routes/_app/_forklifts/forklifts/invoices.tsx
Normal file
136
frontend/src/routes/_app/_forklifts/forklifts/invoices.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { format } from "date-fns";
|
||||
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getInvoices } from "@/lib/querys/forklifts/getInvoices";
|
||||
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
|
||||
|
||||
type Invoices = {
|
||||
id: string;
|
||||
invoiceNumber: string;
|
||||
invoiceDate: Date;
|
||||
comment: string;
|
||||
totalAmount: string;
|
||||
add_date: Date;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/_app/_forklifts/forklifts/invoices")({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: invoiceData = [], isLoading } = useQuery(getInvoices());
|
||||
|
||||
const columnHelper = createColumnHelper<Invoices>();
|
||||
//const submitting = useRef(false);
|
||||
console.log(invoiceData);
|
||||
const columns = [
|
||||
columnHelper.accessor("invoiceNumber", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Invoice Number</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("totalAmount", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Total Amount</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("invoiceDate", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Invoice Date</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
const raw = getValue() as string | Date;
|
||||
const date = typeof raw === "string" ? new Date(raw) : (raw as Date);
|
||||
if (isNaN(date.getTime())) return "Invalid date";
|
||||
return <span>{format(date, "MM/dd/yyyy")}</span>;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("comment", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Invoice Date</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ getValue }) => {
|
||||
return <span>{getValue()}</span>;
|
||||
},
|
||||
}),
|
||||
// columnHelper.accessor("add_date", {
|
||||
// header: ({ column }) => {
|
||||
// return (
|
||||
// <Button
|
||||
// variant="ghost"
|
||||
// onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
// >
|
||||
// <span className="flex flex-row gap-2">Start Date</span>
|
||||
// {column.getIsSorted() === "asc" ? (
|
||||
// <ArrowUp className="ml-2 h-4 w-4" />
|
||||
// ) : (
|
||||
// <ArrowDown className="ml-2 h-4 w-4" />
|
||||
// )}
|
||||
// </Button>
|
||||
// );
|
||||
// },
|
||||
// cell: ({ getValue }) => {
|
||||
// const raw = getValue() as string | Date;
|
||||
// const date = typeof raw === "string" ? new Date(raw) : (raw as Date);
|
||||
// if (isNaN(date.getTime())) return "Invalid date";
|
||||
// return <span>{format(date, "MM/dd/yyyy")}</span>;
|
||||
// },
|
||||
//}),
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="m-auto">Loading user data</div>;
|
||||
}
|
||||
return <TableNoExpand data={invoiceData} columns={columns} />;
|
||||
}
|
||||
@@ -7,7 +7,13 @@ export const Route = createFileRoute("/_old/old/(logistics)/siloAdjustments/")({
|
||||
component: RouteComponent,
|
||||
beforeLoad: async () => {
|
||||
const auth = await checkUserAccess({
|
||||
allowedRoles: ["systemAdmin", "technician", "admin", "manager"],
|
||||
allowedRoles: [
|
||||
"systemAdmin",
|
||||
"technician",
|
||||
"admin",
|
||||
"manager",
|
||||
"supervisor",
|
||||
],
|
||||
moduleName: "siloAdjustments", // optional
|
||||
});
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export function AddCards() {
|
||||
<Cards name={"ppoo"} inventory />
|
||||
<Cards name={"inv-empty"} rowType={"empty"} />
|
||||
<Cards name={"inv-fg"} rowType={"fg"} />
|
||||
<Cards name={"qualityCard"} />
|
||||
</div>
|
||||
<div className="">
|
||||
<Cards name={"inv-materials"} rowType={"materials"} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCardStore } from "../../-lib/store/useCardStore";
|
||||
import Quality from "../../quality/-components/WarehouseCard";
|
||||
import INVCheckCard from "../logistics/warehouse/InventoryCard";
|
||||
import OpenOrders from "../logistics/warehouse/openOrders";
|
||||
import PPOO from "../logistics/warehouse/PPOOCard";
|
||||
@@ -7,6 +8,7 @@ const componentsMap: any = {
|
||||
ppoo: PPOO,
|
||||
inv: INVCheckCard,
|
||||
openOrder: OpenOrders,
|
||||
qualityCard: Quality,
|
||||
//QualityRequest,
|
||||
};
|
||||
|
||||
@@ -26,14 +28,21 @@ export default function DashBoard() {
|
||||
<Component age={a.age} type={a.rowType} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
//console.log(name.split("-")[0], a);
|
||||
}
|
||||
|
||||
if (name === "qualityCard") {
|
||||
return (
|
||||
<div key={a.name} className="col-span-3">
|
||||
<Component data={a} />
|
||||
<div key={a.name} className="col-span-6">
|
||||
<Component />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
//console.log(name.split("-")[0], a);
|
||||
return (
|
||||
<div key={a.name} className="col-span-3">
|
||||
<Component data={a} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import { AdminSideBar } from "./side-components/admin";
|
||||
import { Header } from "./side-components/header";
|
||||
import { LogisticsSideBar } from "./side-components/logistics";
|
||||
import { ProductionSideBar } from "./side-components/production";
|
||||
import { QualitySideBar } from "./side-components/quality";
|
||||
|
||||
export function AppSidebar() {
|
||||
const { session } = useAuth();
|
||||
@@ -31,11 +32,12 @@ export function AppSidebar() {
|
||||
|
||||
{/* userAccess("logistics", ["systemAdmin", "admin","manager","viewer"]) */}
|
||||
<LogisticsSideBar user={session?.user as any} userRoles={userRoles} />
|
||||
<QualitySideBar user={session?.user as any} userRoles={userRoles} />
|
||||
{userAccess(null, ["systemAdmin"]) && (
|
||||
<>
|
||||
{/* <ForkliftSideBar />
|
||||
<EomSideBar />
|
||||
<QualitySideBar /> */}
|
||||
*/}
|
||||
<AdminSideBar />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Printer } from "lucide-react";
|
||||
import { Cat, Printer } from "lucide-react";
|
||||
import type { UserRoles } from "@/lib/authClient";
|
||||
import type { User } from "@/types";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
@@ -7,31 +9,49 @@ import {
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "../../../../../../components/ui/sidebar";
|
||||
import { useModuleStore } from "../../../-lib/store/useModuleStore";
|
||||
import { hasPageAccess } from "../../../-utils/userAccess";
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: "Qaulity Request",
|
||||
url: "#",
|
||||
icon: Printer,
|
||||
},
|
||||
];
|
||||
const iconMap: any = {
|
||||
Printer: Printer,
|
||||
Cat: Cat,
|
||||
};
|
||||
|
||||
export function QualitySideBar({
|
||||
user,
|
||||
userRoles,
|
||||
}: {
|
||||
user: User | null;
|
||||
userRoles: UserRoles[] | null;
|
||||
}) {
|
||||
const { modules } = useModuleStore();
|
||||
|
||||
const items = modules?.filter((m) => m.category === "quality" && m.active);
|
||||
const userUpdate = { ...user, roles: userRoles };
|
||||
|
||||
export function QualitySideBar() {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Quality</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
{items.map((item) => {
|
||||
if (!item.active) return;
|
||||
const Icon = iconMap[item.icon === "" ? "Cat" : item.icon];
|
||||
return (
|
||||
<SidebarMenuItem key={item.module_id}>
|
||||
<>
|
||||
{hasPageAccess(userUpdate as any, item.roles, item.name) && (
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.link}>
|
||||
<Icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
)}
|
||||
</>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
|
||||
@@ -6,6 +6,7 @@ export default function DMButtons() {
|
||||
const { settings } = useSettingStore();
|
||||
const testServers = ["test1", "test2", "test3"];
|
||||
const plantToken = settings.filter((n) => n.name === "plantToken");
|
||||
|
||||
//console.log(plantToken);
|
||||
return (
|
||||
<div className="flex flex-row-reverse gap-1">
|
||||
@@ -15,6 +16,7 @@ export default function DMButtons() {
|
||||
<div className="flex flex-row gap-2">
|
||||
<OrderImport fileType={"abbott"} name={"Abbott truck list"} />
|
||||
<OrderImport fileType={"energizer"} name={"Energizer Truck List"} />
|
||||
<OrderImport fileType={"scj"} name={"SCJ Orders"} />
|
||||
<ForecastImport fileType={"loreal"} name={"VMI Import"} />
|
||||
<ForecastImport fileType={"pg"} name={"P&G"} />
|
||||
<ForecastImport fileType={"energizer"} name={"Energizer Forecast"} />
|
||||
@@ -50,6 +52,11 @@ export default function DMButtons() {
|
||||
<ForecastImport fileType={"pg"} name={"P&G"} />
|
||||
</div>
|
||||
)}
|
||||
{plantToken[0]?.value === "usweb1" && (
|
||||
<div className="flex flex-row gap-2">
|
||||
<OrderImport fileType={"scj"} name={"SCJ Orders"} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user