diff --git a/package-lock.json b/package-lock.json index b388afa..7113f5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,10 @@ "eslint-plugin-react-hooks": "^4.6.2", "prettier": "^3.3.3", "prettier-eslint": "^16.3.0", - "standard": "^17.1.0" + "standard": "^17.1.0", + "svgo-loader": "^4.0.0", + "webpack": "^5.99.9", + "webpack-cli": "^6.0.1" } }, "node_modules/@alloc/quick-lru": { @@ -2611,6 +2614,16 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -5136,9 +5149,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "license": "MIT" }, "node_modules/@types/express": { @@ -5653,148 +5666,148 @@ "license": "ISC" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -5804,6 +5817,53 @@ "integrity": "sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q==", "license": "glslang/LICENSE.txt" }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -5837,9 +5897,9 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5870,15 +5930,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6964,9 +7015,9 @@ "license": "BSD-2-Clause" }, "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "funding": [ { "type": "opencollective", @@ -6983,10 +7034,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -7146,9 +7197,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001643", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", - "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", "funding": [ { "type": "opencollective", @@ -7321,6 +7372,21 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -8518,9 +8584,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz", - "integrity": "sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==", + "version": "1.5.161", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", + "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", "license": "ISC" }, "node_modules/emittery": { @@ -8624,6 +8690,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -8833,9 +8912,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -10005,6 +10084,16 @@ "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", "license": "MIT" }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -10211,6 +10300,16 @@ "node": ">=8" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -11432,6 +11531,16 @@ "node": ">= 0.4" } }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -11756,6 +11865,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -11950,6 +12072,16 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -15132,9 +15264,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, "node_modules/normalize-path": { @@ -15652,9 +15784,9 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { @@ -18734,6 +18866,19 @@ "node": ">=8.10.0" } }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/recursive-readdir": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", @@ -19312,9 +19457,9 @@ } }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -19323,7 +19468,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -19589,6 +19734,19 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -20569,6 +20727,185 @@ "node": ">=4.0.0" } }, + "node_modules/svgo-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo-loader/-/svgo-loader-4.0.0.tgz", + "integrity": "sha512-bdk2H73AHP8Vo9zgMuA8piEzi5pjFzllK4EwfebDF3hDjmHQpmmqXMoDd6abDtVFrlKTJuveepmnc2kuTdt/WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "svgo": "^3.0.0" + } + }, + "node_modules/svgo-loader/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/svgo-loader/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo-loader/node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/svgo-loader/node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/svgo-loader/node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/svgo-loader/node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/svgo-loader/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/svgo-loader/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/svgo-loader/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/svgo-loader/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/svgo-loader/node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/svgo-loader/node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, "node_modules/svgo/node_modules/css-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", @@ -20771,16 +21108,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -20804,24 +21141,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -21378,9 +21697,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -21397,8 +21716,8 @@ ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -21624,21 +21943,21 @@ } }, "node_modules/webpack": { - "version": "5.93.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", - "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "version": "5.99.9", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", + "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -21648,9 +21967,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", + "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, @@ -21670,6 +21989,59 @@ } } }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/webpack-dev-middleware": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", @@ -21811,6 +22183,21 @@ "node": ">=10.13.0" } }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", @@ -21842,24 +22229,6 @@ "node": ">=4.0" } }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -22024,6 +22393,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 92f17a4..209a992 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,9 @@ "eslint-plugin-react-hooks": "^4.6.2", "prettier": "^3.3.3", "prettier-eslint": "^16.3.0", - "standard": "^17.1.0" + "standard": "^17.1.0", + "svgo-loader": "^4.0.0", + "webpack": "^5.99.9", + "webpack-cli": "^6.0.1" } } diff --git a/src/assets/icons/gcodefileicon.afdesign b/src/assets/icons/gcodefileicon.afdesign index 6b9a90f..4a7a762 100644 Binary files a/src/assets/icons/gcodefileicon.afdesign and b/src/assets/icons/gcodefileicon.afdesign differ diff --git a/src/assets/icons/gcodefileicon.svg b/src/assets/icons/gcodefileicon.svg index 32f5792..e6ca6ab 100644 --- a/src/assets/icons/gcodefileicon.svg +++ b/src/assets/icons/gcodefileicon.svg @@ -1,10 +1,10 @@ - - - - - - - + + + + + + + diff --git a/src/assets/icons/jobicon.afdesign b/src/assets/icons/jobicon.afdesign new file mode 100644 index 0000000..5627604 Binary files /dev/null and b/src/assets/icons/jobicon.afdesign differ diff --git a/src/assets/icons/jobicon.svg b/src/assets/icons/jobicon.svg new file mode 100644 index 0000000..cc1ba74 --- /dev/null +++ b/src/assets/icons/jobicon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icons/plusminusicon.afdesign b/src/assets/icons/plusminusicon.afdesign new file mode 100644 index 0000000..7a80279 Binary files /dev/null and b/src/assets/icons/plusminusicon.afdesign differ diff --git a/src/assets/icons/plusminusicon.svg b/src/assets/icons/plusminusicon.svg new file mode 100644 index 0000000..1b90591 --- /dev/null +++ b/src/assets/icons/plusminusicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/Dashboard/Inventory/FilamentStocks.jsx b/src/components/Dashboard/Inventory/FilamentStocks.jsx index 9d0de56..d63729a 100644 --- a/src/components/Dashboard/Inventory/FilamentStocks.jsx +++ b/src/components/Dashboard/Inventory/FilamentStocks.jsx @@ -93,14 +93,14 @@ const FilamentStocks = () => { // Add WebSocket event listener for real-time updates if (socket && !initialized) { setInitialized(true) - socket.on('notify_filamentstock_update', (statusUpdate) => { - console.log('Received filament stock update:', statusUpdate) + socket.on('notify_filamentstock_update', (updateData) => { + console.log('Received filament stock update:', updateData) setFilamentStocksData((prevData) => { return prevData.map((stock) => { - if (stock._id === statusUpdate.id) { + if (stock._id === updateData._id) { return { ...stock, - ...statusUpdate + ...updateData } } return stock @@ -163,7 +163,12 @@ const FilamentStocks = () => { ) }, - + { + title: 'State', + key: 'state', + width: 350, + render: (record) => + }, { title: 'Current (g)', dataIndex: 'currentNetWeight', @@ -182,13 +187,6 @@ const FilamentStocks = () => { {startingNetWeight.toFixed(2) + 'g'} ) }, - { - title: 'State', - key: 'state', - width: 350, - render: (record) => - }, - { title: 'Created At', dataIndex: 'createdAt', @@ -203,6 +201,20 @@ const FilamentStocks = () => { } } }, + { + title: 'Updated At', + dataIndex: 'updatedAt', + key: 'updatedAt', + width: 180, + render: (updatedAt) => { + if (updatedAt) { + const formattedDate = moment(updatedAt).format('YYYY-MM-DD HH:mm:ss') + return {formattedDate} + } else { + return 'n/a' + } + } + }, { title: 'Actions', key: 'actions', diff --git a/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx b/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx index d94ca72..650b0ca 100644 --- a/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx +++ b/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx @@ -8,18 +8,23 @@ import { Button, message, Typography, - Flex, Form, - Badge + Badge, + Collapse } from 'antd' -import { LoadingOutlined, ReloadOutlined } from '@ant-design/icons' +import { + LoadingOutlined, + ReloadOutlined, + CaretRightOutlined +} from '@ant-design/icons' import IdText from '../../common/IdText' import moment from 'moment' import { SocketContext } from '../../context/SocketContext' import FilamentStockState from '../../common/FilamentStockState' import StockEventTable from '../../common/StockEventTable' +import useCollapseState from '../../hooks/useCollapseState' -const { Title, Text } = Typography +const { Text, Title } = Typography const FilamentStockInfo = () => { const [filamentStockData, setFilamentStockData] = useState(null) @@ -33,6 +38,13 @@ const FilamentStockInfo = () => { ) const [form] = Form.useForm() const { socket } = useContext(SocketContext) + const [collapseState, updateCollapseState] = useCollapseState( + 'FilamentStockInfo', + { + info: true, + events: true + } + ) useEffect(() => { if (filamentStockId) { @@ -64,6 +76,11 @@ const FilamentStockInfo = () => { return prevData }) }) + + // Add WebSocket event listener for filament stock updates + socket.on('notify_filamentstock_update', (filamentStockUpdate) => { + console.log('GOT FILAMENT STOCK UPDATE', filamentStockUpdate) + }) } return () => { if (socket && initialized) { @@ -120,111 +137,142 @@ const FilamentStockInfo = () => { return (
{contextHolder} - updateCollapseState('info', keys.length > 0)} + expandIcon={({ isActive }) => ( + + )} > - - Filament Stock Information - - + + Filament Stock Information + + } + key='1' + > +
+ + {/* Read-only fields */} + + {filamentStockData.id ? ( + + ) : ( + 'n/a' + )} + + + {moment(filamentStockData.createdAt).format( + 'YYYY-MM-DD HH:mm:ss' + )} + - + + + + + {moment(filamentStockData.updatedAt).format( + 'YYYY-MM-DD HH:mm:ss' + )} + + + + {filamentStockData.filament ? ( + + ) : ( + 'n/a' + )} + + + + {filamentStockData.filament ? ( + + ) : ( + 'n/a' + )} + + + {filamentStockData.currentGrossWeight ? ( + + {filamentStockData.currentGrossWeight.toFixed(2) + 'g'} + + ) : ( + 'n/a' + )} + + + {filamentStockData.startingGrossWeight ? ( + + {filamentStockData.startingGrossWeight.toFixed(2) + 'g'} + + ) : ( + 'n/a' + )} + + + {filamentStockData.currentNetWeight ? ( + + {filamentStockData.currentNetWeight.toFixed(2) + 'g'} + + ) : ( + 'n/a' + )} + + + {filamentStockData.startingNetWeight ? ( + + {filamentStockData.startingNetWeight.toFixed(2) + 'g'} + + ) : ( + 'n/a' + )} + + +
+
+ + + updateCollapseState('events', keys.length > 0)} + expandIcon={({ isActive }) => ( + + )} > - - {/* Read-only fields */} - - {filamentStockData.id ? ( - - ) : ( - 'n/a' - )} - - - {moment(filamentStockData.createdAt).format('YYYY-MM-DD HH:mm:ss')} - - - - - - - - {moment(filamentStockData.updatedAt).format('YYYY-MM-DD HH:mm:ss')} - - - - {filamentStockData.filament ? ( - - ) : ( - 'n/a' - )} - - - - {filamentStockData.filament ? ( - - ) : ( - 'n/a' - )} - - - {filamentStockData.currentGrossWeight ? ( - - {filamentStockData.currentGrossWeight.toFixed(2) + 'g'} - - ) : ( - 'n/a' - )} - - - {filamentStockData.startingGrossWeight ? ( - - {filamentStockData.startingGrossWeight.toFixed(2) + 'g'} - - ) : ( - 'n/a' - )} - - - {filamentStockData.currentNetWeight ? ( - {filamentStockData.currentNetWeight.toFixed(2) + 'g'} - ) : ( - 'n/a' - )} - - - {filamentStockData.startingNetWeight ? ( - - {filamentStockData.startingNetWeight.toFixed(2) + 'g'} - - ) : ( - 'n/a' - )} - - - - - - Filament Stock Events - - - + + Filament Stock Events + + } + key='2' + > + + +
) } diff --git a/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx b/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx index 06911eb..db06633 100644 --- a/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx +++ b/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx @@ -91,7 +91,8 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => { setNextEnabled( Boolean(unloadFilamentStockFormValues.printer) && !unloadFilamentStockLoading && - currentTemperature > targetTemperature + currentTemperature + 1 > targetTemperature && + targetTemperature != 0 ) }) .catch(() => setNextEnabled(false)) @@ -144,7 +145,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => { <> {targetTemperature == 0 ? ( @@ -161,7 +162,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => { ) : null} {targetTemperature > 0 && - currentTemperature >= targetTemperature && + currentTemperature + 1 > targetTemperature && filamentSensorDetected ? ( { > Previous - {currentStep < steps.length - 1 && ( - - )} {currentStep === steps.length - 1 && ( - - + + + + + + + + + + {lazyLoading && } />} + }} - scroll={{ y: 'calc(100vh - 270px)' }} + onScroll={handleScroll} + onChange={handleTableChange} + showSorterTooltip={false} /> { const [isEditing, setIsEditing] = useState(false) const [form] = Form.useForm() const navigate = useNavigate() + const [collapseState, updateCollapseState] = useCollapseState( + 'FilamentInfo', + { + info: true, + details: true + } + ) useEffect(() => { if (filamentId) { @@ -198,297 +207,286 @@ const FilamentInfo = () => { return (
{contextHolder} + updateCollapseState('info', keys.length > 0)} + expandIcon={({ isActive }) => ( + + )} + > + + + Filament Information + + + - - - ) : ( -
) } diff --git a/src/components/Dashboard/Management/Parts.jsx b/src/components/Dashboard/Management/Parts.jsx index 7aff893..051f473 100644 --- a/src/components/Dashboard/Management/Parts.jsx +++ b/src/components/Dashboard/Management/Parts.jsx @@ -31,10 +31,10 @@ import { } from '@ant-design/icons' import { AuthContext } from '../../Auth/AuthContext' - import IdText from '../common/IdText' import NewProduct from './Products/NewProduct' import PartIcon from '../../Icons/PartIcon' +import useColumnVisibility from '../hooks/useColumnVisibility' const { Text } = Typography @@ -201,13 +201,9 @@ const Parts = () => { const [filters, setFilters] = useState({}) const [sorter, setSorter] = useState({}) - const [columnVisibility, setColumnVisibility] = useState( - columns.reduce((acc, col) => { - if (col.key) { - acc[col.key] = true - } - return acc - }, {}) + const [columnVisibility, updateColumnVisibility] = useColumnVisibility( + 'Parts', + columns ) const { authenticated } = useContext(AuthContext) @@ -391,10 +387,7 @@ const Parts = () => { checked={columnVisibility[col.key]} key={col.key} onChange={(e) => { - setColumnVisibility((prev) => ({ - ...prev, - [col.key]: e.target.checked - })) + updateColumnVisibility(col.key, e.target.checked) }} > {col.title} diff --git a/src/components/Dashboard/Management/Parts/PartInfo.jsx b/src/components/Dashboard/Management/Parts/PartInfo.jsx index 7221258..d92d521 100644 --- a/src/components/Dashboard/Management/Parts/PartInfo.jsx +++ b/src/components/Dashboard/Management/Parts/PartInfo.jsx @@ -15,20 +15,23 @@ import { Checkbox, InputNumber, Switch, - Tag + Tag, + Collapse } from 'antd' import { LoadingOutlined, EditOutlined, ReloadOutlined, CheckOutlined, - CloseOutlined + CloseOutlined, + CaretRightOutlined } from '@ant-design/icons' import IdText from '../../common/IdText.jsx' import moment from 'moment' +import { StlViewer } from 'react-stl-viewer' +import useCollapseState from '../../hooks/useCollapseState' const { Title } = Typography -import { StlViewer } from 'react-stl-viewer' const PartInfo = () => { const [partData, setPartData] = useState(null) @@ -39,6 +42,10 @@ const PartInfo = () => { const partId = new URLSearchParams(location.search).get('partId') const [marginOrPrice, setMarginOrPrice] = useState(false) const [useGlobalPricing, setUseGlobalPricing] = useState(true) + const [collapseState, updateCollapseState] = useCollapseState('PartInfo', { + info: true, + preview: true + }) const [partForm] = Form.useForm() const [partFormValues, setPartFormValues] = useState({}) @@ -231,222 +238,270 @@ const PartInfo = () => { return (
{contextHolder} - updateCollapseState('info', keys.length > 0)} + expandIcon={({ isActive }) => ( + + )} > - - Part Information - - - {isEditing ? ( - <> - - - - ) : ( - - )} - - - -
- setPartFormValues((prevValues) => ({ - ...prevValues, - ...changedValues - })) - } - initialValues={{ - name: partData.name || '' - }} - > - - - {partData.id ? ( - - ) : ( - 'n/a' - )} - - - {moment(partData.createdAt).format('YYYY-MM-DD HH:mm:ss')} - - - - {isEditing ? ( - - - - ) : ( - partData.name || 'n/a' - )} - - - - {moment(partData.updatedAt).format('YYYY-MM-DD HH:mm:ss')} - - - - {partData.product.name || 'n/a'} - - - {( - - ) || 'n/a'} - - - {isEditing && useGlobalPricing == false ? ( - - {marginOrPrice == false ? ( - - + + Part Information + + + {isEditing ? ( + <> +
) } diff --git a/src/components/Dashboard/Management/Products.jsx b/src/components/Dashboard/Management/Products.jsx index 49cea1c..0d3cb1b 100644 --- a/src/components/Dashboard/Management/Products.jsx +++ b/src/components/Dashboard/Management/Products.jsx @@ -14,7 +14,10 @@ import { Dropdown, message, Spin, - Tag + Tag, + Checkbox, + Popover, + Input } from 'antd' import { createStyles } from 'antd-style' import { @@ -22,7 +25,9 @@ import { PlusOutlined, DownloadOutlined, ReloadOutlined, - InfoCircleOutlined + InfoCircleOutlined, + CheckOutlined, + CloseOutlined } from '@ant-design/icons' import { AuthContext } from '../../Auth/AuthContext' @@ -30,6 +35,7 @@ import { AuthContext } from '../../Auth/AuthContext' import IdText from '../common/IdText' import NewProduct from './Products/NewProduct' import ProductIcon from '../../Icons/ProductIcon' +import useColumnVisibility from '../hooks/useColumnVisibility' const useStyle = createStyles(({ css, token }) => { const { antCls } = token @@ -58,6 +64,8 @@ const Products = () => { const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(true) const [lazyLoading, setLazyLoading] = useState(false) + const [filters, setFilters] = useState({}) + const [sorter, setSorter] = useState({}) const [newProductOpen, setNewProductOpen] = useState(false) @@ -71,16 +79,19 @@ const Products = () => { const response = await axios.get('http://localhost:8080/products', { params: { page: pageNum, - limit: 25 + limit: 25, + ...filters, + sort: sorter.field, + order: sorter.order }, headers: { Accept: 'application/json' }, - withCredentials: true // Important for including cookies + withCredentials: true }) const newData = response.data - setHasMore(newData.length === 25) // If we get less than 25 items, we've reached the end + setHasMore(newData.length === 25) if (append) { setProductsData((prev) => [...prev, ...newData]) @@ -105,7 +116,7 @@ const Products = () => { setLazyLoading(false) } }, - [messageApi] + [messageApi, filters, sorter] ) useEffect(() => { @@ -121,7 +132,6 @@ const Products = () => { const scrollTop = target.scrollTop const clientHeight = target.clientHeight - // If we're near the bottom (within 100px) and not currently loading if ( scrollHeight - scrollTop - clientHeight < 100 && !lazyLoading && @@ -173,7 +183,23 @@ const Products = () => { dataIndex: 'name', key: 'name', width: 200, - fixed: 'left' + fixed: 'left', + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'name' + }), + onFilter: (value, record) => + record.name.toLowerCase().includes(value.toLowerCase()), + sorter: true }, { title: 'ID', @@ -181,7 +207,23 @@ const Products = () => { key: 'id', fixed: 'left', width: 165, - render: (text) => + render: (text) => , + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'ID' + }), + onFilter: (value, record) => + record._id.toLowerCase().includes(value.toLowerCase()), + sorter: true }, { title: 'Tags', @@ -200,14 +242,48 @@ const Products = () => { ))} ) - } + }, + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'tags' + }), + onFilter: (value, record) => + record.tags?.some((tag) => + tag.toLowerCase().includes(value.toLowerCase()) + ), + sorter: true }, { title: 'Version', dataIndex: 'version', key: 'version', width: 120, - render: (text) => (text ? {text} : 'n/a') + render: (text) => (text ? {text} : 'n/a'), + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'version' + }), + onFilter: (value, record) => + record.version?.toLowerCase().includes(value.toLowerCase()), + sorter: true }, { title: 'Created At', @@ -221,7 +297,9 @@ const Products = () => { } else { return 'n/a' } - } + }, + sorter: true, + defaultSortOrder: 'descend' }, { title: 'Updated At', @@ -235,7 +313,9 @@ const Products = () => { } else { return 'n/a' } - } + }, + sorter: true, + defaultSortOrder: 'descend' }, { title: 'Actions', @@ -262,6 +342,11 @@ const Products = () => { } ] + const [columnVisibility, updateColumnVisibility] = useColumnVisibility( + 'Products', + columns + ) + const actionItems = { items: [ { @@ -285,29 +370,115 @@ const Products = () => { } } + const getFilterDropdown = ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName + }) => { + return ( +
+ + + setSelectedKeys(e.target.value ? [e.target.value] : []) + } + onPressEnter={() => confirm()} + style={{ width: 200, display: 'block' }} + /> +
+ ) + } + + const getViewDropdownItems = () => { + const columnItems = columns + .filter((col) => col.key && col.title !== '') + .map((col) => ( + { + updateColumnVisibility(col.key, e.target.checked) + }} + > + {col.title} + + )) + + return ( + + + {columnItems} + + + ) + } + + const handleTableChange = (pagination, filters, sorter) => { + const newFilters = {} + Object.entries(filters).forEach(([key, value]) => { + if (value && value.length > 0) { + newFilters[key] = value[0] + } + }) + setPage(1) + setFilters(newFilters) + setSorter({ + field: sorter.field, + order: sorter.order + }) + } + + const visibleColumns = columns.filter( + (col) => !col.key || columnVisibility[col.key] + ) + return ( <> {contextHolder} - + + + + - {lazyLoading == true ? ( - }> - ) : null} + {lazyLoading && } />}
}} onScroll={handleScroll} + onChange={handleTableChange} + showSorterTooltip={false} /> { const [isEditing, setIsEditing] = useState(false) const [fetchLoading, setFetchLoading] = useState(true) const [marginOrPrice, setMarginOrPrice] = useState(false) + const [collapseState, updateCollapseState] = useCollapseState('ProductInfo', { + info: true, + parts: true + }) const [productForm] = Form.useForm() const [productFormValues, setProductFormValues] = useState({}) @@ -183,233 +190,268 @@ const ProductInfo = () => { } return ( - +
{contextHolder} - updateCollapseState('info', keys.length > 0)} + expandIcon={({ isActive }) => ( + + )} > - - Product Information - - - {isEditing ? ( - <> - - - - ) : ( - - )} - - - -
- setProductFormValues((prevValues) => ({ - ...prevValues, - ...changedValues - })) - } - initialValues={{ - name: productData.name || '', - vendor: productData.vendor || { id: null, name: '' }, - version: productData.version || '', - tags: productData.tags || [] - }} - > - - - {productData.id ? ( - - ) : ( - 'n/a' - )} - - - - {moment(productData.createdAt).format('YYYY-MM-DD HH:mm:ss')} - - - - {isEditing ? ( - - - - ) : ( - productData.name || 'n/a' - )} - - - - {moment(productData.updatedAt).format('YYYY-MM-DD HH:mm:ss')} - - - - {isEditing ? ( - - - - ) : ( - productData.vendor.name || 'n/a' - )} - - - - - - - + + Product Information + + + {isEditing ? ( + <> +
) } diff --git a/src/components/Dashboard/Management/Vendors.jsx b/src/components/Dashboard/Management/Vendors.jsx index afe1c26..93975e0 100644 --- a/src/components/Dashboard/Management/Vendors.jsx +++ b/src/components/Dashboard/Management/Vendors.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useContext, useCallback } from 'react' +import React, { useState, useContext, useCallback, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import axios from 'axios' import moment from 'moment' @@ -10,7 +10,11 @@ import { Modal, Dropdown, message, - Typography + Typography, + Checkbox, + Popover, + Input, + Spin } from 'antd' import { createStyles } from 'antd-style' import { @@ -18,13 +22,16 @@ import { PlusOutlined, ReloadOutlined, InfoCircleOutlined, - ExportOutlined + ExportOutlined, + CheckOutlined, + CloseOutlined } from '@ant-design/icons' import { AuthContext } from '../../Auth/AuthContext' import IdText from '../common/IdText' import NewVendor from './Vendors/NewVendor' import CountryDisplay from '../common/CountryDisplay' import VendorIcon from '../../Icons/VendorIcon' +import useColumnVisibility from '../hooks/useColumnVisibility' const { Link } = Typography @@ -53,38 +60,152 @@ const Vendors = () => { const [vendorsData, setVendorsData] = useState([]) const [newVendorOpen, setNewVendorOpen] = useState(false) const [loading, setLoading] = useState(true) + const [page, setPage] = useState(1) + const [hasMore, setHasMore] = useState(true) + const [lazyLoading, setLazyLoading] = useState(false) + const [filters, setFilters] = useState({}) + const [sorter, setSorter] = useState({}) + const { authenticated } = useContext(AuthContext) - const fetchVendorsData = useCallback(async () => { - try { - const response = await axios.get('http://localhost:8080/vendors', { - params: { - page: 1, - limit: 25 - }, - headers: { - Accept: 'application/json' - }, - withCredentials: true - }) - setVendorsData(response.data) - setLoading(false) - } catch (error) { - if (error.response) { - messageApi.error('Error fetching vendor data:', error.response.status) - } else { - messageApi.error( - 'An unexpected error occurred. Please try again later.' - ) - } - } - }, [messageApi]) + const fetchVendorsData = useCallback( + async (pageNum = 1, append = false) => { + try { + const response = await axios.get('http://localhost:8080/vendors', { + params: { + page: pageNum, + limit: 25, + ...filters, + sort: sorter.field, + order: sorter.order + }, + headers: { + Accept: 'application/json' + }, + withCredentials: true + }) - useEffect(() => { - if (authenticated) { - fetchVendorsData() - } - }, [authenticated, fetchVendorsData]) + const newData = response.data + setHasMore(newData.length === 25) + + if (append) { + setVendorsData((prev) => [...prev, ...newData]) + } else { + setVendorsData(newData) + } + + setLoading(false) + setLazyLoading(false) + } catch (error) { + if (error.response) { + messageApi.error('Error fetching vendor data:', error.response.status) + } else { + messageApi.error( + 'An unexpected error occurred. Please try again later.' + ) + } + setLoading(false) + setLazyLoading(false) + } + }, + [messageApi, filters, sorter] + ) + + const handleScroll = useCallback( + (e) => { + const { target } = e + const scrollHeight = target.scrollHeight + const scrollTop = target.scrollTop + const clientHeight = target.clientHeight + + if ( + scrollHeight - scrollTop - clientHeight < 100 && + !lazyLoading && + hasMore + ) { + setLazyLoading(true) + const nextPage = page + 1 + setPage(nextPage) + fetchVendorsData(nextPage, true) + } + }, + [page, lazyLoading, hasMore, fetchVendorsData] + ) + + const getFilterDropdown = ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName + }) => { + return ( +
+ + + setSelectedKeys(e.target.value ? [e.target.value] : []) + } + onPressEnter={() => confirm()} + style={{ width: 200, display: 'block' }} + /> +
+ ) + } + + const getViewDropdownItems = () => { + const columnItems = columns + .filter((col) => col.key && col.title !== '') + .map((col) => ( + { + updateColumnVisibility(col.key, e.target.checked) + }} + > + {col.title} + + )) + + return ( + + + {columnItems} + + + ) + } + + const handleTableChange = (pagination, filters, sorter) => { + const newFilters = {} + Object.entries(filters).forEach(([key, value]) => { + if (value && value.length > 0) { + newFilters[key] = value[0] + } + }) + setPage(1) + setFilters(newFilters) + setSorter({ + field: sorter.field, + order: sorter.order + }) + } const getVendorActionItems = (id) => { return { @@ -117,14 +238,46 @@ const Vendors = () => { dataIndex: 'name', key: 'name', width: 200, - fixed: 'left' + fixed: 'left', + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'name' + }), + onFilter: (value, record) => + record.name.toLowerCase().includes(value.toLowerCase()), + sorter: true }, { title: 'ID', dataIndex: '_id', key: 'id', width: 165, - render: (text) => + render: (text) => , + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'ID' + }), + onFilter: (value, record) => + record._id.toLowerCase().includes(value.toLowerCase()), + sorter: true }, { title: 'Website', @@ -139,21 +292,69 @@ const Vendors = () => { ) : ( 'n/a' - ) + ), + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'website' + }), + onFilter: (value, record) => + record.website?.toLowerCase().includes(value.toLowerCase()), + sorter: true }, { title: 'Country', dataIndex: 'country', key: 'country', width: 200, - render: (text) => (text ? : 'n/a') + render: (text) => (text ? : 'n/a'), + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'country' + }), + onFilter: (value, record) => + record.country?.toLowerCase().includes(value.toLowerCase()), + sorter: true }, { title: 'Contact', dataIndex: 'contact', key: 'contact', width: 200, - render: (text) => (text ? text : 'n/a') + render: (text) => (text ? text : 'n/a'), + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }) => + getFilterDropdown({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + propertyName: 'contact' + }), + onFilter: (value, record) => + record.contact?.toLowerCase().includes(value.toLowerCase()), + sorter: true }, { title: 'Created At', @@ -167,7 +368,9 @@ const Vendors = () => { } else { return 'n/a' } - } + }, + sorter: true, + defaultSortOrder: 'descend' }, { title: 'Updated At', @@ -181,7 +384,9 @@ const Vendors = () => { } else { return 'n/a' } - } + }, + sorter: true, + defaultSortOrder: 'descend' }, { title: 'Actions', @@ -208,6 +413,15 @@ const Vendors = () => { } ] + const [columnVisibility, updateColumnVisibility] = useColumnVisibility( + 'Vendors', + columns + ) + + const visibleColumns = columns.filter( + (col) => !col.key || columnVisibility[col.key] + ) + const actionItems = { items: [ { @@ -231,23 +445,42 @@ const Vendors = () => { } } + useEffect(() => { + if (authenticated) { + fetchVendorsData() + } + }, [authenticated, fetchVendorsData]) + return ( <> {contextHolder} - - - - - + + + + + + + + + + {lazyLoading && } />} +
}} + onScroll={handleScroll} + onChange={handleTableChange} + showSorterTooltip={false} /> { const [showDeleted, setShowDeleted] = useState(false) const [filters, setFilters] = useState({}) const [sorter, setSorter] = useState({}) - const [columnVisibility, setColumnVisibility] = useState( - columns.reduce((acc, col) => { - if (col.key) { - acc[col.key] = true - } - return acc - }, {}) + const [columnVisibility, updateColumnVisibility] = useColumnVisibility( + 'GCodeFiles', + columns ) const { authenticated } = useContext(AuthContext) @@ -442,16 +438,13 @@ const GCodeFiles = () => { const getViewDropdownItems = () => { const columnItems = columns - .filter((col) => col.key && col.title !== '') // Filter out empty title columns and ensure key exists + .filter((col) => col.key && col.title !== '') .map((col) => ( { - setColumnVisibility((prev) => ({ - ...prev, - [col.key]: e.target.checked - })) + updateColumnVisibility(col.key, e.target.checked) }} > {col.title} diff --git a/src/components/Dashboard/Production/PrintJobs.jsx b/src/components/Dashboard/Production/PrintJobs.jsx index 4415a21..5ffa0f7 100644 --- a/src/components/Dashboard/Production/PrintJobs.jsx +++ b/src/components/Dashboard/Production/PrintJobs.jsx @@ -41,6 +41,7 @@ import NewPrintJob from './PrintJobs/NewPrintJob' import JobState from '../common/JobState' import SubJobCounter from '../common/SubJobCounter' import IdText from '../common/IdText' +import useColumnVisibility from '../hooks/useColumnVisibility' const { Text } = Typography @@ -287,18 +288,14 @@ const PrintJobs = () => { } ] - const [columnVisibility, setColumnVisibility] = useState( - columns.reduce((acc, col) => { - if (col.key) { - acc[col.key] = true - } - return acc - }, {}) - ) - const { authenticated } = useContext(AuthContext) const { socket } = useContext(SocketContext) + const [columnVisibility, updateColumnVisibility] = useColumnVisibility( + 'PrintJobs', + columns + ) + const handleDeployPrintJob = (printJobId) => { if (socket) { messageApi.info(`Print job ${printJobId} deployment initiated`) @@ -473,16 +470,13 @@ const PrintJobs = () => { const getViewDropdownItems = () => { const columnItems = columns - .filter((col) => col.key && col.title !== '') // Filter out empty title columns and ensure key exists + .filter((col) => col.key && col.title !== '') .map((col) => ( { - setColumnVisibility((prev) => ({ - ...prev, - [col.key]: e.target.checked - })) + updateColumnVisibility(col.key, e.target.checked) }} > {col.title} diff --git a/src/components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx b/src/components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx index eabfdcd..bc0b050 100644 --- a/src/components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx +++ b/src/components/Dashboard/Production/PrintJobs/PrintJobInfo.jsx @@ -8,7 +8,8 @@ import { Button, message, Progress, - Typography + Typography, + Flex } from 'antd' import { LoadingOutlined, ReloadOutlined } from '@ant-design/icons' import moment from 'moment' @@ -37,7 +38,7 @@ const PrintJobInfo = () => { useEffect(() => { if (socket && printJobId) { socket.on('notify_job_update', (updateData) => { - if (updateData.id === printJobId) { + if (updateData._id === printJobId) { setPrintJobData((prevData) => { if (!prevData) return prevData return { @@ -103,7 +104,16 @@ const PrintJobInfo = () => { return (
- + + + Print Job Information + + + @@ -163,9 +173,15 @@ const PrintJobInfo = () => { )} - - Sub Job Information - + + + Sub Job Information + +
) diff --git a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx index 4514338..d41da4c 100644 --- a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx +++ b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx @@ -13,7 +13,8 @@ import { Progress, Modal, Typography, - Badge + Badge, + Alert } from 'antd' import { LoadingOutlined, @@ -38,6 +39,7 @@ import FilamentStockIcon from '../../../Icons/FilamentStockIcon' import LoadFilamentStock from '../../Inventory/FilamentStocks/LoadFilamentStock' import UnloadFilamentStock from '../../Inventory/FilamentStocks/UnloadFilamentStock' +import FilamentStockState from '../../common/FilamentStockState' const { Text } = Typography @@ -57,6 +59,9 @@ const ControlPrinter = () => { useState(false) const [unloadFilamentStockModalOpen, setUnloadFilamentStockModalOpen] = useState(false) + const [klippyErrorModalOpen, setKlippyErrorModalOpen] = useState(false) + const [klippyErrorMessage, setKlippyErrorMessage] = useState('') + const [klippyStartupMessage, setKlippyStartupMessage] = useState('') const { socket } = useContext(SocketContext) const { authenticated } = useContext(AuthContext) @@ -98,7 +103,7 @@ const ControlPrinter = () => { socket.on('notify_printer_update', (statusUpdate) => { console.log('GOT STATUS', statusUpdate) setPrinterData((prevData) => { - if (statusUpdate?.id === printerId) { + if (statusUpdate?._id === printerId) { return { ...prevData, ...statusUpdate @@ -146,13 +151,35 @@ const ControlPrinter = () => { }, [authenticated, fetchPrinterDetails]) useEffect(() => { - if ( - printerData?.alerts?.some((alert) => alert.type === 'loadFilamentStock') - ) { + const loadFilamentStock = printerData?.alerts?.find( + (alert) => alert.type === 'loadFilamentStock' + ) + const klippyError = printerData?.alerts?.find( + (alert) => alert.type === 'klippyError' + ) + const klippyStartup = printerData?.alerts?.find( + (alert) => alert.type === 'klippyStartup' + ) + + if (loadFilamentStock) { setLoadFilamentStockModalOpen(true) } else { setLoadFilamentStockModalOpen(false) } + + if (klippyError) { + setKlippyErrorModalOpen(true) + setKlippyErrorMessage(klippyError.message) + } else { + setKlippyErrorModalOpen(false) + setKlippyErrorMessage('') + } + + if (klippyStartup) { + setKlippyStartupMessage(klippyStartup.message) + } else { + setKlippyStartupMessage('') + } }, [printerData?.alerts]) const actionItems = { @@ -344,6 +371,12 @@ const ControlPrinter = () => { {printerData ? ( + {printerData?.alerts?.some( + (alert) => alert.type === 'klippyError' + ) && } + {printerData?.alerts?.some( + (alert) => alert.type === 'klippyStartup' + ) && } {printerData.name} @@ -406,6 +439,14 @@ const ControlPrinter = () => { )} + {printerData?.currentFilamentStock && ( + + + + )} + {printerData.currentFilamentStock?.currentNetWeight ? ( @@ -455,6 +496,17 @@ const ControlPrinter = () => { )} + {printerData?.state.type === 'printing' && ( + + + + )} + {(() => { if ( @@ -493,27 +545,18 @@ const ControlPrinter = () => { } })()} - - {printerData?.state.type === 'printing' && ( - - - - )} - + - + @@ -560,6 +603,38 @@ const ControlPrinter = () => { reset={unloadFilamentStockModalOpen} />
+ + + Klipper Error + + } + onCancel={() => setKlippyErrorModalOpen(false)} + footer={[ + , + + ]} + > + {klippyErrorMessage} + ) } diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx index 080bebb..792dc06 100644 --- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx +++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx @@ -26,7 +26,7 @@ const breadcrumbNameMap = { '/dashboard/management/vendors/info': 'Info', '/dashboard/management/materials': 'Materials', '/dashboard/management/materials/info': 'Info', - '/dashboard/inventory/filamentstocks': 'Filaments', + '/dashboard/inventory/filamentstocks': 'Filament Stocks', '/dashboard/inventory/filamentstocks/info': 'Info', '/dashboard/inventory/partstocks': 'Parts', '/dashboard/inventory/partstocks/info': 'Info' diff --git a/src/components/Dashboard/common/DashboardLayout.jsx b/src/components/Dashboard/common/DashboardLayout.jsx index 8aa02b4..760ffbd 100644 --- a/src/components/Dashboard/common/DashboardLayout.jsx +++ b/src/components/Dashboard/common/DashboardLayout.jsx @@ -1,20 +1,17 @@ // DashboardLayout.js import PropTypes from 'prop-types' -import React, { useContext } from 'react' -import { Layout, Flex, Spin } from 'antd' -import { LoadingOutlined } from '@ant-design/icons' +import React from 'react' +import { Layout, Flex } from 'antd' import { useLocation } from 'react-router-dom' import DashboardNavigation from './DashboardNavigation' import ProductionSidebar from './ProductionSidebar' import InventorySidebar from './InventorySidebar' import ManagementSidebar from './ManagementSidebar' import DashboardBreadcrumb from './DashboardBreadcrumb' -import { SocketContext } from '../context/SocketContext' const { Content } = Layout const DashboardLayout = ({ children }) => { - const { connecting } = useContext(SocketContext) const location = useLocation() const isProduction = location.pathname.startsWith('/dashboard/production') const isInventory = location.pathname.startsWith('/dashboard/inventory') @@ -38,15 +35,6 @@ const DashboardLayout = ({ children }) => { - {connecting ? ( - } - size='middle' - style={{ color: '#808080' }} - /> - ) : ( - <> - )} {children} diff --git a/src/components/Dashboard/common/FilamentStockState.jsx b/src/components/Dashboard/common/FilamentStockState.jsx index ba553f2..573f454 100644 --- a/src/components/Dashboard/common/FilamentStockState.jsx +++ b/src/components/Dashboard/common/FilamentStockState.jsx @@ -1,14 +1,29 @@ import PropTypes from 'prop-types' import { Badge, Progress, Flex, Space, Tag, Typography } from 'antd' -import { green, red } from '@ant-design/colors' +import { green } from '@ant-design/colors' import React, { useState, useContext, useEffect } from 'react' import { SocketContext } from '../context/SocketContext' +const { Text } = Typography + +const getProgressColor = (percent) => { + if (percent <= 50) { + return green[5] + } else if (percent <= 80) { + // Interpolate between green and yellow + const ratio = (percent - 50) / 30 + return `rgb(${Math.round(255 * ratio)}, ${Math.round(255 * (1 - ratio))}, 0)` + } else { + // Interpolate between yellow and red + const ratio = (percent - 80) / 20 + return `rgb(255, ${Math.round(255 * (1 - ratio))}, 0)` + } +} + const FilamentStockState = ({ filamentStock, showProgress = true, - showStatus = true, - showFilamentStockName = true + showStatus = true }) => { const { socket } = useContext(SocketContext) const [badgeStatus, setBadgeStatus] = useState('unknown') @@ -20,7 +35,6 @@ const FilamentStockState = ({ } ) const [initialized, setInitialized] = useState(false) - const { Text } = Typography useEffect(() => { if (socket && !initialized && filamentStock?._id) { @@ -64,7 +78,6 @@ const FilamentStockState = ({ return ( - {showFilamentStockName && {filamentStock.name}} {showStatus && ( @@ -76,20 +89,21 @@ const FilamentStockState = ({ )} {showProgress && currentState.type === 'partiallyconsumed' ? ( - + +
+ +
+ + {Math.round(currentState.percent * 100) + '%'} + +
) : null}
) @@ -111,8 +125,7 @@ FilamentStockState.propTypes = { }) }), showProgress: PropTypes.bool, - showStatus: PropTypes.bool, - showFilamentStockName: PropTypes.bool + showStatus: PropTypes.bool } export default FilamentStockState diff --git a/src/components/Dashboard/common/IdText.jsx b/src/components/Dashboard/common/IdText.jsx index 58646f9..a8a32e5 100644 --- a/src/components/Dashboard/common/IdText.jsx +++ b/src/components/Dashboard/common/IdText.jsx @@ -61,6 +61,10 @@ const IdText = ({ prefix = 'FLS' hyperlink = `/dashboard/inventory/filamentstocks/info?filamentStockId=${id}` break + case 'stockaudit': + prefix = 'SAU' + hyperlink = `/dashboard/inventory/stockaudits/info?stockAuditId=${id}` + break case 'partstock': prefix = 'PTS' hyperlink = `/dashboard/management/partstocks/info?partStockId=${id}` diff --git a/src/components/Dashboard/common/InventorySidebar.jsx b/src/components/Dashboard/common/InventorySidebar.jsx index 934a186..ae14cd5 100644 --- a/src/components/Dashboard/common/InventorySidebar.jsx +++ b/src/components/Dashboard/common/InventorySidebar.jsx @@ -40,19 +40,30 @@ const InventorySidebar = () => { label: Overview, icon: }, + { type: 'divider' }, { key: 'filamentstocks', - label: Filaments, + label: ( + Filament Stocks + ), icon: }, { key: 'partstocks', - label: Parts, + label: Part Stocks, icon: }, { key: 'productstocks', - label: Products, + label: ( + Product Stocks + ), + icon: + }, + { type: 'divider' }, + { + key: 'stockaudits', + label: Stock Audits, icon: } ] diff --git a/src/components/Dashboard/common/JobState.jsx b/src/components/Dashboard/common/JobState.jsx index 779edff..98fffc9 100644 --- a/src/components/Dashboard/common/JobState.jsx +++ b/src/components/Dashboard/common/JobState.jsx @@ -71,7 +71,7 @@ const JobState = ({ {showId && ( <> - {'Sub Job '} + {'Job '} )} @@ -91,6 +91,7 @@ const JobState = ({ currentState.type === 'processing') ? ( ) : null} diff --git a/src/components/Dashboard/common/ManagementSidebar.jsx b/src/components/Dashboard/common/ManagementSidebar.jsx index 55c9604..e894bb5 100644 --- a/src/components/Dashboard/common/ManagementSidebar.jsx +++ b/src/components/Dashboard/common/ManagementSidebar.jsx @@ -64,6 +64,7 @@ const ManagementSidebar = () => { label: Materials, icon: }, + { type: 'divider' }, { key: 'settings', label: Settings, diff --git a/src/components/Dashboard/common/PrinterJobsTree.jsx b/src/components/Dashboard/common/PrinterJobsTree.jsx index 5c02f28..79d3c16 100644 --- a/src/components/Dashboard/common/PrinterJobsTree.jsx +++ b/src/components/Dashboard/common/PrinterJobsTree.jsx @@ -1,5 +1,5 @@ import PropTypes from 'prop-types' -import { Card, Tree, Spin, Space, Button, message, Typography } from 'antd' +import { Card, Tree, Spin, Space, Button, message } from 'antd' import { LoadingOutlined, ReloadOutlined } from '@ant-design/icons' import React, { useState, useEffect, useContext } from 'react' import SubJobState from './SubJobState' @@ -15,8 +15,6 @@ const PrinterJobsTree = ({ subJobs: initialSubJobs }) => { const [expandedKeys, setExpandedKeys] = useState([]) const [treeData, setTreeData] = useState([]) - const { Text } = Typography - const buildTreeData = (subJobsData) => { if (!subJobsData?.length) { setTreeData([]) @@ -42,20 +40,7 @@ const PrinterJobsTree = ({ subJobs: initialSubJobs }) => { ({ printJob, subJobs }) => { setExpandedKeys((prev) => [...prev, `printjob-${printJob._id}`]) return { - title: ( - - Print Job - - {printJob._id.substring(printJob._id.length - 6)} - - printJob.quantity - - } - /> - ), + title: , key: `printjob-${printJob._id}`, children: subJobs.map((subJob) => ({ title: ( @@ -112,7 +97,7 @@ const PrinterJobsTree = ({ subJobs: initialSubJobs }) => { if (updateData.subJobId) { setSubJobs((prevSubJobs) => prevSubJobs.map((subJob) => { - if (subJob._id === updateData.id) { + if (subJob._id === updateData._id) { return { ...subJob, state: updateData.state, @@ -161,6 +146,7 @@ const PrinterJobsTree = ({ subJobs: initialSubJobs }) => { treeData={treeData} expandedKeys={expandedKeys} onExpand={setExpandedKeys} + showLine={true} /> ) diff --git a/src/components/Dashboard/common/PrinterState.jsx b/src/components/Dashboard/common/PrinterState.jsx index d1ea988..3063761 100644 --- a/src/components/Dashboard/common/PrinterState.jsx +++ b/src/components/Dashboard/common/PrinterState.jsx @@ -32,7 +32,7 @@ const PrinterState = ({ if (socket && !initialized && printer?.id) { setInitialized(true) socket.on('notify_printer_update', (statusUpdate) => { - if (statusUpdate?.id === printer.id && statusUpdate?.state) { + if (statusUpdate?._id === printer.id && statusUpdate?.state) { setCurrentState(statusUpdate.state) } }) @@ -98,6 +98,10 @@ const PrinterState = ({ setBadgeStatus('error') setBadgeText('Error') break + case 'startup': + setBadgeStatus('warning') + setBadgeText('Startup') + break default: setBadgeStatus('default') setBadgeText(currentState.type) @@ -122,6 +126,7 @@ const PrinterState = ({ currentState.type === 'deploying') ? ( ) : null} diff --git a/src/components/Dashboard/common/ProductionSidebar.jsx b/src/components/Dashboard/common/ProductionSidebar.jsx index 477e937..5509a87 100644 --- a/src/components/Dashboard/common/ProductionSidebar.jsx +++ b/src/components/Dashboard/common/ProductionSidebar.jsx @@ -40,6 +40,7 @@ const ProductionSidebar = () => { label: Overview, icon: }, + { type: 'divider' }, { key: 'printers', label: Printers, diff --git a/src/components/Dashboard/common/StockEventTable.jsx b/src/components/Dashboard/common/StockEventTable.jsx index a671d1d..4fed950 100644 --- a/src/components/Dashboard/common/StockEventTable.jsx +++ b/src/components/Dashboard/common/StockEventTable.jsx @@ -1,52 +1,197 @@ -import React from 'react' -import { Table } from 'antd' +import React, { useEffect, useContext, useState } from 'react' +import { Table, Typography } from 'antd' import PropTypes from 'prop-types' import moment from 'moment' import IdText from './IdText' +import { AuditOutlined, PlayCircleOutlined } from '@ant-design/icons' +import { SocketContext } from '../context/SocketContext' + +import PlusMinusIcon from '../../Icons/PlusMinusIcon' + +const { Text } = Typography const StockEventTable = ({ stockEvents }) => { + const { socket } = useContext(SocketContext) + const [initialized, setInitialized] = useState(false) + const [stockEventsData, setStockEventsData] = useState(stockEvents) + + useEffect(() => { + // Add WebSocket event listener for real-time updates + if (socket && !initialized) { + setInitialized(true) + socket.on('notify_stockevent_update', (updateData) => { + console.log('Received stock event update:', updateData) + setStockEventsData((prevData) => { + return prevData.map((stockEvent) => { + if (stockEvent?._id) { + console.log('UD', updateData) + console.log('SE', stockEvent) + if (stockEvent._id === updateData._id) { + return { + ...stockEvent, + ...updateData + } + } + } + }) + }) + }) + } + + return () => { + if (socket && initialized) { + console.log('Deregistering stock event update listener') + socket.off('notify_stockevent_update') + } + } + }, [socket, initialized]) + + const getTypeFilterProps = () => { + // Get unique types from the data + const uniqueTypes = [ + ...new Set( + stockEventsData.map((record) => { + const type = record.type.toLowerCase() + if (type === 'subjob') return 'Sub Job' + if (type === 'audit') return 'Audit Adjustment' + return type.charAt(0).toUpperCase() + type.slice(1) + }) + ) + ] + + return { + filters: uniqueTypes.map((type) => ({ text: type, value: type })), + onFilter: (value, record) => { + const recordType = record.type.toLowerCase() + if (recordType === 'subjob') { + return value === 'Sub Job' + } else if (recordType === 'audit') { + return value === 'Audit Adjustment' + } + return ( + value === recordType.charAt(0).toUpperCase() + recordType.slice(1) + ) + } + } + } + const columns = [ + { + title: '', + key: 'icon', + width: 50, + render: (record) => { + switch (record.type.toLowerCase()) { + case 'subjob': + return + case 'audit': + return + default: + return null + } + } + }, { title: 'Type', dataIndex: 'type', key: 'type', - render: (type) => type.charAt(0).toUpperCase() + type.slice(1) + width: 200, + sorter: (a, b) => a.type.localeCompare(b.type), + ...getTypeFilterProps(), + render: (type) => { + switch (type.toLowerCase()) { + case 'subjob': + return 'Sub Job' + case 'audit': + return 'Audit Adjustment' + default: + return type.charAt(0).toUpperCase() + type.slice(1) + } + } }, { - title: 'Value', + title: , dataIndex: 'value', key: 'value', - render: (value) => value.toFixed(2) + 'g' + width: 100, + sorter: (a, b) => a.value - b.value, + render: (value) => { + const formattedValue = value.toFixed(2) + 'g' + return ( + + {value > 0 ? '+' + formattedValue : formattedValue} + + ) + } }, { - title: 'Sub Job ID', - render: (record) => - record.subJob ? ( - - ) : ( - 'n/a' - ) + title: 'ID', + width: 100, + render: (record) => { + if (record.subJob) { + return ( + + ) + } + if (record.stockAudit) { + return ( + + ) + } + return 'n/a' + } }, { title: 'Job ID', - render: (record) => - record.subJob ? ( - - ) : ( - 'n/a' - ) + width: 100, + render: (record) => { + if (record.subJob) { + return ( + + ) + } + return 'n/a' + } }, { - title: 'Timestamp', - dataIndex: ['timestamp', '$date'], - key: 'timestamp', - render: (timestamp) => { - if (timestamp) { - const formattedDate = moment(timestamp).format('YYYY-MM-DD HH:mm:ss') + title: 'Created At', + dataIndex: 'createdAt', + key: 'createdAt', + width: 180, + defaultSortOrder: 'descend', + sorter: (a, b) => moment(a.createdAt).unix() - moment(b.createdAt).unix(), + render: (createdAt) => { + if (createdAt) { + const formattedDate = moment(createdAt).format('YYYY-MM-DD HH:mm:ss') + return {formattedDate} + } else { + return 'n/a' + } + } + }, + { + title: 'Updated At', + dataIndex: 'updatedAt', + key: 'updatedAt', + width: 180, + sorter: (a, b) => moment(a.updatedAt).unix() - moment(b.updatedAt).unix(), + render: (updatedAt) => { + if (updatedAt) { + const formattedDate = moment(updatedAt).format('YYYY-MM-DD HH:mm:ss') return {formattedDate} } else { return 'n/a' @@ -57,9 +202,9 @@ const StockEventTable = ({ stockEvents }) => { return (
record._id.$oid} + rowKey={(record) => record._id} pagination={false} /> ) diff --git a/src/components/Dashboard/common/SubJobState.jsx b/src/components/Dashboard/common/SubJobState.jsx index d636892..962fb23 100644 --- a/src/components/Dashboard/common/SubJobState.jsx +++ b/src/components/Dashboard/common/SubJobState.jsx @@ -110,6 +110,7 @@ const SubJobState = ({ currentState.type === 'processing') ? ( ) : null} diff --git a/src/components/Dashboard/common/SubJobsTree.jsx b/src/components/Dashboard/common/SubJobsTree.jsx index a457c6d..ebfc20c 100644 --- a/src/components/Dashboard/common/SubJobsTree.jsx +++ b/src/components/Dashboard/common/SubJobsTree.jsx @@ -140,7 +140,7 @@ const SubJobsTree = ({ printJobData }) => { ...prevData, // eslint-disable-next-line camelcase subJobs: prevData.subJobs.map((subJob) => { - if (subJob._id === updateData.id) { + if (subJob._id === updateData._id) { return { ...subJob, state: updateData.state, @@ -193,6 +193,7 @@ const SubJobsTree = ({ printJobData }) => { treeData={treeData} expandedKeys={expandedKeys} onExpand={setExpandedKeys} + showLine={true} /> ) diff --git a/src/components/Dashboard/hooks/useCollapseState.js b/src/components/Dashboard/hooks/useCollapseState.js new file mode 100644 index 0000000..2c0e06e --- /dev/null +++ b/src/components/Dashboard/hooks/useCollapseState.js @@ -0,0 +1,31 @@ +import { useState, useEffect } from 'react' + +const useCollapseState = (componentName, defaultState = {}) => { + const getInitialState = () => { + const stored = sessionStorage.getItem(`${componentName}_collapseState`) + if (stored) { + return JSON.parse(stored) + } + return defaultState + } + + const [collapseState, setCollapseState] = useState(getInitialState) + + useEffect(() => { + sessionStorage.setItem( + `${componentName}_collapseState`, + JSON.stringify(collapseState) + ) + }, [collapseState, componentName]) + + const updateCollapseState = (key, value) => { + setCollapseState((prev) => ({ + ...prev, + [key]: value + })) + } + + return [collapseState, updateCollapseState] +} + +export default useCollapseState diff --git a/src/components/Dashboard/hooks/useColumnVisibility.js b/src/components/Dashboard/hooks/useColumnVisibility.js new file mode 100644 index 0000000..5051889 --- /dev/null +++ b/src/components/Dashboard/hooks/useColumnVisibility.js @@ -0,0 +1,37 @@ +import { useState, useEffect } from 'react' + +const useColumnVisibility = (componentName, columns) => { + const getInitialVisibility = () => { + const stored = sessionStorage.getItem(`${componentName}_columnVisibility`) + if (stored) { + return JSON.parse(stored) + } + // Default visibility - all columns visible + return columns.reduce((acc, col) => { + if (col.key) { + acc[col.key] = true + } + return acc + }, {}) + } + + const [columnVisibility, setColumnVisibility] = useState(getInitialVisibility) + + useEffect(() => { + sessionStorage.setItem( + `${componentName}_columnVisibility`, + JSON.stringify(columnVisibility) + ) + }, [columnVisibility, componentName]) + + const updateColumnVisibility = (key, value) => { + setColumnVisibility((prev) => ({ + ...prev, + [key]: value + })) + } + + return [columnVisibility, updateColumnVisibility] +} + +export default useColumnVisibility diff --git a/src/components/Icons/PlusMinusIcon.jsx b/src/components/Icons/PlusMinusIcon.jsx new file mode 100644 index 0000000..123265f --- /dev/null +++ b/src/components/Icons/PlusMinusIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/plusminusicon.svg' + +const PlusMinusIcon = (props) => + +export default PlusMinusIcon