diff --git a/client/package-lock.json b/client/package-lock.json index 9e3a2cfd9..4338c24ee 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,15 +14,15 @@ "@emotion/is-prop-valid": "^1.3.1", "@fingerprintjs/fingerprintjs": "^4.6.1", "@firebase/analytics": "^0.10.17", - "@firebase/app": "^0.13.2", + "@firebase/app": "^0.14.0", "@firebase/auth": "^1.10.8", "@firebase/firestore": "^4.8.0", "@firebase/messaging": "^0.12.22", "@jsreport/browser-client": "^3.1.0", "@reduxjs/toolkit": "^2.8.2", - "@sentry/cli": "^2.46.0", - "@sentry/react": "^9.35.0", - "@sentry/vite-plugin": "^3.5.0", + "@sentry/cli": "^2.50.0", + "@sentry/react": "^9.40.0", + "@sentry/vite-plugin": "^3.6.1", "@splitsoftware/splitio-react": "^2.3.1", "@tanem/react-nprogress": "^5.0.53", "antd": "^5.26.4", @@ -42,9 +42,9 @@ "i18next": "^24.2.3", "i18next-browser-languagedetector": "^8.2.0", "immutability-helper": "^3.1.1", - "libphonenumber-js": "^1.12.9", + "libphonenumber-js": "^1.12.10", "logrocket": "^9.0.2", - "markerjs2": "^2.32.4", + "markerjs2": "^2.32.6", "memoize-one": "^6.0.0", "normalize-url": "^8.0.2", "object-hash": "^3.0.0", @@ -91,12 +91,12 @@ "@ant-design/icons": "^6.0.0", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.27.1", - "@dotenvx/dotenvx": "^1.45.2", + "@dotenvx/dotenvx": "^1.48.0", "@emotion/babel-plugin": "^11.13.5", "@emotion/react": "^11.14.0", - "@eslint/js": "^9.30.1", - "@playwright/test": "^1.53.2", - "@sentry/webpack-plugin": "^3.5.0", + "@eslint/js": "^9.31.0", + "@playwright/test": "^1.54.1", + "@sentry/webpack-plugin": "^3.6.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -111,7 +111,7 @@ "jsdom": "^26.0.0", "memfs": "^4.17.2", "os-browserify": "^0.3.0", - "playwright": "^1.53.2", + "playwright": "^1.54.1", "react-error-overlay": "^6.1.0", "redux-logger": "^3.0.6", "source-map-explorer": "^2.5.3", @@ -232,15 +232,15 @@ "license": "MIT" }, "node_modules/@ant-design/pro-layout": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@ant-design/pro-layout/-/pro-layout-7.22.6.tgz", - "integrity": "sha512-4DGK9nZ7B0FGVpJkMCUDrksBhTHc/pg72BMOBguqMOZCdsuEuJAyIl12eYK8gasGcbYtHzjvdYCamR+TJR8Low==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@ant-design/pro-layout/-/pro-layout-7.22.7.tgz", + "integrity": "sha512-fvmtNA1r9SaasVIQIQt611VSlNxtVxDbQ3e+1GhYQza3tVJi/3gCZuDyfMfTnbLmf3PaW/YvLkn7MqDbzAzoLA==", "license": "MIT", "dependencies": { "@ant-design/cssinjs": "^1.21.1", "@ant-design/icons": "^5.0.0", - "@ant-design/pro-provider": "2.16.1", - "@ant-design/pro-utils": "2.17.2", + "@ant-design/pro-provider": "2.16.2", + "@ant-design/pro-utils": "2.18.0", "@babel/runtime": "^7.18.0", "@umijs/route-utils": "^4.0.0", "@umijs/use-params": "^1.0.9", @@ -301,9 +301,9 @@ } }, "node_modules/@ant-design/pro-provider": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@ant-design/pro-provider/-/pro-provider-2.16.1.tgz", - "integrity": "sha512-9kSiptgoEybRleajA9KiMqnGqLTbIGdm7TDclBS4QGGrjkHfcXDw+GruiDwghOHvTwTkZT98Xttvw4w1XfrS+g==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@ant-design/pro-provider/-/pro-provider-2.16.2.tgz", + "integrity": "sha512-0KmCH1EaOND787Jz6VRMYtLNZmqfT0JPjdUfxhyOxFfnBRfrjyfZgIa6CQoAJLEUMWv57PccWS8wRHVUUk2Yiw==", "license": "MIT", "dependencies": { "@ant-design/cssinjs": "^1.21.1", @@ -320,13 +320,13 @@ } }, "node_modules/@ant-design/pro-utils": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/@ant-design/pro-utils/-/pro-utils-2.17.2.tgz", - "integrity": "sha512-DE7H14nfmdTyPwmJA1mgxHzcedOHSBYpTcqCvEAooK8fi8yXpcwpUs7XuUTeFHPj1gHYl/17X8Mzc3ngB8s63Q==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@ant-design/pro-utils/-/pro-utils-2.18.0.tgz", + "integrity": "sha512-8+ikyrN8L8a8Ph4oeHTOJEiranTj18+9+WHCHjKNdEfukI7Rjn8xpYdLJWb2AUJkb9d4eoAqjd5+k+7w81Df0w==", "license": "MIT", "dependencies": { "@ant-design/icons": "^5.0.0", - "@ant-design/pro-provider": "2.16.1", + "@ant-design/pro-provider": "2.16.2", "@babel/runtime": "^7.18.0", "classnames": "^2.3.2", "dayjs": "^1.11.10", @@ -495,30 +495,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -569,15 +569,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -670,6 +670,15 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", @@ -843,12 +852,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -2421,36 +2430,27 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", + "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2585,9 +2585,9 @@ } }, "node_modules/@dotenvx/dotenvx": { - "version": "1.45.2", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.45.2.tgz", - "integrity": "sha512-dljVOZTZhwF3G6E1ifDviOOF/vdHqf37xHVWYzNWW+Zk8Mm8KSJiKvWnFze1KknuWUPNb1jDL53I1f8XXKHEYg==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.48.0.tgz", + "integrity": "sha512-UrAKBvUYl8SwCyYa8U8lND4rkdPbd7uOPtCPeiObeg5B44yrdH2ioO0tLfsGAGoenLmf6IpN6be4eT4gXxyzjQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2602,8 +2602,7 @@ "which": "^4.0.0" }, "bin": { - "dotenvx": "src/cli/dotenvx.js", - "git-dotenvx": "src/cli/dotenvx.js" + "dotenvx": "src/cli/dotenvx.js" }, "funding": { "url": "https://dotenvx.com" @@ -2910,9 +2909,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.30.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", - "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, "license": "MIT", "engines": { @@ -2932,15 +2931,15 @@ } }, "node_modules/@firebase/analytics": { - "version": "0.10.17", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.17.tgz", - "integrity": "sha512-n5vfBbvzduMou/2cqsnKrIes4auaBjdhg8QNA2ZQZ59QgtO2QiwBaXQZQE4O4sgB0Ds1tvLgUUkY+pwzu6/xEg==", + "version": "0.10.18", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.18.tgz", + "integrity": "sha512-iN7IgLvM06iFk8BeFoWqvVpRFW3Z70f+Qe2PfCJ7vPIgLPjHXDE774DhCT5Y2/ZU/ZbXPDPD60x/XPWEoZLNdg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.18", - "@firebase/installations": "0.6.18", - "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.1", + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2948,34 +2947,34 @@ } }, "node_modules/@firebase/app": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz", - "integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.0.tgz", + "integrity": "sha512-APIAeKvRNFWKJLjIL8wLDjh7u8g6ZjaeVmItyqSjCdEkJj14UuVlus74D8ofsOMWh45HEwxwkd96GYbi+CImEg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.18", - "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.1", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", "idb": "7.1.1", "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@firebase/auth": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz", - "integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.0.tgz", + "integrity": "sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.18", - "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.1", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "@firebase/app": "0.x", @@ -2988,47 +2987,47 @@ } }, "node_modules/@firebase/component": { - "version": "0.6.18", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.18.tgz", - "integrity": "sha512-n28kPCkE2dL2U28fSxZJjzPPVpKsQminJ6NrzcKXAI0E/lYC8YhfwpyllScqVEvAI3J2QgJZWYgrX+1qGI+SQQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz", + "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==", "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.12.1", + "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@firebase/firestore": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.8.0.tgz", - "integrity": "sha512-QSRk+Q1/CaabKyqn3C32KSFiOdZpSqI9rpLK5BHPcooElumOBooPFa6YkDdiT+/KhJtel36LdAacha9BptMj2A==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.0.tgz", + "integrity": "sha512-5zl0+/h1GvlCSLt06RMwqFsd7uqRtnNZt4sW99k2rKRd6k/ECObIWlEnvthm2cuOSnUmwZknFqtmd1qyYSLUuQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.18", - "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.1", - "@firebase/webchannel-wrapper": "1.0.3", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "@firebase/webchannel-wrapper": "1.0.4", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "node_modules/@firebase/installations": { - "version": "0.6.18", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.18.tgz", - "integrity": "sha512-NQ86uGAcvO8nBRwVltRL9QQ4Reidc/3whdAasgeWCPIcrhOKDuNpAALa6eCVryLnK14ua2DqekCOX5uC9XbU/A==", + "version": "0.6.19", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz", + "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.18", - "@firebase/util": "1.12.1", + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -3037,27 +3036,27 @@ } }, "node_modules/@firebase/logger": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", - "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", + "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@firebase/messaging": { - "version": "0.12.22", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.22.tgz", - "integrity": "sha512-GJcrPLc+Hu7nk+XQ70Okt3M1u1eRr2ZvpMbzbc54oTPJZySHcX9ccZGVFcsZbSZ6o1uqumm8Oc7OFkD3Rn1/og==", + "version": "0.12.23", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz", + "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.18", - "@firebase/installations": "0.6.18", + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.12.1", + "@firebase/util": "1.13.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -3072,22 +3071,22 @@ "license": "Apache-2.0" }, "node_modules/@firebase/util": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.1.tgz", - "integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", + "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@firebase/webchannel-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz", - "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.4.tgz", + "integrity": "sha512-6m8+P+dE/RPl4OPzjTxcTbQ0rGeRyeTvAi9KwIffBVCiAMKrfXfLZaqD1F+m8t4B5/Q5aHsMozOgirkH1F5oMQ==", "license": "Apache-2.0" }, "node_modules/@graphql-typed-document-node/core": { @@ -3184,17 +3183,13 @@ "license": "MIT" }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -3206,15 +3201,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", @@ -3233,9 +3219,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3516,13 +3502,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz", - "integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==", + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz", + "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.53.2" + "playwright": "1.54.1" }, "bin": { "playwright": "cli.js" @@ -3884,9 +3870,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.19", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", - "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", "dev": true, "license": "MIT" }, @@ -4466,89 +4452,89 @@ "license": "MIT" }, "node_modules/@sentry-internal/browser-utils": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.35.0.tgz", - "integrity": "sha512-75/zOArDQ4ASgndKGQo0m0v8P921eq/Q/sJvR14NopzwuwAchBhjziixWCwxKgvoA20eg3OGwMIkzztxmdp2Tw==", + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.40.0.tgz", + "integrity": "sha512-Ajvz6jN+EEMKrOHcUv2+HlhbRUh69uXhhRoBjJw8sc61uqA2vv3QWyBSmTRoHdTnLGboT5bKEhHIkzVXb+YgEw==", "license": "MIT", "dependencies": { - "@sentry/core": "9.35.0" + "@sentry/core": "9.40.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/feedback": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.35.0.tgz", - "integrity": "sha512-IKaZWUmqqqLucuJ5EGgwdrBdvP3l3STXvgKsLmW2l+s9WYbvfPPHukZhUULYRsXleQKXnOuz44WQmwNeZYQutw==", + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.40.0.tgz", + "integrity": "sha512-39UbLdGWGvSJ7bAzRnkv91cBdd6fLbdkLVVvqE2ZUfegm7+rH1mRPglmEhw4VE4mQfKZM1zWr/xus2+XPqJcYw==", "license": "MIT", "dependencies": { - "@sentry/core": "9.35.0" + "@sentry/core": "9.40.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.35.0.tgz", - "integrity": "sha512-veGNAXeHXULzkGPudMg5iFqkW4wFD/qVbQSr+s0q3+IZ7vJ+Eql+eBDZEKrfKYIBdNOf5POr+KaEBMpMGCbEkQ==", + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.40.0.tgz", + "integrity": "sha512-WrmCvqbLJQC45IFRVN3k0J5pU5NkdX0e9o6XxjcmDiATKk00RHnW4yajnCJ8J1cPR4918yqiJHPX5xpG08BZNA==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.35.0", - "@sentry/core": "9.35.0" + "@sentry-internal/browser-utils": "9.40.0", + "@sentry/core": "9.40.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.35.0.tgz", - "integrity": "sha512-nXxrEIkpn+FBxYsD4JPQStEGQWF0j0Rs0LoCyuB1e2QeEg6Pipqg4DIjWDjZyeUAsdoaUsIRhWbMK5OBWUuudw==", + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.40.0.tgz", + "integrity": "sha512-GLoJ4R4Uipd7Vb+0LzSJA2qCyN1J6YalQIoDuOJTfYyykHvKltds5D8a/5S3Q6d8PcL/nxTn93fynauGEZt2Ow==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "9.35.0", - "@sentry/core": "9.35.0" + "@sentry-internal/replay": "9.40.0", + "@sentry/core": "9.40.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/babel-plugin-component-annotate": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.5.0.tgz", - "integrity": "sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.6.1.tgz", + "integrity": "sha512-zmvUa4RpzDG3LQJFpGCE8lniz8Rk1Wa6ZvvK+yEH+snZeaHHRbSnAQBMR607GOClP+euGHNO2YtaY4UAdNTYbg==", "license": "MIT", "engines": { "node": ">= 14" } }, "node_modules/@sentry/browser": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.35.0.tgz", - "integrity": "sha512-m1fRwMa1vik6VFAAz6RlJUUU+0+Uo+QIKJWWOx9calb11Zt4wIg9wvox7TOgMd8KPt3sefPXIPM38A+uixyXYw==", + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.40.0.tgz", + "integrity": "sha512-qz/1Go817vcsbcIwgrz4/T34vi3oQ4UIqikosuaCTI9wjZvK0HyW3QmLvTbAnsE7G7h6+UZsVkpO5R16IQvQhQ==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "9.35.0", - "@sentry-internal/feedback": "9.35.0", - "@sentry-internal/replay": "9.35.0", - "@sentry-internal/replay-canvas": "9.35.0", - "@sentry/core": "9.35.0" + "@sentry-internal/browser-utils": "9.40.0", + "@sentry-internal/feedback": "9.40.0", + "@sentry-internal/replay": "9.40.0", + "@sentry-internal/replay-canvas": "9.40.0", + "@sentry/core": "9.40.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/bundler-plugin-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.5.0.tgz", - "integrity": "sha512-zDzPrhJqAAy2VzV4g540qAZH4qxzisstK2+NIJPZUUKztWRWUV2cMHsyUtdctYgloGkLyGpZJBE3RE6dmP/xqQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.6.1.tgz", + "integrity": "sha512-/ubWjPwgLep84sUPzHfKL2Ns9mK9aQrEX4aBFztru7ygiJidKJTxYGtvjh4dL2M1aZ0WRQYp+7PF6+VKwdZXcQ==", "license": "MIT", "dependencies": { "@babel/core": "^7.18.5", - "@sentry/babel-plugin-component-annotate": "3.5.0", - "@sentry/cli": "2.42.2", + "@sentry/babel-plugin-component-annotate": "3.6.1", + "@sentry/cli": "^2.49.0", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^9.3.2", @@ -4559,175 +4545,10 @@ "node": ">= 14" } }, - "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli": { - "version": "2.42.2", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.42.2.tgz", - "integrity": "sha512-spb7S/RUumCGyiSTg8DlrCX4bivCNmU/A1hcfkwuciTFGu8l5CDc2I6jJWWZw8/0enDGxuj5XujgXvU5tr4bxg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.7", - "progress": "^2.0.3", - "proxy-from-env": "^1.1.0", - "which": "^2.0.2" - }, - "bin": { - "sentry-cli": "bin/sentry-cli" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@sentry/cli-darwin": "2.42.2", - "@sentry/cli-linux-arm": "2.42.2", - "@sentry/cli-linux-arm64": "2.42.2", - "@sentry/cli-linux-i686": "2.42.2", - "@sentry/cli-linux-x64": "2.42.2", - "@sentry/cli-win32-i686": "2.42.2", - "@sentry/cli-win32-x64": "2.42.2" - } - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-darwin": { - "version": "2.42.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.42.2.tgz", - "integrity": "sha512-GtJSuxER7Vrp1IpxdUyRZzcckzMnb4N5KTW7sbTwUiwqARRo+wxS+gczYrS8tdgtmXs5XYhzhs+t4d52ITHMIg==", - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-linux-arm": { - "version": "2.42.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.2.tgz", - "integrity": "sha512-7udCw+YL9lwq+9eL3WLspvnuG+k5Icg92YE7zsteTzWLwgPVzaxeZD2f8hwhsu+wmL+jNqbpCRmktPteh3i2mg==", - "cpu": [ - "arm" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-linux-arm64": { - "version": "2.42.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.2.tgz", - "integrity": "sha512-BOxzI7sgEU5Dhq3o4SblFXdE9zScpz6EXc5Zwr1UDZvzgXZGosUtKVc7d1LmkrHP8Q2o18HcDWtF3WvJRb5Zpw==", - "cpu": [ - "arm64" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-linux-i686": { - "version": "2.42.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.2.tgz", - "integrity": "sha512-Sw/dQp5ZPvKnq3/y7wIJyxTUJYPGoTX/YeMbDs8BzDlu9to2LWV3K3r7hE7W1Lpbaw4tSquUHiQjP5QHCOS7aQ==", - "cpu": [ - "x86", - "ia32" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-linux-x64": { - "version": "2.42.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.2.tgz", - "integrity": "sha512-mU4zUspAal6TIwlNLBV5oq6yYqiENnCWSxtSQVzWs0Jyq97wtqGNG9U+QrnwjJZ+ta/hvye9fvL2X25D/RxHQw==", - "cpu": [ - "x64" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "linux", - "freebsd" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-win32-i686": { - "version": "2.42.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.2.tgz", - "integrity": "sha512-iHvFHPGqgJMNqXJoQpqttfsv2GI3cGodeTq4aoVLU/BT3+hXzbV0x1VpvvEhncJkDgDicJpFLM8sEPHb3b8abw==", - "cpu": [ - "x86", - "ia32" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/@sentry/cli-win32-x64": { - "version": "2.42.2", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.2.tgz", - "integrity": "sha512-vPPGHjYoaGmfrU7xhfFxG7qlTBacroz5NdT+0FmDn6692D8IvpNXl1K+eV3Kag44ipJBBeR8g1HRJyx/F/9ACw==", - "cpu": [ - "x64" - ], - "license": "BSD-3-Clause", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/@sentry/bundler-plugin-core/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@sentry/cli": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.46.0.tgz", - "integrity": "sha512-nqoPl7UCr446QFkylrsRrUXF51x8Z9dGquyf4jaQU+OzbOJMqclnYEvU6iwbwvaw3tu/2DnoZE/Og+Nq1h63sA==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.50.0.tgz", + "integrity": "sha512-OHRRQPUNjBpzOT6arNhxXQ71DKs5jSziCfDzmEGwAs+K8J/I1QxnvJkto88HbXE54oiWhSEJwL0pvcowFXyVbA==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -4744,20 +4565,20 @@ "node": ">= 10" }, "optionalDependencies": { - "@sentry/cli-darwin": "2.46.0", - "@sentry/cli-linux-arm": "2.46.0", - "@sentry/cli-linux-arm64": "2.46.0", - "@sentry/cli-linux-i686": "2.46.0", - "@sentry/cli-linux-x64": "2.46.0", - "@sentry/cli-win32-arm64": "2.46.0", - "@sentry/cli-win32-i686": "2.46.0", - "@sentry/cli-win32-x64": "2.46.0" + "@sentry/cli-darwin": "2.50.0", + "@sentry/cli-linux-arm": "2.50.0", + "@sentry/cli-linux-arm64": "2.50.0", + "@sentry/cli-linux-i686": "2.50.0", + "@sentry/cli-linux-x64": "2.50.0", + "@sentry/cli-win32-arm64": "2.50.0", + "@sentry/cli-win32-i686": "2.50.0", + "@sentry/cli-win32-x64": "2.50.0" } }, "node_modules/@sentry/cli-darwin": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.46.0.tgz", - "integrity": "sha512-5Ll+e5KAdIk9OYiZO8aifMBRNWmNyPjSqdjaHlBC1Qfh7pE3b1zyzoHlsUazG0bv0sNrSGea8e7kF5wIO1hvyg==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.50.0.tgz", + "integrity": "sha512-Aj+cLBZ0dCw+pdUxvJ1U71PnKh2YjvpzLN9h1ZTe8UI3FqmkKkSH/J8mN/5qmR7qUHjDcm2l+wfgVUaaP8CWbA==", "license": "BSD-3-Clause", "optional": true, "os": [ @@ -4768,9 +4589,9 @@ } }, "node_modules/@sentry/cli-linux-arm": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.46.0.tgz", - "integrity": "sha512-WRrLNq/TEX/TNJkGqq6Ad0tGyapd5dwlxtsPbVBrIdryuL1mA7VCBoaHBr3kcwJLsgBHFH0lmkMee2ubNZZdkg==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.50.0.tgz", + "integrity": "sha512-SGPAFwOY2of2C+RUBJcxMN2JXikVFEk8ypYOsQTEvV/48cLejcO/O2mHIj/YKgIkrfn3t7LlqdK6g75lkz+F8Q==", "cpu": [ "arm" ], @@ -4786,9 +4607,9 @@ } }, "node_modules/@sentry/cli-linux-arm64": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.46.0.tgz", - "integrity": "sha512-OEJN8yAjI9y5B4telyqzu27Hi3+S4T8VxZCqJz1+z2Mp0Q/MZ622AahVPpcrVq/5bxrnlZR16+lKh8L1QwNFPg==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.50.0.tgz", + "integrity": "sha512-p6hIh4Bb87qBfEz9w5dxEPAohIKcw68qoy5VUTx+cCanO8uXNWWsT78xtUNFRscW9zc6MxQMSITTWaCEIKvxRA==", "cpu": [ "arm64" ], @@ -4804,9 +4625,9 @@ } }, "node_modules/@sentry/cli-linux-i686": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.46.0.tgz", - "integrity": "sha512-xko3/BVa4LX8EmRxVOCipV+PwfcK5Xs8lP6lgF+7NeuAHMNL4DqF6iV9rrN8gkGUHCUI9RXSve37uuZnFy55+Q==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.50.0.tgz", + "integrity": "sha512-umhGmbiCUG7MvjTm8lXFmFxQjyTVtYakilBwPTVzRELmNKxxhfKRxwSSA+hUKetAUzNd8fJx8K7yqdw+qRA7Pg==", "cpu": [ "x86", "ia32" @@ -4823,9 +4644,9 @@ } }, "node_modules/@sentry/cli-linux-x64": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.46.0.tgz", - "integrity": "sha512-hJ1g5UEboYcOuRia96LxjJ0jhnmk8EWLDvlGnXLnYHkwy3ree/L7sNgdp/QsY8Z4j2PGO5f22Va+UDhSjhzlfQ==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.50.0.tgz", + "integrity": "sha512-ugIIx9+wUmguxOUe9ZVacvdCffZwqtFSKwpJ06Nqes0XfL4ZER4Qlq3/miCZ8m150C4xK5ym/QCwB41ffBqI4g==", "cpu": [ "x64" ], @@ -4841,9 +4662,9 @@ } }, "node_modules/@sentry/cli-win32-arm64": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.46.0.tgz", - "integrity": "sha512-mN7cpPoCv2VExFRGHt+IoK11yx4pM4ADZQGEso5BAUZ5duViXB2WrAXCLd8DrwMnP0OE978a7N8OtzsFqjkbNA==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.50.0.tgz", + "integrity": "sha512-fMyBSKLrVHY9944t8oTpul+6osyQeuN8GGGP3diDxGQpynYL+vhcHZIpXFRH398+3kedG/IFoY7EwGgIEqWzmw==", "cpu": [ "arm64" ], @@ -4857,9 +4678,9 @@ } }, "node_modules/@sentry/cli-win32-i686": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.46.0.tgz", - "integrity": "sha512-6F73AUE3lm71BISUO19OmlnkFD5WVe4/wA1YivtLZTc1RU3eUYJLYxhDfaH3P77+ycDppQ2yCgemLRaA4A8mNQ==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.50.0.tgz", + "integrity": "sha512-VbC+l2Y2kB7Lsun2c8t7ZGwmljmXnyncZLW9PjdEyJSTAJ9GnEnSvyFSPXNLV/eHJnfQffzU7QTjU8vkQ7XMYg==", "cpu": [ "x86", "ia32" @@ -4874,9 +4695,9 @@ } }, "node_modules/@sentry/cli-win32-x64": { - "version": "2.46.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.46.0.tgz", - "integrity": "sha512-yuGVcfepnNL84LGA0GjHzdMIcOzMe0bjPhq/rwPsPN+zu11N+nPR2wV2Bum4U0eQdqYH3iAlMdL5/BEQfuLJww==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.50.0.tgz", + "integrity": "sha512-nMktyF93NtQUOViAAKHpHSWACOGjOkKjiewi4pD6W3sWllFiPPyt15XoyApqWwnICDRQu2DI5vnil4ck6/k7mw==", "cpu": [ "x64" ], @@ -4911,22 +4732,22 @@ } }, "node_modules/@sentry/core": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.35.0.tgz", - "integrity": "sha512-bdAtzVQZ/wn4L/m8r2OUCCG/NWr0Q8dyZDwdwvINJaMbyhDRUdQh/MWjrz+id/3JoOL1LigAyTV1h4FJDGuwUQ==", + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.40.0.tgz", + "integrity": "sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@sentry/react": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.35.0.tgz", - "integrity": "sha512-zoLcucRYhSLKGYJ0b06MBVF+s3DvLK3YY651sf9boV071tWZs6Q8FDDD3E+pgw8t+ngL+6kB989Ns2HhyLyYIQ==", + "version": "9.40.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.40.0.tgz", + "integrity": "sha512-y00d33qozmQAKroQ4Kk2jxhznprPBOb55SL4LOpNPRHGEomxZCUeM3geltczrf14JsGowCr5+xlT+cZQ2XcNlA==", "license": "MIT", "dependencies": { - "@sentry/browser": "9.35.0", - "@sentry/core": "9.35.0", + "@sentry/browser": "9.40.0", + "@sentry/core": "9.40.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -4937,12 +4758,12 @@ } }, "node_modules/@sentry/vite-plugin": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-3.5.0.tgz", - "integrity": "sha512-jUnpTdpicG8wefamw7eNo2uO+Q3KCbOAiF76xH4gfNHSW6TN2hBfOtmLu7J+ive4c0Al3+NEHz19bIPR0lkwWg==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-3.6.1.tgz", + "integrity": "sha512-x8WMdv2K2HcGS2ezEUIEZXpT/fNeWQ9rsEeF0K9DfKXK8Z9lzRmCr6TVA6I9+yW39Is+1/0cv1Rsu0LhO7lHzg==", "license": "MIT", "dependencies": { - "@sentry/bundler-plugin-core": "3.5.0", + "@sentry/bundler-plugin-core": "3.6.1", "unplugin": "1.0.1" }, "engines": { @@ -4950,13 +4771,13 @@ } }, "node_modules/@sentry/webpack-plugin": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-3.5.0.tgz", - "integrity": "sha512-xvclj0QY2HyU7uJLzOlHSrZQBDwfnGKJxp8mmlU4L7CwmK+8xMCqlO7tYZoqE4K/wU3c2xpXql70x8qmvNMxzQ==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-3.6.1.tgz", + "integrity": "sha512-F2yqwbdxfCENMN5u4ih4WfOtGjW56/92DBC0bU6un7Ns/l2qd+wRONIvrF+58rl/VkCFfMlUtZTVoKGRyMRmHA==", "dev": true, "license": "MIT", "dependencies": { - "@sentry/bundler-plugin-core": "3.5.0", + "@sentry/bundler-plugin-core": "3.6.1", "unplugin": "1.0.1", "uuid": "^9.0.0" }, @@ -5819,16 +5640,16 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz", - "integrity": "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", + "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.19", + "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, @@ -5836,7 +5657,7 @@ "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@vitest/expect": { @@ -6120,9 +5941,9 @@ } }, "node_modules/antd": { - "version": "5.26.4", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.26.4.tgz", - "integrity": "sha512-e1EnOvEkvvqcQ18dxfzChBJyJACyih13WpNf2OtnP9z2POh/SF0fXL+ynUemT1zfr+p+P1po/tmHXaMc5PMghg==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.26.5.tgz", + "integrity": "sha512-HB7Cr0tPZMMeAyDDH8KZU0aP8/uO51oasmflJhDBzaRRZmLT8Pyjtt8qS22Sc839glm8gpKQcaG0mln66Gt9Fg==", "license": "MIT", "dependencies": { "@ant-design/colors": "^7.2.1", @@ -7197,9 +7018,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "funding": [ { "type": "opencollective", @@ -7239,9 +7060,9 @@ } }, "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", "dev": true, "license": "MIT", "dependencies": { @@ -7252,7 +7073,7 @@ "pathval": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/chalk": { @@ -8361,9 +8182,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.176", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.176.tgz", - "integrity": "sha512-2nDK9orkm7M9ZZkjO3PjbEd3VUulQLyg5T9O3enJdFvUg46Hzd4DUvTvAuEgbdHYXyFsiG4A5sO9IzToMH1cDg==", + "version": "1.5.187", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", + "integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==", "license": "ISC" }, "node_modules/elliptic": { @@ -11496,9 +11317,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.9", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.9.tgz", - "integrity": "sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg==", + "version": "1.12.10", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.10.tgz", + "integrity": "sha512-E91vHJD61jekHHR/RF/E83T/CMoaLXT7cwYA75T4gim4FZjnM6hbJjVIGg7chqlSqRsSvQ3izGmOjHy1SQzcGQ==", "license": "MIT" }, "node_modules/lines-and-columns": { @@ -11679,9 +11500,9 @@ } }, "node_modules/markerjs2": { - "version": "2.32.4", - "resolved": "https://registry.npmjs.org/markerjs2/-/markerjs2-2.32.4.tgz", - "integrity": "sha512-pk8gZMqSw0iDwSuH4Rt3jsYwA2J0EYUngIFIUvkHFVTiZPK+djuwrv4wfdK81I81FqnQ5iYp9buv/Sjg3Td0Tw==", + "version": "2.32.6", + "resolved": "https://registry.npmjs.org/markerjs2/-/markerjs2-2.32.6.tgz", + "integrity": "sha512-uT2ZzORY/oTJm0ByvXtKllg6hPyb+ndFmXAL9tRJQsyUBUEHDAlg3+/mBKRTzeDP0wD94Ef0XXod5v4+g1hgwg==", "license": "SEE LICENSE IN LICENSE" }, "node_modules/material-colors": { @@ -13263,9 +13084,9 @@ "license": "MIT" }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -13337,13 +13158,13 @@ } }, "node_modules/playwright": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", - "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==", + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz", + "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.53.2" + "playwright-core": "1.54.1" }, "bin": { "playwright": "cli.js" @@ -13356,9 +13177,9 @@ } }, "node_modules/playwright-core": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz", - "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==", + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz", + "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -16475,9 +16296,9 @@ } }, "node_modules/swr": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz", - "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", + "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", "license": "MIT", "dependencies": { "dequal": "^2.0.3", diff --git a/client/package.json b/client/package.json index 732de4277..460621c33 100644 --- a/client/package.json +++ b/client/package.json @@ -13,15 +13,15 @@ "@emotion/is-prop-valid": "^1.3.1", "@fingerprintjs/fingerprintjs": "^4.6.1", "@firebase/analytics": "^0.10.17", - "@firebase/app": "^0.13.2", + "@firebase/app": "^0.14.0", "@firebase/auth": "^1.10.8", "@firebase/firestore": "^4.8.0", "@firebase/messaging": "^0.12.22", "@jsreport/browser-client": "^3.1.0", "@reduxjs/toolkit": "^2.8.2", - "@sentry/cli": "^2.46.0", - "@sentry/react": "^9.35.0", - "@sentry/vite-plugin": "^3.5.0", + "@sentry/cli": "^2.50.0", + "@sentry/react": "^9.40.0", + "@sentry/vite-plugin": "^3.6.1", "@splitsoftware/splitio-react": "^2.3.1", "@tanem/react-nprogress": "^5.0.53", "antd": "^5.26.4", @@ -41,9 +41,9 @@ "i18next": "^24.2.3", "i18next-browser-languagedetector": "^8.2.0", "immutability-helper": "^3.1.1", - "libphonenumber-js": "^1.12.9", + "libphonenumber-js": "^1.12.10", "logrocket": "^9.0.2", - "markerjs2": "^2.32.4", + "markerjs2": "^2.32.6", "memoize-one": "^6.0.0", "normalize-url": "^8.0.2", "object-hash": "^3.0.0", @@ -131,12 +131,12 @@ "@ant-design/icons": "^6.0.0", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-react": "^7.27.1", - "@dotenvx/dotenvx": "^1.45.2", + "@dotenvx/dotenvx": "^1.48.0", "@emotion/babel-plugin": "^11.13.5", "@emotion/react": "^11.14.0", - "@eslint/js": "^9.30.1", - "@playwright/test": "^1.53.2", - "@sentry/webpack-plugin": "^3.5.0", + "@eslint/js": "^9.31.0", + "@playwright/test": "^1.54.1", + "@sentry/webpack-plugin": "^3.6.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -151,7 +151,7 @@ "jsdom": "^26.0.0", "memfs": "^4.17.2", "os-browserify": "^0.3.0", - "playwright": "^1.53.2", + "playwright": "^1.54.1", "react-error-overlay": "^6.1.0", "redux-logger": "^3.0.6", "source-map-explorer": "^2.5.3", diff --git a/client/src/components/bill-line-search-select/bill-line-search-select.component.jsx b/client/src/components/bill-line-search-select/bill-line-search-select.component.jsx index 0da329d6f..ac97c8e81 100644 --- a/client/src/components/bill-line-search-select/bill-line-search-select.component.jsx +++ b/client/src/components/bill-line-search-select/bill-line-search-select.component.jsx @@ -1,5 +1,5 @@ import { Select } from "antd"; -import React, { forwardRef } from "react"; +import { forwardRef } from "react"; import { useTranslation } from "react-i18next"; import InstanceRenderMgr from "../../utils/instanceRenderMgr"; @@ -43,7 +43,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps }, item.oem_partno ? ` - ${item.oem_partno}` : "" }${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(), label: ( -
+
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${ item.oem_partno ? ` - ${item.oem_partno}` : "" diff --git a/client/src/components/contract-status-select/contract-status-select.component.jsx b/client/src/components/contract-status-select/contract-status-select.component.jsx index 8bd048195..3a4ee82ee 100644 --- a/client/src/components/contract-status-select/contract-status-select.component.jsx +++ b/client/src/components/contract-status-select/contract-status-select.component.jsx @@ -1,10 +1,10 @@ -import React, { forwardRef, useEffect, useState } from "react"; +import { forwardRef, useEffect, useState } from "react"; import { Select } from "antd"; import { useTranslation } from "react-i18next"; const { Option } = Select; -const ContractStatusComponent = ({ value, onChange }, ref) => { +const ContractStatusComponent = ({ value, onChange }) => { const [option, setOption] = useState(value); const { t } = useTranslation(); diff --git a/client/src/components/courtesy-car-fuel-select/courtesy-car-fuel-select.component.jsx b/client/src/components/courtesy-car-fuel-select/courtesy-car-fuel-select.component.jsx index ffe1c1f97..a189e0660 100644 --- a/client/src/components/courtesy-car-fuel-select/courtesy-car-fuel-select.component.jsx +++ b/client/src/components/courtesy-car-fuel-select/courtesy-car-fuel-select.component.jsx @@ -1,5 +1,5 @@ import { Slider } from "antd"; -import React, { forwardRef } from "react"; +import { forwardRef } from "react"; import { useTranslation } from "react-i18next"; const CourtesyCarFuelComponent = (props, ref) => { diff --git a/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx b/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx new file mode 100644 index 000000000..12c2f6569 --- /dev/null +++ b/client/src/components/dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx @@ -0,0 +1,411 @@ +import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; +import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { TimeFormatter } from "../../../utils/DateFormatter"; +import { onlyUnique } from "../../../utils/arrayHelper"; +import dayjs from "../../../utils/day"; +import { alphaSort, dateSort } from "../../../utils/sorters"; +import useLocalStorage from "../../../utils/useLocalStorage"; +import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; +import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../../owner-name-display/owner-name-display.component"; +import DashboardRefreshRequired from "../refresh-required.component"; + +export default function DashboardScheduledDeliveryToday({ data, ...cardProps }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {} + }); + const [isTvModeScheduledDelivery, setIsTvModeScheduledDelivery] = useLocalStorage("isTvModeScheduledDelivery", false); + if (!data) return null; + if (!data.scheduled_delivery_today) return ; + + const scheduledDeliveryToday = data.scheduled_delivery_today.map((item) => { + const joblines_body = item.joblines + ? item.joblines.filter((l) => l.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0) + : 0; + const joblines_ref = item.joblines + ? item.joblines.filter((l) => l.mod_lbr_ty === "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0) + : 0; + return { + ...item, + joblines_body, + joblines_ref + }; + }); + + const tvFontSize = 18; + const tvFontWeight = "bold"; + + const tvColumns = [ + { + title: t("jobs.fields.scheduled_delivery"), + dataIndex: "scheduled_delivery", + key: "scheduled_delivery", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery), + sortOrder: state.sortedInfo.columnKey === "scheduled_delivery" && state.sortedInfo.order, + render: (text, record) => ( + + {record.scheduled_delivery} + + ) + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + e.stopPropagation()}> + + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && } + {record.iouparent && ( + + + + )} + + + + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()}> + + + + + ) : ( + + + + ); + } + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, + `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` + ), + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()}> + + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + + ) : ( + {`${ + record.v_model_yr || "" + } ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("appointments.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, + filters: + (scheduledDeliveryToday && + scheduledDeliveryToday + .map((j) => j.alt_transport) + .filter(onlyUnique) + .map((s) => { + return { + text: s || t("dashboard.errors.atp"), + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.alt_transport), + render: (text, record) => ( + {record.alt_transport} + ) + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + ellipsis: true, + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filters: + (scheduledDeliveryToday && + scheduledDeliveryToday + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || t("dashboard.errors.status"), + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.status), + render: (text, record) => {record.status} + }, + { + title: t("jobs.fields.lab"), + dataIndex: "joblines_body", + key: "joblines_body", + sorter: (a, b) => a.joblines_body - b.joblines_body, + sortOrder: state.sortedInfo.columnKey === "joblines_body" && state.sortedInfo.order, + align: "right", + render: (text, record) => ( + {record.joblines_body.toFixed(1)} + ) + }, + { + title: t("jobs.fields.lar"), + dataIndex: "joblines_ref", + key: "joblines_ref", + sorter: (a, b) => a.joblines_ref - b.joblines_ref, + sortOrder: state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order, + align: "right", + render: (text, record) => ( + {record.joblines_ref.toFixed(1)} + ) + } + ]; + + const columns = [ + { + title: t("jobs.fields.scheduled_delivery"), + dataIndex: "scheduled_delivery", + key: "scheduled_delivery", + ellipsis: true, + sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery), + sortOrder: state.sortedInfo.columnKey === "scheduled_delivery" && state.sortedInfo.order, + render: (text, record) => {record.scheduled_delivery} + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + render: (text, record) => ( + e.stopPropagation()}> + + {record.ro_number || t("general.labels.na")} + {record.production_vars && record.production_vars.alert ? ( + + ) : null} + {record.suspended && } + {record.iouparent && ( + + + + )} + + + ) + }, + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + ellipsis: true, + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)), + sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + render: (text, record) => { + return record.ownerid ? ( + e.stopPropagation()}> + + + ) : ( + + + + ); + } + }, + { + title: t("dashboard.labels.phone"), + dataIndex: "ownr_ph", + key: "ownr_ph", + ellipsis: true, + responsive: ["md"], + render: (text, record) => ( + + + + + + ) + }, + { + title: t("jobs.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea", + ellipsis: true, + responsive: ["md"], + render: (text, record) => {record.ownr_ea} + }, + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + sorter: (a, b) => + alphaSort( + `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`, + `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}` + ), + sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order, + render: (text, record) => { + return record.vehicleid ? ( + e.stopPropagation()}> + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + + ) : ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} + ); + } + }, + { + title: t("jobs.fields.ins_co_nm"), + dataIndex: "ins_co_nm", + key: "ins_co_nm", + ellipsis: true, + responsive: ["md"], + sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), + sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, + filters: + (scheduledDeliveryToday && + scheduledDeliveryToday + .map((j) => j.ins_co_nm) + .filter(onlyUnique) + .map((s) => { + return { + text: s || t("dashboard.errors.insco"), + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.ins_co_nm) + }, + { + title: t("appointments.fields.alt_transport"), + dataIndex: "alt_transport", + key: "alt_transport", + ellipsis: true, + sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order, + filters: + (scheduledDeliveryToday && + scheduledDeliveryToday + .map((j) => j.alt_transport) + .filter(onlyUnique) + .map((s) => { + return { + text: s || t("dashboard.errors.atp"), + value: [s] + }; + }) + .sort((a, b) => alphaSort(a.text, b.text))) || + [], + onFilter: (value, record) => value.includes(record.alt_transport) + } + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + return ( + + {t("general.labels.tvmode")} + setIsTvModeScheduledDelivery(!isTvModeScheduledDelivery)} + defaultChecked={isTvModeScheduledDelivery} + /> + + } + {...cardProps} + > +
+ + + + ); +} + +export const DashboardScheduledDeliveryTodayGql = ` + scheduled_delivery_today: jobs(where: { + date_invoiced: {_is_null: true}, + ro_number: {_is_null: false}, + voided: {_eq: false}, + scheduled_delivery: {_gte: "${dayjs().startOf("day").toISOString()}", + _lte: "${dayjs().endOf("day").toISOString()}"}}) { + alt_transport + clm_no + jobid: id + joblines(where: {removed: {_eq: false}}) { + mod_lb_hrs + mod_lbr_ty + } + ins_co_nm + iouparent + ownerid + ownr_co_nm + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + production_vars + ro_number + scheduled_delivery + status + suspended + v_make_desc + v_model_desc + v_model_yr + v_vin + vehicleid + } +`; diff --git a/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx b/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx index d3caeaca2..05d76983e 100644 --- a/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx +++ b/client/src/components/dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx @@ -1,11 +1,11 @@ import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; -import dayjs from "../../../utils/day"; -import React, { useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { TimeFormatter } from "../../../utils/DateFormatter"; import { onlyUnique } from "../../../utils/arrayHelper"; +import dayjs from "../../../utils/day"; import { alphaSort, dateSort } from "../../../utils/sorters"; import useLocalStorage from "../../../utils/useLocalStorage"; import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; @@ -169,7 +169,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Alt. Transport", + text: s || t("dashboard.errors.atp"), value: [s] }; }) @@ -313,7 +313,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Ins. Co.*", + text: s || t("dashboard.errors.insco"), value: [s] }; }) @@ -335,7 +335,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Alt. Transport", + text: s || t("dashboard.errors.atp"), value: [s] }; }) diff --git a/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx b/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx index f83e2df5d..fdfcc53a5 100644 --- a/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx +++ b/client/src/components/dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx @@ -1,11 +1,11 @@ import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined } from "@ant-design/icons"; import { Card, Space, Switch, Table, Tooltip, Typography } from "antd"; -import dayjs from "../../../utils/day"; -import React, { useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { TimeFormatter } from "../../../utils/DateFormatter"; import { onlyUnique } from "../../../utils/arrayHelper"; +import dayjs from "../../../utils/day"; import { alphaSort, dateSort } from "../../../utils/sorters"; import useLocalStorage from "../../../utils/useLocalStorage"; import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; @@ -138,7 +138,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Alt. Transport*", + text: s || t("dashboard.errors.atp"), value: [s] }; }) @@ -154,7 +154,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) { dataIndex: "status", key: "status", ellipsis: true, - sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), + sorter: (a, b) => alphaSort(a.status, b.status), sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order, filters: (scheduledOutToday && @@ -163,7 +163,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Status*", + text: s || t("dashboard.errors.status"), value: [s] }; }) @@ -306,7 +306,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Ins. Co.*", + text: s || t("dashboard.errors.insco"), value: [s] }; }) @@ -328,7 +328,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) { .filter(onlyUnique) .map((s) => { return { - text: s || "No Alt. Transport*", + text: s || t("dashboard.errors.atp"), value: [s] }; }) diff --git a/client/src/components/dashboard-grid/componentList.js b/client/src/components/dashboard-grid/componentList.js index 015d3509e..5bd866927 100644 --- a/client/src/components/dashboard-grid/componentList.js +++ b/client/src/components/dashboard-grid/componentList.js @@ -1,30 +1,33 @@ import i18next from "i18next"; -import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component.jsx"; -import { - DashboardTotalProductionHours, - DashboardTotalProductionHoursGql -} from "../dashboard-components/total-production-hours/total-production-hours.component.jsx"; -import DashboardProjectedMonthlySales, { - DashboardProjectedMonthlySalesGql -} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx"; -import DashboardMonthlyRevenueGraph, { - DashboardMonthlyRevenueGraphGql -} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx"; -import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx"; -import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component.jsx"; -import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx"; +import JobLifecycleDashboardComponent, { + JobLifecycleDashboardGQL +} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx"; import DashboardMonthlyEmployeeEfficiency, { DashboardMonthlyEmployeeEfficiencyGql } from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component.jsx"; +import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component.jsx"; +import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component.jsx"; +import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component.jsx"; +import DashboardMonthlyRevenueGraph, { + DashboardMonthlyRevenueGraphGql +} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component.jsx"; +import DashboardProjectedMonthlySales, { + DashboardProjectedMonthlySalesGql +} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component.jsx"; +import DashboardScheduledDeliveryToday, { + DashboardScheduledDeliveryTodayGql +} from "../dashboard-components/scheduled-delivery-today/scheduled-delivery-today.component.jsx"; import DashboardScheduledInToday, { DashboardScheduledInTodayGql } from "../dashboard-components/scheduled-in-today/scheduled-in-today.component.jsx"; import DashboardScheduledOutToday, { DashboardScheduledOutTodayGql } from "../dashboard-components/scheduled-out-today/scheduled-out-today.component.jsx"; -import JobLifecycleDashboardComponent, { - JobLifecycleDashboardGQL -} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component.jsx"; +import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component.jsx"; +import { + DashboardTotalProductionHours, + DashboardTotalProductionHoursGql +} from "../dashboard-components/total-production-hours/total-production-hours.component.jsx"; const componentList = { ProductionDollars: { @@ -118,6 +121,15 @@ const componentList = { w: 10, h: 3 }, + ScheduleDeliveryToday: { + label: i18next.t("dashboard.titles.scheduleddeliverytoday"), + component: DashboardScheduledDeliveryToday, + gqlFragment: DashboardScheduledDeliveryTodayGql, + minW: 6, + minH: 2, + w: 10, + h: 3 + }, JobLifecycle: { label: i18next.t("dashboard.titles.joblifecycle"), component: JobLifecycleDashboardComponent, diff --git a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx index 453a935ff..0add659d9 100644 --- a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx +++ b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx @@ -1,6 +1,6 @@ import { DatePicker, Space, TimePicker } from "antd"; import PropTypes from "prop-types"; -import React, { useCallback, useState } from "react"; +import { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -94,7 +94,24 @@ const DateTimePicker = ({ showTime={false} format="MM/DD/YYYY" value={value ? dayjs(value) : null} - onChange={handleChange} + onChange={(dateValue) => { + if (dateValue) { + // When date changes, preserve the existing time if it exists + if (value && dayjs(value).isValid()) { + const existingTime = dayjs(value); + const newDateTime = dayjs(dateValue) + .hour(existingTime.hour()) + .minute(existingTime.minute()) + .second(existingTime.second()); + handleChange(newDateTime); + } else { + // If no existing time, just set the date without time + handleChange(dateValue); + } + } else { + handleChange(dateValue); + } + }} placeholder={t("general.labels.date")} onBlur={handleBlur} disabledDate={handleDisabledDate} @@ -105,13 +122,25 @@ const DateTimePicker = ({ { - handleChange(value); - onBlur(); + onChange={(timeValue) => { + if (timeValue) { + // When time changes, combine it with the existing date + const existingDate = dayjs(value); + const newDateTime = existingDate + .hour(timeValue.hour()) + .minute(timeValue.minute()) + .second(0); + handleChange(newDateTime); + } else { + // If time is cleared, just update with null time but keep date + handleChange(timeValue); + } + if (onBlur) onBlur(); }} placeholder={t("general.labels.time")} {...restProps} diff --git a/client/src/components/header/buildAccountingChildren.jsx b/client/src/components/header/buildAccountingChildren.jsx new file mode 100644 index 000000000..574dbf603 --- /dev/null +++ b/client/src/components/header/buildAccountingChildren.jsx @@ -0,0 +1,190 @@ +import { Link } from "react-router-dom"; +import { FaCreditCard, FaFileInvoiceDollar } from "react-icons/fa"; +import { GiPayMoney, GiPlayerTime } from "react-icons/gi"; +import { BankFilled, ExportOutlined, FieldTimeOutlined } from "@ant-design/icons"; +import LockWrapper from "../../components/lock-wrapper/lock-wrapper.component.jsx"; +import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component"; + +// --- Menu Item Builders --- +const buildAccountingChildren = ({ + t, + bodyshop, + currentUser, + setBillEnterContext, + setPaymentContext, + setCardPaymentContext, + setTimeTicketContext, + ImEXPay, + DmsAp, + Simple_Inventory +}) => [ + { + key: "bills", + id: "header-accounting-bills", + icon: , + label: ( + + + {t("menus.header.bills")} + + + ) + }, + { + key: "enterbills", + id: "header-accounting-enterbills", + icon: , + label: ( + + {t("menus.header.enterbills")} + + ), + onClick: () => + HasFeatureAccess({ featureName: "bills", bodyshop }) && setBillEnterContext({ actions: {}, context: {} }) + }, + ...(Simple_Inventory.treatment === "on" + ? [ + { type: "divider" }, + { + key: "inventory", + id: "header-accounting-inventory", + icon: , + label: {t("menus.header.inventory")} + } + ] + : []), + { type: "divider" }, + { + key: "allpayments", + id: "header-accounting-allpayments", + icon: , + label: {t("menus.header.allpayments")} + }, + { + key: "enterpayments", + id: "header-accounting-enterpayments", + icon: , + label: t("menus.header.enterpayment"), + onClick: () => setPaymentContext({ actions: {}, context: null }) + }, + ...(ImEXPay.treatment === "on" + ? [ + { + key: "entercardpayments", + id: "header-accounting-entercardpayments", + icon: , + label: t("menus.header.entercardpayment"), + onClick: () => setCardPaymentContext({ actions: {}, context: {} }) + } + ] + : []), + { type: "divider" }, + { + key: "timetickets", + id: "header-accounting-timetickets", + icon: , + label: ( + + + {t("menus.header.timetickets")} + + + ) + }, + ...(bodyshop?.md_tasks_presets?.use_approvals + ? [ + { + key: "ttapprovals", + id: "header-accounting-ttapprovals", + icon: , + label: {t("menus.header.ttapprovals")} + } + ] + : []), + { + key: "entertimetickets", + id: "header-accounting-entertimetickets", + icon: , + label: ( + + {t("menus.header.entertimeticket")} + + ), + onClick: () => + HasFeatureAccess({ featureName: "timetickets", bodyshop }) && + setTimeTicketContext({ + actions: {}, + context: { + created_by: currentUser.displayName ? `${currentUser.email} | ${currentUser.displayName}` : currentUser.email + } + }) + }, + { type: "divider" }, + { + key: "accountingexport", + id: "header-accounting-export", + icon: , + label: ( + + {t("menus.header.export")} + + ), + children: [ + { + key: "receivables", + id: "header-accounting-receivables", + label: ( + + + {t("menus.header.accounting-receivables")} + + + ) + }, + ...(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on" + ? [ + { + key: "payables", + id: "header-accounting-payables", + label: ( + + + {t("menus.header.accounting-payables")} + + + ) + } + ] + : []), + ...(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) + ? [ + { + key: "payments", + id: "header-accounting-payments", + label: ( + + + {t("menus.header.accounting-payments")} + + + ) + } + ] + : []), + { type: "divider" }, + { + key: "exportlogs", + id: "header-accounting-exportlogs", + label: ( + + + {t("menus.header.export-logs")} + + + ) + } + ] + } +]; + +export default buildAccountingChildren; diff --git a/client/src/components/header/buildLeftMenuItems.jsx b/client/src/components/header/buildLeftMenuItems.jsx new file mode 100644 index 000000000..166779572 --- /dev/null +++ b/client/src/components/header/buildLeftMenuItems.jsx @@ -0,0 +1,390 @@ +import { Link } from "react-router-dom"; +import { + BarChartOutlined, + CarFilled, + CheckCircleOutlined, + ClockCircleFilled, + DashboardFilled, + DollarCircleFilled, + FileAddFilled, + FileAddOutlined, + FileFilled, + HomeFilled, + ImportOutlined, + LineChartOutlined, + OneToOneOutlined, + PaperClipOutlined, + PhoneOutlined, + PlusCircleOutlined, + QuestionCircleFilled, + ScheduleOutlined, + SettingOutlined, + TeamOutlined, + ToolFilled, + UnorderedListOutlined, + UsergroupAddOutlined, + UserOutlined +} from "@ant-design/icons"; +import { FaCalendarAlt, FaCarCrash, FaTasks } from "react-icons/fa"; +import { BsKanban } from "react-icons/bs"; +import { FiLogOut } from "react-icons/fi"; +import { GiPlayerTime, GiSettingsKnobs } from "react-icons/gi"; +import { RiSurveyLine } from "react-icons/ri"; +import { IoBusinessOutline } from "react-icons/io5"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import LockWrapper from "../../components/lock-wrapper/lock-wrapper.component.jsx"; + +const buildLeftMenuItems = ({ + t, + bodyshop, + recentItems, + setTaskUpsertContext, + setReportCenterContext, + signOutStart, + accountingChildren +}) => { + return [ + { + key: "home", + id: "header-home", + icon: , + label: {t("menus.header.home")} + }, + { + key: "schedule", + id: "header-schedule", + icon: , + label: {t("menus.header.schedule")} + }, + { + key: "jobssubmenu", + id: "header-jobs", + icon: , + label: t("menus.header.jobs"), + children: [ + { + key: "activejobs", + id: "header-active-jobs", + icon: , + label: {t("menus.header.activejobs")} + }, + { + key: "readyjobs", + id: "header-ready-jobs", + icon: , + label: {t("menus.header.readyjobs")} + }, + { + key: "parts-queue", + id: "header-parts-queue", + icon: , + label: {t("menus.header.parts-queue")} + }, + { + key: "availablejobs", + id: "header-jobs-available", + icon: , + label: {t("menus.header.availablejobs")} + }, + { + key: "newjob", + id: "header-new-job", + icon: , + label: {t("menus.header.newjob")} + }, + { type: "divider" }, + { + key: "alljobs", + id: "header-all-jobs", + icon: , + label: {t("menus.header.alljobs")} + }, + { type: "divider" }, + { + key: "productionlist", + id: "header-production-list", + icon: , + label: {t("menus.header.productionlist")} + }, + { + key: "productionboard", + id: "header-production-board", + icon: , + label: ( + + + {t("menus.header.productionboard")} + + + ) + }, + { type: "divider" }, + { + key: "scoreboard", + id: "header-scoreboard", + icon: , + label: ( + + + {t("menus.header.scoreboard")} + + + ) + } + ] + }, + { + key: "customers", + id: "header-customers", + icon: , + label: t("menus.header.customers"), + children: [ + { + key: "owners", + id: "header-owners", + icon: , + label: {t("menus.header.owners")} + }, + { + key: "vehicles", + id: "header-vehicles", + icon: , + label: {t("menus.header.vehicles")} + } + ] + }, + { + key: "ccs", + id: "header-css", + icon: , + label: ( + + {t("menus.header.courtesycars")} + + ), + children: [ + { + key: "courtesycarsall", + id: "header-courtesycars-all", + icon: , + label: ( + + + {t("menus.header.courtesycars-all")} + + + ) + }, + { + key: "contracts", + id: "header-contracts", + icon: , + label: ( + + + {t("menus.header.courtesycars-contracts")} + + + ) + }, + { + key: "newcontract", + id: "header-newcontract", + icon: , + label: ( + + + {t("menus.header.courtesycars-newcontract")} + + + ) + } + ] + }, + ...(accountingChildren.length > 0 + ? [ + { + key: "accounting", + id: "header-accounting", + icon: , + label: t("menus.header.accounting"), + children: accountingChildren + } + ] + : []), + { + key: "phonebook", + id: "header-phonebook", + icon: , + label: {t("menus.header.phonebook")} + }, + { + key: "temporarydocs", + id: "header-temporarydocs", + icon: , + label: ( + + + {t("menus.header.temporarydocs")} + + + ) + }, + { + key: "tasks", + id: "tasks", + icon: , + label: t("menus.header.tasks"), + children: [ + { + key: "createTask", + id: "header-create-task", + icon: , + label: t("menus.header.create_task"), + onClick: () => setTaskUpsertContext({ actions: {}, context: {} }) + }, + { + key: "mytasks", + id: "header-my-tasks", + icon: , + label: {t("menus.header.my_tasks")} + }, + { + key: "all_tasks", + id: "header-all-tasks", + icon: , + label: {t("menus.header.all_tasks")} + } + ] + }, + { + key: "shopsubmenu", + id: "header-shopsubmenu", + icon: , + label: t("menus.header.shop"), + children: [ + { + key: "shop", + id: "header-shop", + icon: , + label: {t("menus.header.shop_config")} + }, + { + key: "dashboard", + id: "header-dashboard", + icon: , + label: ( + + {t("menus.header.dashboard")} + + ) + }, + { + key: "reportcenter", + id: "header-reportcenter", + icon: , + label: t("menus.header.reportcenter"), + onClick: () => setReportCenterContext({ actions: {}, context: {} }) + }, + { + key: "shop-vendors", + id: "header-shop-vendors", + icon: , + label: {t("menus.header.shop_vendors")} + }, + { + key: "shop-csi", + id: "header-shop-csi", + icon: , + label: ( + + + {t("menus.header.shop_csi")} + + + ) + } + ] + }, + { + key: "recent", + id: "header-recent", + icon: , + label: t("menus.header.recent"), + children: recentItems.map((i, idx) => ({ + key: idx, + id: `header-recent-${idx}`, + label: {i.label} + })) + }, + { + key: "user", + id: "header-user", + icon: , + label: t("menus.currentuser.profile"), + children: [ + { + key: "signout", + id: "header-signout", + icon: , + danger: true, + label: t("user.actions.signout"), + onClick: () => signOutStart() + }, + { + key: "help", + id: "header-help", + icon: , + label: t("menus.header.help"), + onClick: () => window.open("https://help.imex.online/", "_blank") + }, + { + key: "remoteassist", + id: "header-remote-assist", + icon: , + label: t("menus.header.remoteassist"), + children: [ + ...(InstanceRenderManager({ imex: true, rome: false }) + ? [ + { + key: "rescue", + id: "header-rescue", + icon: , + label: t("menus.header.rescueme"), + onClick: () => window.open("https://imexrescue.com/", "_blank") + } + ] + : []), + { + key: "rescue-zoho", + id: "header-rescue-zoho", + icon: , + label: t("menus.header.rescuemezoho"), + onClick: () => window.open("https://join.zoho.com/", "_blank") + } + ] + }, + { + key: "shiftclock", + id: "header-shiftclock", + icon: , + label: ( + + + {t("menus.header.shiftclock")} + + + ) + }, + { + key: "profile", + id: "header-profile", + icon: , + label: {t("menus.currentuser.profile")} + } + ] + } + ]; +}; + +export default buildLeftMenuItems; diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 826edea54..3312bf9f4 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -1,61 +1,29 @@ -import { - BankFilled, - BarChartOutlined, - BellFilled, - CarFilled, - CheckCircleOutlined, - ClockCircleFilled, - DashboardFilled, - DollarCircleFilled, - ExportOutlined, - FieldTimeOutlined, - FileAddFilled, - FileAddOutlined, - FileFilled, - HomeFilled, - ImportOutlined, - LineChartOutlined, - OneToOneOutlined, - PaperClipOutlined, - PhoneOutlined, - PlusCircleOutlined, - QuestionCircleFilled, - ScheduleOutlined, - SettingOutlined, - TeamOutlined, - ToolFilled, - UnorderedListOutlined, - UsergroupAddOutlined, - UserOutlined -} from "@ant-design/icons"; +// noinspection RegExpAnonymousGroup + +import { BellFilled } from "@ant-design/icons"; import { useQuery } from "@apollo/client"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { Badge, Layout, Menu, Spin } from "antd"; -import { useEffect, useRef, useState } from "react"; +import { Badge, Layout, Menu, Spin, Tooltip } from "antd"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { BsKanban } from "react-icons/bs"; -import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa"; -import { FiLogOut } from "react-icons/fi"; -import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi"; -import { IoBusinessOutline } from "react-icons/io5"; -import { RiSurveyLine } from "react-icons/ri"; +import { FaTasks } from "react-icons/fa"; import { connect } from "react-redux"; -import { Link } from "react-router-dom"; import { createStructuredSelector } from "reselect"; -import { useSocket } from "../../contexts/SocketIO/useSocket.js"; +import { TASKS_CENTER_POLL_INTERVAL, useSocket } from "../../contexts/SocketIO/useSocket.js"; import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js"; +import { QUERY_MY_TASKS_COUNT } from "../../graphql/tasks.queries.js"; import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors"; import { setModalContext } from "../../redux/modals/modals.actions"; import { signOutStart } from "../../redux/user/user.actions"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import day from "../../utils/day.js"; -import InstanceRenderManager from "../../utils/instanceRenderMgr"; import { useIsEmployee } from "../../utils/useIsEmployee.js"; -import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; -import LockWrapper from "../lock-wrapper/lock-wrapper.component"; import NotificationCenterContainer from "../notification-center/notification-center.container.jsx"; +import TaskCenterContainer from "../task-center/task-center.container.jsx"; +import buildAccountingChildren from "./buildAccountingChildren.jsx"; +import buildLeftMenuItems from "./buildLeftMenuItems.jsx"; -// Redux mappings +// --- Redux mappings --- const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, recentItems: selectRecentItems, @@ -73,36 +41,8 @@ const mapDispatchToProps = (dispatch) => ({ setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" })) }); -function Header({ - handleMenuClick, - currentUser, - bodyshop, - selectedHeader, - signOutStart, - setBillEnterContext, - setTimeTicketContext, - setPaymentContext, - setReportCenterContext, - recentItems, - setCardPaymentContext, - setTaskUpsertContext -}) { - const { - treatments: { ImEXPay, DmsAp, Simple_Inventory } - } = useSplitTreatments({ - attributes: {}, - names: ["ImEXPay", "DmsAp", "Simple_Inventory"], - splitKey: bodyshop && bodyshop.imexshopid - }); - - const { t } = useTranslation(); - const { isConnected, scenarioNotificationsOn } = useSocket(); - const [notificationVisible, setNotificationVisible] = useState(false); - const baseTitleRef = useRef(document.title || ""); - const lastSetTitleRef = useRef(""); - const userAssociationId = bodyshop?.associations?.[0]?.id; - const isEmployee = useIsEmployee(bodyshop, currentUser); - +// --- Utility Hooks --- +function useUnreadNotifications(userAssociationId, isConnected, scenarioNotificationsOn) { const { data: unreadData, refetch: refetchUnread, @@ -128,633 +68,286 @@ function Header({ } }, [isConnected, unreadLoading, refetchUnread, userAssociationId]); - // Keep The unread count in the title. + return { unreadCount, unreadLoading }; +} + +function useIncompleteTaskCount(assignedToId, bodyshopId, isEmployee, isConnected) { + const { data: taskCountData, loading: taskCountLoading } = useQuery(QUERY_MY_TASKS_COUNT, { + variables: { assigned_to: assignedToId, bodyshopid: bodyshopId }, + skip: !assignedToId || !bodyshopId || !isEmployee, + fetchPolicy: "network-only", + pollInterval: isConnected ? 0 : TASKS_CENTER_POLL_INTERVAL + }); + + const incompleteTaskCount = taskCountData?.tasks_aggregate?.aggregate?.count ?? 0; + return { incompleteTaskCount, taskCountLoading }; +} + +// --- Main Component --- +function Header(props) { + const { + handleMenuClick, + currentUser, + bodyshop, + selectedHeader, + signOutStart, + setBillEnterContext, + setTimeTicketContext, + setPaymentContext, + setReportCenterContext, + recentItems, + setCardPaymentContext, + setTaskUpsertContext + } = props; + + // Feature flags + const { + treatments: { ImEXPay, DmsAp, Simple_Inventory } + } = useSplitTreatments({ + attributes: {}, + names: ["ImEXPay", "DmsAp", "Simple_Inventory"], + splitKey: bodyshop && bodyshop.imexshopid + }); + + // Contexts and hooks + const { t } = useTranslation(); + const { isConnected, scenarioNotificationsOn } = useSocket(); + const [notificationVisible, setNotificationVisible] = useState(false); + const [taskCenterVisible, setTaskCenterVisible] = useState(false); + const baseTitleRef = useRef(document.title || ""); + const lastSetTitleRef = useRef(""); + const taskCenterRef = useRef(null); + const notificationRef = useRef(null); + const userAssociationId = bodyshop?.associations?.[0]?.id; + const isEmployee = useIsEmployee(bodyshop, currentUser); + + // Data hooks + const { unreadCount, unreadLoading } = useUnreadNotifications( + userAssociationId, + isConnected, + scenarioNotificationsOn + ); + const assignedToId = bodyshop?.employees?.find((e) => e.user_email === currentUser.email)?.id; + const { incompleteTaskCount, taskCountLoading } = useIncompleteTaskCount( + assignedToId, + bodyshop?.id, + isEmployee, + isConnected + ); + + // --- Effects --- + + // Update document title with unread count useEffect(() => { const updateTitle = () => { const currentTitle = document.title; - // Check if the current title differs from what we last set if (currentTitle !== lastSetTitleRef.current) { - // Extract base title by removing any unread count prefix const baseTitleMatch = currentTitle.match(/^\(\d+\)\s*(.*)$/); baseTitleRef.current = baseTitleMatch ? baseTitleMatch[1] : currentTitle; } - - // Apply unread count to the base title const newTitle = unreadCount > 0 ? `(${unreadCount}) ${baseTitleRef.current}` : baseTitleRef.current; - - // Only update if the title has changed to avoid unnecessary DOM writes if (document.title !== newTitle) { document.title = newTitle; - lastSetTitleRef.current = newTitle; // Store what we set + lastSetTitleRef.current = newTitle; + } + }; + updateTitle(); + const interval = setInterval(updateTitle, 100); + return () => { + clearInterval(interval); + document.title = baseTitleRef.current; + }; + }, [unreadCount]); + + // Handle outside clicks for popovers + useEffect(() => { + const handleClickOutside = (event) => { + const isNotificationClick = event.target.closest("#header-notifications"); + const isTaskCenterClick = event.target.closest("#header-taskcenter"); + + if (isNotificationClick && scenarioNotificationsOn) { + setTaskCenterVisible(false); // Close task center + return; + } + + if (isTaskCenterClick) { + setNotificationVisible(scenarioNotificationsOn ? false : notificationVisible); // Close notification center if enabled + return; + } + + if (taskCenterVisible && taskCenterRef.current && !taskCenterRef.current.contains(event.target)) { + setTaskCenterVisible(false); + } + + if ( + scenarioNotificationsOn && + notificationVisible && + notificationRef.current && + !notificationRef.current.contains(event.target) + ) { + setNotificationVisible(false); } }; - // Initial update - updateTitle(); + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [taskCenterVisible, notificationVisible, scenarioNotificationsOn]); - // Poll every 100ms to catch child component changes - const interval = setInterval(updateTitle, 100); + // --- Event Handlers --- + const handleTaskCenterClick = useCallback( + (e) => { + setTaskCenterVisible((prev) => { + if (prev) return false; + return true; + }); + if (handleMenuClick) handleMenuClick(e); + }, + [handleMenuClick] + ); - // Cleanup - return () => { - clearInterval(interval); - document.title = baseTitleRef.current; // Reset to base title on unmount - }; - }, [unreadCount]); // Re-run when unreadCount changes + const handleNotificationClick = useCallback( + (e) => { + setNotificationVisible((prev) => { + if (prev) return false; + return true; + }); + if (handleMenuClick) handleMenuClick(e); + }, + [handleMenuClick] + ); - const handleNotificationClick = (e) => { - setNotificationVisible(!notificationVisible); - if (handleMenuClick) handleMenuClick(e); - }; + // --- Menu Items --- - const accountingChildren = [ - { - key: "bills", - id: "header-accounting-bills", - icon: , - label: ( - - - {t("menus.header.bills")} - - - ) - }, - { - key: "enterbills", - id: "header-accounting-enterbills", - icon: , - label: ( - - {t("menus.header.enterbills")} - - ), - onClick: () => - HasFeatureAccess({ featureName: "bills", bodyshop }) && - setBillEnterContext({ - actions: {}, - context: {} - }) - }, - ...(Simple_Inventory.treatment === "on" - ? [ - { type: "divider" }, - { - key: "inventory", - id: "header-accounting-inventory", - icon: , - label: {t("menus.header.inventory")} - } - ] - : []), - { type: "divider" }, - { - key: "allpayments", - id: "header-accounting-allpayments", - icon: , - label: {t("menus.header.allpayments")} - }, - { - key: "enterpayments", - id: "header-accounting-enterpayments", - icon: , - label: t("menus.header.enterpayment"), - onClick: () => - setPaymentContext({ - actions: {}, - context: null - }) - }, - ...(ImEXPay.treatment === "on" - ? [ - { - key: "entercardpayments", - id: "header-accounting-entercardpayments", - icon: , - label: t("menus.header.entercardpayment"), - onClick: () => setCardPaymentContext({ actions: {}, context: {} }) - } - ] - : []), - { type: "divider" }, - { - key: "timetickets", - id: "header-accounting-timetickets", - icon: , - label: ( - - - {t("menus.header.timetickets")} - - - ) - }, - ...(bodyshop?.md_tasks_presets?.use_approvals - ? [ - { - key: "ttapprovals", - id: "header-accounting-ttapprovals", - icon: , - label: {t("menus.header.ttapprovals")} - } - ] - : []), - { - key: "entertimetickets", - id: "header-accounting-entertimetickets", - icon: , - label: ( - - {t("menus.header.entertimeticket")} - - ), - onClick: () => - HasFeatureAccess({ featureName: "timetickets", bodyshop }) && - setTimeTicketContext({ - actions: {}, - context: { - created_by: currentUser.displayName - ? `${currentUser.email} | ${currentUser.displayName}` - : currentUser.email - } - }) - }, - { type: "divider" }, - { - key: "accountingexport", - id: "header-accounting-export", - icon: , - label: ( - - {t("menus.header.export")} - - ), - children: [ - { - key: "receivables", - id: "header-accounting-receivables", - label: ( - - - {t("menus.header.accounting-receivables")} - - - ) - }, - ...(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || - DmsAp.treatment === "on" - ? [ - { - key: "payables", - id: "header-accounting-payables", - label: ( - - - {t("menus.header.accounting-payables")} - - - ) - } - ] - : []), - ...(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) - ? [ - { - key: "payments", - id: "header-accounting-payments", - label: ( - - - {t("menus.header.accounting-payments")} - - - ) - } - ] - : []), - { type: "divider" }, - { - key: "exportlogs", - id: "header-accounting-exportlogs", - label: ( - - - {t("menus.header.export-logs")} - - - ) - } - ] + // built externally to keep the component clean, but on this level to prevent unnecessary re-renders + const accountingChildren = useMemo( + () => + buildAccountingChildren({ + t, + bodyshop, + currentUser, + setBillEnterContext, + setPaymentContext, + setCardPaymentContext, + setTimeTicketContext, + ImEXPay, + DmsAp, + Simple_Inventory + }), + [ + t, + bodyshop, + currentUser, + setBillEnterContext, + setPaymentContext, + setCardPaymentContext, + setTimeTicketContext, + ImEXPay, + DmsAp, + Simple_Inventory + ] + ); + + // Built externally to keep the component clean + const leftMenuItems = useMemo( + () => + buildLeftMenuItems({ + t, + bodyshop, + recentItems, + setTaskUpsertContext, + setReportCenterContext, + signOutStart, + accountingChildren + }), + [t, bodyshop, recentItems, setTaskUpsertContext, setReportCenterContext, signOutStart, accountingChildren] + ); + + const rightMenuItems = useMemo(() => { + const items = []; + if (scenarioNotificationsOn) { + items.push({ + key: "notifications", + id: "header-notifications", + icon: unreadLoading ? ( + + ) : ( + + + + ), + onClick: handleNotificationClick + }); } - ]; - - // Left menu items (includes original navigation items) - const leftMenuItems = [ - { - key: "home", - id: "header-home", - icon: , - label: {t("menus.header.home")} - }, - { - key: "schedule", - id: "header-schedule", - icon: , - label: {t("menus.header.schedule")} - }, - { - key: "jobssubmenu", - id: "header-jobs", - icon: , - label: t("menus.header.jobs"), - children: [ - { - key: "activejobs", - id: "header-active-jobs", - icon: , - label: {t("menus.header.activejobs")} - }, - { - key: "readyjobs", - id: "header-ready-jobs", - icon: , - label: {t("menus.header.readyjobs")} - }, - { - key: "parts-queue", - id: "header-parts-queue", - icon: , - label: {t("menus.header.parts-queue")} - }, - { - key: "availablejobs", - id: "header-jobs-available", - icon: , - label: {t("menus.header.availablejobs")} - }, - { - key: "newjob", - id: "header-new-job", - icon: , - label: {t("menus.header.newjob")} - }, - { type: "divider" }, - { - key: "alljobs", - id: "header-all-jobs", - icon: , - label: {t("menus.header.alljobs")} - }, - { type: "divider" }, - { - key: "productionlist", - id: "header-production-list", - icon: , - label: {t("menus.header.productionlist")} - }, - { - key: "productionboard", - id: "header-production-board", - icon: , - label: ( - - - {t("menus.header.productionboard")} - - - ) - }, - { type: "divider" }, - { - key: "scoreboard", - id: "header-scoreboard", - icon: , - label: ( - - - {t("menus.header.scoreboard")} - - - ) - } - ] - }, - { - key: "customers", - id: "header-customers", - icon: , - label: t("menus.header.customers"), - children: [ - { - key: "owners", - id: "header-owners", - icon: , - label: {t("menus.header.owners")} - }, - { - key: "vehicles", - id: "header-vehicles", - icon: , - label: {t("menus.header.vehicles")} - } - ] - }, - { - key: "ccs", - id: "header-css", - icon: , - label: ( - - {t("menus.header.courtesycars")} - + items.push({ + key: "taskcenter", + id: "header-taskcenter", + icon: taskCountLoading ? ( + + ) : ( + 0 ? incompleteTaskCount : 0} showZero={false}> + + + + ), - children: [ - { - key: "courtesycarsall", - id: "header-courtesycars-all", - icon: , - label: ( - - - {t("menus.header.courtesycars-all")} - - - ) - }, - { - key: "contracts", - id: "header-contracts", - icon: , - label: ( - - - {t("menus.header.courtesycars-contracts")} - - - ) - }, - { - key: "newcontract", - id: "header-newcontract", - icon: , - label: ( - - - {t("menus.header.courtesycars-newcontract")} - - - ) - } - ] - }, - ...(accountingChildren.length > 0 - ? [ - { - key: "accounting", - id: "header-accounting", - icon: , - label: t("menus.header.accounting"), - children: accountingChildren - } - ] - : []), - { - key: "phonebook", - id: "header-phonebook", - icon: , - label: {t("menus.header.phonebook")} - }, - { - key: "temporarydocs", - id: "header-temporarydocs", - icon: , - label: ( - - - {t("menus.header.temporarydocs")} - - - ) - }, - { - key: "tasks", - id: "tasks", - icon: , - label: t("menus.header.tasks"), - children: [ - { - key: "createTask", - id: "header-create-task", - icon: , - label: t("menus.header.create_task"), - onClick: () => setTaskUpsertContext({ actions: {}, context: {} }) - }, - { - key: "mytasks", - id: "header-my-tasks", - icon: , - label: {t("menus.header.my_tasks")} - }, - { - key: "all_tasks", - id: "header-all-tasks", - icon: , - label: {t("menus.header.all_tasks")} - } - ] - }, - { - key: "shopsubmenu", - id: "header-shopsubmenu", - icon: , - label: t("menus.header.shop"), - children: [ - { - key: "shop", - id: "header-shop", - icon: , - label: {t("menus.header.shop_config")} - }, - { - key: "dashboard", - id: "header-dashboard", - icon: , - label: ( - - {t("menus.header.dashboard")} - - ) - }, - { - key: "reportcenter", - id: "header-reportcenter", - icon: , - label: t("menus.header.reportcenter"), - onClick: () => setReportCenterContext({ actions: {}, context: {} }) - }, - { - key: "shop-vendors", - id: "header-shop-vendors", - icon: , - label: {t("menus.header.shop_vendors")} - }, - { - key: "shop-csi", - id: "header-shop-csi", - icon: , - label: ( - - - {t("menus.header.shop_csi")} - - - ) - } - ] - }, - { - key: "recent", - id: "header-recent", - icon: , - label: t("menus.header.recent"), - children: recentItems.map((i, idx) => ({ - key: idx, - id: `header-recent-${idx}`, - label: {i.label} - })) - }, - { - key: "user", - id: "header-user", - icon: , - label: t("menus.currentuser.profile"), - children: [ - { - key: "signout", - id: "header-signout", - icon: , - danger: true, - label: t("user.actions.signout"), - onClick: () => signOutStart() - }, - { - key: "help", - id: "header-help", - icon: , - label: t("menus.header.help"), - onClick: () => window.open("https://help.imex.online/", "_blank") - }, - { - key: "remoteassist", - id: "header-remote-assist", - icon: , - label: t("menus.header.remoteassist"), - children: [ - ...(InstanceRenderManager({ imex: true, rome: false }) - ? [ - { - key: "rescue", - id: "header-rescue", - icon: , - label: t("menus.header.rescueme"), - onClick: () => window.open("https://imexrescue.com/", "_blank") - } - ] - : []), - { - key: "rescue-zoho", - id: "header-rescue-zoho", - icon: , - label: t("menus.header.rescuemezoho"), - onClick: () => window.open("https://join.zoho.com/", "_blank") - } - ] - }, - { - key: "shiftclock", - id: "header-shiftclock", - icon: , - label: ( - - - {t("menus.header.shiftclock")} - - - ) - }, - { - key: "profile", - id: "header-profile", - icon: , - label: {t("menus.currentuser.profile")} - } - ] - } - ]; - - // Notifications item (always on the right) - const notificationItem = scenarioNotificationsOn - ? [ - { - key: "notifications", - id: "header-notifications", - icon: unreadLoading ? ( - - ) : ( - - - - ), - onClick: handleNotificationClick - } - ] - : []; + onClick: handleTaskCenterClick + }); + return items; + }, [ + scenarioNotificationsOn, + unreadLoading, + unreadCount, + taskCountLoading, + incompleteTaskCount, + isEmployee, + handleNotificationClick, + handleTaskCenterClick, + t + ]); + // --- Render --- return ( -
- - {scenarioNotificationsOn && ( +
+
- )} +
+
+ +
{scenarioNotificationsOn && ( - setNotificationVisible(false)} - unreadCount={unreadCount} - /> +
+ setNotificationVisible(false)} + unreadCount={unreadCount} + /> +
)} +
+ setTaskCenterVisible(false)} + /> +
); } diff --git a/client/src/components/job-at-change/schedule-event.component.jsx b/client/src/components/job-at-change/schedule-event.component.jsx index 6438308da..9bdacfe26 100644 --- a/client/src/components/job-at-change/schedule-event.component.jsx +++ b/client/src/components/job-at-change/schedule-event.component.jsx @@ -385,7 +385,9 @@ export function ScheduleEventComponent({ previousEvent: event.id, color: event.color, alt_transport: event.job && event.job.alt_transport, - note: event.note + note: event.note, + scheduled_in: event.job && event.job.scheduled_in, + scheduled_completion: event.job && event.job.scheduled_completion } }); }} diff --git a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx index 16d4c9f6b..9a5f0b6b2 100644 --- a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx +++ b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx @@ -5,9 +5,9 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import dayjs from "../../utils/day"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import FormRow from "../layout-form-row/layout-form-row.component"; -import dayjs from "../../utils/day"; const mapStateToProps = createStructuredSelector({ jobRO: selectJobReadOnly, @@ -43,14 +43,14 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) { @@ -63,7 +63,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) { - + @@ -110,16 +110,16 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) { - + - + - + - +
diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index ec1ec32b6..2dc1c18e6 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -673,7 +673,9 @@ export function JobsDetailHeaderActions({ context: { jobId: job.id, job: job, - alt_transport: job.alt_transport + alt_transport: job.alt_transport, + scheduled_in: job.scheduled_in, + scheduled_completion: job.scheduled_completion } }); } @@ -1090,11 +1092,7 @@ export function JobsDetailHeaderActions({ {t("menus.jobsactions.deletejob")} ) : ( - e.stopPropagation()} - showCancel={false} - > + e.stopPropagation()} showCancel={false}> {t("menus.jobsactions.deletejob")} ) diff --git a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx index 50716982b..35b4a785c 100644 --- a/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx +++ b/client/src/components/jobs-documents-imgproxy-gallery/jobs-document-imgproxy-gallery.download.component.jsx @@ -11,7 +11,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); @@ -25,7 +25,7 @@ const mapDispatchToProps = (dispatch) => ({ export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyDownloadButton); -export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, identifier, jobId }) { +export function JobsDocumentsImgproxyDownloadButton({ galleryImages, identifier, jobId }) { const { t } = useTranslation(); const [download, setDownload] = useState(null); const [loading, setLoading] = useState(false); diff --git a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx index fe3799c49..a515f682f 100644 --- a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx +++ b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx @@ -3,7 +3,7 @@ import { Button, Card, Input, Space, Table, Typography } from "antd"; import axios from "axios"; import _ from "lodash"; import queryString from "query-string"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useLocation, useNavigate } from "react-router-dom"; @@ -20,7 +20,7 @@ const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); @@ -203,6 +203,8 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { return ( {search.search && ( @@ -256,6 +258,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) { rowKey="id" dataSource={search?.search ? openSearchResults : jobs} onChange={handleTableChange} + id="all-jobs-list-table" /> ); diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx index f7164f748..aaf56ba79 100644 --- a/client/src/components/jobs-list/jobs-list.component.jsx +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -2,7 +2,7 @@ import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOut import { useQuery } from "@apollo/client"; import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; import queryString from "query-string"; -import React, { useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useLocation, useNavigate } from "react-router-dom"; @@ -22,7 +22,7 @@ const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({}); +const mapDispatchToProps = () => ({}); export function JobsList({ bodyshop }) { const searchParams = queryString.parse(useLocation().search); @@ -342,13 +342,14 @@ export function JobsList({ bodyshop }) { type: "radio" }} onChange={handleTableChange} - onRow={(record, rowIndex) => { + onRow={(record) => { return { - onClick: (event) => { + onClick: () => { handleOnRowClick(record); } }; }} + id="active-jobs-list-table" /> ); diff --git a/client/src/components/notification-center/notification-center.component.jsx b/client/src/components/notification-center/notification-center.component.jsx index 35dac2af6..a29e675fd 100644 --- a/client/src/components/notification-center/notification-center.component.jsx +++ b/client/src/components/notification-center/notification-center.component.jsx @@ -131,4 +131,6 @@ const NotificationCenterComponent = forwardRef( } ); +NotificationCenterComponent.displayName = "NotificationCenterComponent"; + export default NotificationCenterComponent; diff --git a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx index b00ff0c36..faea7a23d 100644 --- a/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx +++ b/client/src/components/schedule-calendar-wrapper/schedule-calendar-header.component.jsx @@ -1,9 +1,9 @@ import Icon from "@ant-design/icons"; import { Card, Popover, Space } from "antd"; -import _ from "lodash"; +import { groupBy } from "lodash"; import dayjs from "../../utils/day"; -import React, { useMemo } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { MdFileDownload, MdFileUpload } from "react-icons/md"; import { connect } from "react-redux"; @@ -26,21 +26,12 @@ const mapStateToProps = createStructuredSelector({ calculating: selectScheduleLoadCalculating }); -const mapDispatchToProps = (dispatch) => ({}); +const mapDispatchToProps = () => ({}); -export function ScheduleCalendarHeaderComponent({ - bodyshop, - label, - refetch, - date, - load, - calculating, - events, - ...otherProps -}) { +export function ScheduleCalendarHeaderComponent({ bodyshop, label, refetch, date, load, calculating, events }) { const ATSToday = useMemo(() => { if (!events) return []; - return _.groupBy( + return groupBy( events.filter((e) => !e.vacation && e.isintake && dayjs(date).isSame(dayjs(e.start), "day")), "job.alt_transport" ); @@ -155,7 +146,11 @@ export function ScheduleCalendarHeaderComponent({ - {`${(loadData.allHoursInBody || 0) && loadData.allHoursInBody.toFixed(1)}/${(loadData.allHoursInRefinish || 0) && loadData.allHoursInRefinish.toFixed(1)}/${(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}`} + + {`${(loadData.allHoursInBody || 0) && loadData.allHoursInBody.toFixed(1)}/${ + (loadData.allHoursInRefinish || 0) && loadData.allHoursInRefinish.toFixed(1) + }/${(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}`} + diff --git a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx index 23ff4340d..18c5a3cec 100644 --- a/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx +++ b/client/src/components/schedule-job-modal/schedule-job-modal.container.jsx @@ -1,10 +1,10 @@ import { useMutation, useQuery } from "@apollo/client"; import { Form, Modal } from "antd"; -import dayjs from "../../utils/day"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { logImEXEvent } from "../../firebase/firebase.utils"; import { CANCEL_APPOINTMENT_BY_ID, @@ -19,9 +19,9 @@ import { selectSchedule } from "../../redux/modals/modals.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import { DateTimeFormat } from "../../utils/DateFormatter"; +import dayjs from "../../utils/day"; import { TemplateList } from "../../utils/TemplateConstants"; import ScheduleJobModalComponent from "./schedule-job-modal.component"; -import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -72,7 +72,7 @@ export function ScheduleJobModalContainer({ variables: { jobid: jobId }, fetchPolicy: "network-only", nextFetchPolicy: "network-only", - skip: !open || !!!jobId + skip: !open || !jobId }); useEffect(() => { @@ -93,12 +93,12 @@ export function ScheduleJobModalContainer({ logImEXEvent("schedule_new_appointment"); setLoading(true); - if (!!previousEvent) { + if (previousEvent) { const cancelAppt = await cancelAppointment({ variables: { appid: previousEvent } }); - if (!!cancelAppt.errors) { + if (cancelAppt.errors) { notification["error"]({ message: t("appointments.errors.canceling", { message: JSON.stringify(cancelAppt.errors) @@ -146,7 +146,7 @@ export function ScheduleJobModalContainer({ }); } - if (!!appt.errors) { + if (appt.errors) { notification["error"]({ message: t("appointments.errors.saving", { message: JSON.stringify(appt.errors) @@ -172,7 +172,7 @@ export function ScheduleJobModalContainer({ } }); - if (!!jobUpdate.errors) { + if (jobUpdate.errors) { notification["error"]({ message: t("appointments.errors.saving", { message: JSON.stringify(jobUpdate.errors) @@ -222,9 +222,9 @@ export function ScheduleJobModalContainer({ initialValues={{ notifyCustomer: !!(job && job.ownr_ea), email: (job && job.ownr_ea) || "", - start: null, // smartDates: [], - scheduled_completion: null, + start: context.scheduled_in, + scheduled_completion: context.scheduled_completion , color: context.color, alt_transport: context.alt_transport, note: context.note diff --git a/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx b/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx index 7b613aaed..2ab5ba7a3 100644 --- a/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx +++ b/client/src/components/scoreboard-chart/scoreboard-chart.component.jsx @@ -1,8 +1,7 @@ import { Card } from "antd"; import Dinero from "dinero.js"; -import _ from "lodash"; +import { round } from "lodash"; import dayjs from "../../utils/day"; -import React from "react"; import { connect } from "react-redux"; import { Area, @@ -29,7 +28,7 @@ const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardChart); @@ -40,7 +39,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) { const data = listOfBusDays.reduce((acc, val) => { //Sum up the current day. let dayhrs; - if (!!sbEntriesByDate[val]) { + if (sbEntriesByDate[val]) { dayhrs = sbEntriesByDate[val].reduce( (dayAcc, dayVal) => { return { @@ -61,9 +60,9 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) { const theValue = { date: dayjs(val).format("D ddd"), - paintHrs: _.round(dayhrs.painthrs, 1), - bodyHrs: _.round(dayhrs.bodyhrs, 1), - accTargetHrs: _.round( + paintHrs: round(dayhrs.painthrs, 1), + bodyHrs: round(dayhrs.bodyhrs, 1), + accTargetHrs: round( Utils.AsOfDateTargetHours( bodyshop.scoreboard_target.dailyBodyTarget + bodyshop.scoreboard_target.dailyPaintTarget, val @@ -72,14 +71,14 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) { bodyshop.scoreboard_target.dailyPaintTarget, 1 ), - accHrs: _.round( + accHrs: round( acc.length > 0 ? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs : dayhrs.painthrs + dayhrs.bodyhrs, 1 ), - sales: _.round(dayhrs.sales, 2), - accSales: _.round(acc.length > 0 ? acc[acc.length - 1].accSales + dayhrs.sales : dayhrs.sales, 2) + sales: round(dayhrs.sales, 2), + accSales: round(acc.length > 0 ? acc[acc.length - 1].accSales + dayhrs.sales : dayhrs.sales, 2) }; return [...acc, theValue]; diff --git a/client/src/components/scoreboard-display/scoreboard-display.component.jsx b/client/src/components/scoreboard-display/scoreboard-display.component.jsx index 3d8a2f98a..053aef778 100644 --- a/client/src/components/scoreboard-display/scoreboard-display.component.jsx +++ b/client/src/components/scoreboard-display/scoreboard-display.component.jsx @@ -1,23 +1,25 @@ -import { Col, Row } from "antd"; -import { useEffect } from "react"; +import { Col, Row, Spin } from "antd"; +import { useEffect, useState } from "react"; import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component"; import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component"; import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component"; - import { useApolloClient, useQuery } from "@apollo/client"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import dayjs from "../../utils/day"; +import { + clearHolidays, + clearWorkingWeekdays, + setHolidays, + setWorkingWeekdays +} from "../scoreboard-targets-table/scoreboard-targets-table.util"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); +const mapDispatchToProps = () => ({}); export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardDisplayComponent); export function ScoreboardDisplayComponent({ bodyshop }) { @@ -32,7 +34,6 @@ export function ScoreboardDisplayComponent({ bodyshop }) { const { data } = scoreboardSubscription; const client = useApolloClient(); const scoreBoardlist = data?.scoreboard || []; - const sbEntriesByDate = {}; scoreBoardlist.forEach((i) => { @@ -43,35 +44,52 @@ export function ScoreboardDisplayComponent({ bodyshop }) { sbEntriesByDate[entryDate].push(i); }); - useEffect(() => { - //Update the locals. - async function setDayJSSettings() { - let appointments; + const [loading, setLoading] = useState(true); // Loading state - if (!bodyshop.scoreboard_target.ignoreblockeddays) { - const { data } = await client.query({ - query: GET_BLOCKED_DAYS, - variables: { - start: dayjs().startOf("month"), - end: dayjs().endOf("month") - } - }); - appointments = data.appointments; - } - dayjs.updateLocale(dayjs.locale(), { - workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays), - ...(appointments?.length - ? { - holidays: appointments.map((h) => dayjs(h.start).format("MM-DD-YYYY")) + useEffect(() => { + async function setDayJSSettings() { + try { + let appointments; + + if (!bodyshop.scoreboard_target.ignoreblockeddays) { + const { data } = await client.query({ + query: GET_BLOCKED_DAYS, + variables: { + start: dayjs().startOf("month"), + end: dayjs().endOf("month") } - : {}), - holidayFormat: "MM-DD-YYYY" - }); + }); + appointments = data.appointments; + } + + const holidays = appointments ? appointments.map((h) => dayjs(h.start).format("MM-DD-YYYY")) : []; + const workingWeekdays = translateSettingsToWorkingDays(bodyshop.workingdays); + + // Set holidays and working weekdays + setHolidays(holidays); + setWorkingWeekdays(workingWeekdays); + } finally { + setLoading(false); // Set loading to false after processing + } } setDayJSSettings(); + + // Cleanup on unmount + return () => { + clearHolidays(); + clearWorkingWeekdays(); + }; }, [client, bodyshop]); + if (loading) { + return ( + + + + ); + } + return (
@@ -89,27 +107,12 @@ export function ScoreboardDisplayComponent({ bodyshop }) { function translateSettingsToWorkingDays(workingdays) { const days = []; - - if (workingdays.monday) { - days.push(1); - } - if (workingdays.tuesday) { - days.push(2); - } - if (workingdays.wednesday) { - days.push(3); - } - if (workingdays.thursday) { - days.push(4); - } - if (workingdays.friday) { - days.push(5); - } - if (workingdays.saturday) { - days.push(6); - } - if (workingdays.sunday) { - days.push(0); - } + if (workingdays.monday) days.push(1); + if (workingdays.tuesday) days.push(2); + if (workingdays.wednesday) days.push(3); + if (workingdays.thursday) days.push(4); + if (workingdays.friday) days.push(5); + if (workingdays.saturday) days.push(6); + if (workingdays.sunday) days.push(0); return days; } diff --git a/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx b/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx index d511bc96e..f0140d8e7 100644 --- a/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx +++ b/client/src/components/scoreboard-last-days/scoreboard-last-days.component.jsx @@ -1,4 +1,3 @@ -import React from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; @@ -10,7 +9,7 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); @@ -26,7 +25,7 @@ export function ScoreboardLastDays({ bodyshop, sbEntriesByDate }) { {ArrayOfDate.map((a) => ( - {!!sbEntriesByDate ? : } + {sbEntriesByDate ? : } ))} diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx index c017b0267..ebc3b52b9 100644 --- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx +++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.component.jsx @@ -1,8 +1,8 @@ import { CalendarOutlined } from "@ant-design/icons"; import { Card, Col, Divider, Row, Statistic } from "antd"; -import _ from "lodash"; +import { groupBy } from "lodash"; import dayjs from "../../utils/day"; -import React, { useMemo } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -13,7 +13,7 @@ import * as Util from "./scoreboard-targets-table.util"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = () => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); @@ -24,7 +24,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) { const { t } = useTranslation(); const values = useMemo(() => { - const dateHash = _.groupBy(scoreBoardlist, "date"); + const dateHash = groupBy(scoreBoardlist, "date"); let ret = { todayBody: 0, @@ -213,4 +213,5 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) { ); } + export default connect(mapStateToProps, mapDispatchToProps)(ScoreboardTargetsTable); diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js index 32703881c..af6ace810 100644 --- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js +++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js @@ -1,29 +1,172 @@ import dayjs from "../../utils/day"; -export const CalculateWorkingDaysThisMonth = () => dayjs().endOf("month").businessDaysInMonth().length; +const DEFAULT_WORKING_DAYS = [1, 2, 3, 4, 5]; // Default to Monday-Friday -export const CalculateWorkingDaysInPeriod = (start, end) => dayjs(end).businessDiff(dayjs(start)); +// Module-level state for holidays and working weekdays +let holidays = []; +let workingWeekdays = DEFAULT_WORKING_DAYS; -export const CalculateWorkingDaysAsOfToday = () => dayjs().endOf("day").businessDiff(dayjs().startOf("month")); +/** + * Sets the holidays for the business logic. + * @param newHolidays + */ +export const setHolidays = (newHolidays = []) => { + holidays = newHolidays; +}; -export const CalculateWorkingDaysLastMonth = () => - dayjs().subtract(1, "month").endOf("month").businessDaysInMonth().length; +/** + * Clears the holidays. + */ +export const clearHolidays = () => { + holidays = []; +}; +/** + * Sets the working weekdays for the business logic. + * @param newWorkingWeekdays + */ +export const setWorkingWeekdays = (newWorkingWeekdays = DEFAULT_WORKING_DAYS) => { + workingWeekdays = newWorkingWeekdays; +}; + +/** + * Clears the working weekdays, resetting to default (Monday-Friday). + */ +export const clearWorkingWeekdays = () => { + workingWeekdays = DEFAULT_WORKING_DAYS; // Reset to default +}; + +/** + * Translates the bodyshop working days settings to an array of weekdays. + * @returns {*[]} + */ +export const getHolidays = () => { + return holidays; +}; + +/** + * Translates the working days settings from the bodyshop to an array of weekdays. + * @returns {number[]} + */ +export const getWorkingWeekdays = () => { + return workingWeekdays; +}; + +/** + * Calculates the number of working days in the current month, excluding holidays. + * @returns {number} + * @constructor + */ +export const CalculateWorkingDaysThisMonth = () => { + const businessDays = dayjs().businessDaysInMonth(); + return businessDays.filter((day) => !holidays.includes(dayjs(day).format("MM-DD-YYYY"))).length; +}; + +/** + * Calculates the number of working days in a given period, excluding holidays. + * @param start + * @param end + * @returns {number} + * @constructor + */ +export const CalculateWorkingDaysInPeriod = (start, end) => { + let businessDays = dayjs(end).businessDiff(dayjs(start)); + if (dayjs(end).isBusinessDay() && !holidays.includes(dayjs(end).format("MM-DD-YYYY"))) { + businessDays += 1; + } + return businessDays; +}; + +/** + * Calculates the number of working days as of today, excluding holidays. + * @returns {number} + * @constructor + */ +export const CalculateWorkingDaysAsOfToday = () => { + const today = dayjs().startOf("day"); + let businessDays = today.businessDiff(dayjs().startOf("month")); + if (today.isBusinessDay() && !holidays.includes(today.format("MM-DD-YYYY"))) { + businessDays += 1; + } + return businessDays; +}; + +/** + * Calculates the number of working days in the last month, excluding holidays. + * @returns {number} + * @constructor + */ +export const CalculateWorkingDaysLastMonth = () => { + const businessDays = dayjs().subtract(1, "month").businessDaysInMonth(); + return businessDays.filter((day) => !holidays.includes(dayjs(day).format("MM-DD-YYYY"))).length; +}; + +/** + * Calculates the weekly target hours based on daily target hours and the number of working days in the current week. + * @param dailyTargetHrs + * @returns {number} + * @constructor + */ export const WeeklyTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysInPeriod(dayjs().startOf("week"), dayjs().endOf("week")); +/** + * Calculates the weekly target hours for a specific period. + * @param dailyTargetHrs + * @param start + * @param end + * @returns {number} + * @constructor + */ export const WeeklyTargetHrsInPeriod = (dailyTargetHrs, start, end) => dailyTargetHrs * CalculateWorkingDaysInPeriod(start, end); +/** + * Calculates the monthly target hours based on daily target hours and the number of working days in the current month. + * @param dailyTargetHrs + * @returns {number} + * @constructor + */ export const MonthlyTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysThisMonth(); +/** + * Calculates the monthly target hours for the last month based on daily target hours and the number of working days + * in the last month. + * @param dailyTargetHrs + * @returns {number} + * @constructor + */ export const LastMonthTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysLastMonth(); +/** + * Calculates the target hours as of today based on daily target hours and the number of working days as of today. + * @param dailyTargetHrs + * @returns {number} + * @constructor + */ export const AsOfTodayTargetHrs = (dailyTargetHrs) => dailyTargetHrs * CalculateWorkingDaysAsOfToday(); -export const AsOfDateTargetHours = (dailyTargetHours, date) => - dailyTargetHours * dayjs(date).businessDiff(dayjs().startOf("month")); +/** + * Calculates the target hours as of a specific date based on daily target hours and the number of business days up to + * that date. + * @param dailyTargetHours + * @param date + * @returns {number} + * @constructor + */ +export const AsOfDateTargetHours = (dailyTargetHours, date) => { + let businessDays = dayjs(date).businessDiff(dayjs().startOf("month")); + if (dayjs(date).isBusinessDay() && !holidays.includes(dayjs(date).format("MM-DD-YYYY"))) { + businessDays += 1; + } + return dailyTargetHours * businessDays; +}; +/** + * Generates a list of all days in the current month. + * @returns {*[]} + * @constructor + */ export const ListOfDaysInCurrentMonth = () => { const days = []; let dateStart = dayjs().startOf("month"); @@ -36,6 +179,13 @@ export const ListOfDaysInCurrentMonth = () => { return days; }; +/** + * Generates a list of all days between two dates. + * @param start + * @param end + * @returns {*[]} + * @constructor + */ export const ListDaysBetween = ({ start, end }) => { const days = []; let dateStart = dayjs(start); diff --git a/client/src/components/task-center/task-center.component.jsx b/client/src/components/task-center/task-center.component.jsx new file mode 100644 index 000000000..8b269f7b7 --- /dev/null +++ b/client/src/components/task-center/task-center.component.jsx @@ -0,0 +1,156 @@ +import { Virtuoso } from "react-virtuoso"; +import { Badge, Button, Spin } from "antd"; +import { useTranslation } from "react-i18next"; +import { forwardRef, useMemo, useRef } from "react"; +import day from "../../utils/day.js"; +import "./task-center.styles.scss"; +import { + ArrowRightOutlined, + CalendarOutlined, + ClockCircleOutlined, + PlusCircleOutlined, + QuestionCircleOutlined +} from "@ant-design/icons"; + +const TaskCenterComponent = forwardRef( + ({ visible, tasks, loading, error, onTaskClick, onLoadMore, hasMore, createNewTask, incompleteTaskCount }, ref) => { + const { t } = useTranslation(); + const virtuosoRef = useRef(null); + + const sectionIcons = { + [t("tasks.labels.overdue")]: , + [t("tasks.labels.due_today")]: , + [t("tasks.labels.upcoming")]: , + [t("tasks.labels.no_due_date")]: + }; + + const groups = useMemo(() => { + const now = day(); + const today = now.startOf("day"); + + const overdue = tasks.filter((t) => t.due_date && day(t.due_date).isBefore(today)); + const dueToday = tasks.filter((t) => t.due_date && day(t.due_date).isSame(today, "day")); + const upcoming = tasks.filter( + (t) => t.due_date && day(t.due_date).isAfter(today) && !day(t.due_date).isSame(today, "day") + ); + const noDueDate = tasks.filter((t) => !t.due_date); + + return [ + { label: t("tasks.labels.overdue"), tasks: overdue }, + { label: t("tasks.labels.due_today"), tasks: dueToday }, + { label: t("tasks.labels.upcoming"), tasks: upcoming }, + { label: t("tasks.labels.no_due_date"), tasks: noDueDate } + ].filter((group) => group.tasks.length > 0); + }, [tasks, t]); + + const groupCounts = useMemo(() => groups.map((group) => group.tasks.length), [groups]); + + const flatTasks = useMemo(() => groups.flatMap((group) => group.tasks), [groups]); + + const priorityColors = { + 1: "red", + 2: "orange", + 3: "green" + }; + + const getPriorityColor = (priority) => priorityColors[priority] || null; + + const groupContent = (groupIndex) => { + const { label, tasks } = groups[groupIndex]; + let displayCount = tasks.length; + if (label === t("tasks.labels.no_due_date")) { + displayCount = + incompleteTaskCount - + groups.reduce((sum, group, idx) => (idx !== groupIndex ? sum + group.tasks.length : sum), 0); + } + return ( +
+ {sectionIcons[label]} + {label} ({displayCount}) +
+ ); + }; + + const itemContent = (index) => { + const task = flatTasks[index]; + const priorityColor = getPriorityColor(task.priority); + return ( +
onTaskClick(task.id)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + onTaskClick(task.id); + } + }} + > +
+
+
{task.title}
+
+ {t("tasks.labels.ro-number", { + ro_number: task.job?.ro_number || t("general.labels.na") + })} +
+
+
+
+ {task.due_date && {day(task.due_date).fromNow()}} + {!!priorityColor && } +
+
+ ); + }; + + if (error) { + return ( +
+
+

{t("tasks.labels.my_tasks_center")}

+
+
{t("tasks.errors.load_failed")}
+
+ ); + } + + return ( +
+
+ +

{t("tasks.labels.my_tasks_center")}

+
+
+
+
+ + {tasks.length === 0 && !loading ? ( +
{t("tasks.labels.no_tasks")}
+ ) : ( + + loading ? ( +
+ +
+ ) : null + }} + /> + )} +
+ ); + } +); + +TaskCenterComponent.displayName = "TaskCenterComponent"; +export default TaskCenterComponent; diff --git a/client/src/components/task-center/task-center.container.jsx b/client/src/components/task-center/task-center.container.jsx new file mode 100644 index 000000000..9291b600d --- /dev/null +++ b/client/src/components/task-center/task-center.container.jsx @@ -0,0 +1,135 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useQuery } from "@apollo/client"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; +import { INITIAL_TASKS, TASKS_CENTER_POLL_INTERVAL, useSocket } from "../../contexts/SocketIO/useSocket"; +import { useIsEmployee } from "../../utils/useIsEmployee"; +import TaskCenterComponent from "./task-center.component"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { QUERY_TASKS_NO_DUE_DATE_PAGINATED, QUERY_TASKS_WITH_DUE_DATES } from "../../graphql/tasks.queries"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, + currentUser: selectCurrentUser +}); + +const mapDispatchToProps = (dispatch) => ({ + setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" })) +}); + +const TaskCenterContainer = ({ + visible, + onClose, + bodyshop, + currentUser, + setTaskUpsertContext, + incompleteTaskCount +}) => { + const [tasks, setTasks] = useState([]); + const { isConnected } = useSocket(); + const isEmployee = useIsEmployee(bodyshop, currentUser); + + const assignedToId = useMemo(() => { + const employee = bodyshop?.employees?.find((e) => e.user_email === currentUser?.email); + return employee?.id || null; + }, [bodyshop, currentUser]); + + // Query 1: Tasks with due dates + const { + data: dueDateData, + loading: dueLoading, + error: dueError + } = useQuery(QUERY_TASKS_WITH_DUE_DATES, { + variables: { + bodyshop: bodyshop?.id, + assigned_to: assignedToId, + order: [{ due_date: "asc" }, { created_at: "desc" }] + }, + skip: !bodyshop?.id || !assignedToId || !isEmployee || !currentUser?.email, + fetchPolicy: "cache-and-network", + pollInterval: isConnected ? 0 : TASKS_CENTER_POLL_INTERVAL + }); + + // Query 2: Tasks with no due date (paginated) + const { + data: noDueDateData, + loading: noDueLoading, + error: noDueError, + fetchMore + } = useQuery(QUERY_TASKS_NO_DUE_DATE_PAGINATED, { + variables: { + bodyshop: bodyshop?.id, + assigned_to: assignedToId, + order: [{ priority: "asc" }, { created_at: "desc" }], + limit: INITIAL_TASKS, // Adjust this constant as needed + offset: 0 + }, + skip: !bodyshop?.id || !assignedToId || !isEmployee || !currentUser?.email, + fetchPolicy: "cache-and-network", + pollInterval: isConnected ? 0 : TASKS_CENTER_POLL_INTERVAL + }); + + // Combine tasks from both queries + useEffect(() => { + const dueDateTasks = dueDateData?.tasks || []; + const noDueDateTasks = noDueDateData?.tasks || []; + setTasks([...dueDateTasks, ...noDueDateTasks]); + }, [dueDateData, noDueDateData]); + + const noDueDateLength = noDueDateData?.tasks?.length || 0; + const totalNoDueDate = noDueDateData?.tasks_aggregate?.aggregate?.count || 0; + const hasMore = noDueDateLength < totalNoDueDate; + + // Handle pagination for no-due-date tasks + const handleLoadMore = () => { + fetchMore({ + variables: { + offset: noDueDateData?.tasks?.length || 0 + }, + updateQuery: (prev, { fetchMoreResult }) => { + if (!fetchMoreResult) return prev; + return { + ...prev, + tasks: [...prev.tasks, ...fetchMoreResult.tasks], + tasks_aggregate: fetchMoreResult.tasks_aggregate + }; + } + }); + }; + + const handleTaskClick = useCallback( + (id) => { + const task = tasks.find((t) => t.id === id); + if (task) { + setTaskUpsertContext({ + context: { + existingTask: task + } + }); + } + }, + [tasks, setTaskUpsertContext] + ); + + const createNewTask = () => { + setTaskUpsertContext({ actions: {}, context: {} }); + }; + + return ( + + ); +}; + +export default connect(mapStateToProps, mapDispatchToProps)(TaskCenterContainer); diff --git a/client/src/components/task-center/task-center.styles.scss b/client/src/components/task-center/task-center.styles.scss new file mode 100644 index 000000000..062aa133d --- /dev/null +++ b/client/src/components/task-center/task-center.styles.scss @@ -0,0 +1,147 @@ +.task-center { + position: absolute; + top: 64px; + right: 0; + width: 500px; + max-width: 500px; + background: #fff; + color: rgba(0, 0, 0, 0.85); + border: 1px solid #d9d9d9; + border-radius: 6px; + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08), 0 3px 6px rgba(0, 0, 0, 0.06); + z-index: 1000; + display: none; + overflow-x: hidden; + + &.visible { + display: block; + } + + .task-header { + padding: 4px 10px; + border-bottom: 1px solid #f0f0f0; + display: flex; + justify-content: space-between; + align-items: center; + background: #fafafa; + + h3 { + font-size: 14px; + margin: 0; + } + + .create-task-button { + border: none; + color: white; + padding: 4px 12px; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + + &:hover { + background-color: #40a9ff; + } + } + } + + .task-section { + margin: 0; + padding: 0; + } + + .section-title { + padding: 0px 10px; + margin: 0px; + //font-size: 12px; + background: #f5f5f5; + font-weight: 650; + border-bottom: 1px solid #e8e8e8; + position: sticky; + top: 0; + z-index: 1; + } + + .task-row-container { + margin-top: 15px; + margin-bottom: 15px; + } + + .task-row { + cursor: pointer; + border-bottom: 1px solid #f0f0f0; + display: flex; + justify-content: space-between; + align-items: flex-start; + + &:hover { + background: #f5f5f5; + } + + .task-title-cell { + flex: 1; + padding: 6px 8px; + vertical-align: top; + //font-size: 12px; + line-height: 1.2; + max-width: 350px; // or whatever fits your layout + + .task-title { + font-size: 16px; + font-weight: 550; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; // Or a specific width if you want more control + display: inline-block; + vertical-align: middle; + } + + .task-ro-number { + margin-top: 20px; + color: #1677ff; + } + } + + .task-due-cell { + padding: 6px 8px; + vertical-align: top; + //font-size: 12px; + line-height: 1.2; + text-align: right; + white-space: nowrap; + color: rgba(0, 0, 0, 0.45); + } + } + + button { + margin: 8px auto; + padding: 4px 10px; + background-color: #1677ff; + color: white; + border: none; + border-radius: 4px; + //font-size: 12px; + cursor: pointer; + + &:hover { + background-color: #4096ff; + } + + &:disabled { + background-color: #d9d9d9; + cursor: not-allowed; + } + } + + .no-tasks-message, + .error-message { + padding: 16px; + text-align: center; + color: rgba(0, 0, 0, 0.45); + } + + .loading-footer { + padding: 16px; + text-align: center; + } +} diff --git a/client/src/components/task-list/task-list.component.jsx b/client/src/components/task-list/task-list.component.jsx index 3258f29a2..96a587908 100644 --- a/client/src/components/task-list/task-list.component.jsx +++ b/client/src/components/task-list/task-list.component.jsx @@ -4,13 +4,12 @@ import { DeleteFilled, DeleteOutlined, EditFilled, - ExclamationCircleFilled, PlusCircleFilled, SyncOutlined } from "@ant-design/icons"; import { Button, Card, Space, Switch, Table } from "antd"; import queryString from "query-string"; -import React, { useCallback, useEffect } from "react"; +import { useCallback, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { Link, useLocation, useNavigate } from "react-router-dom"; @@ -19,6 +18,7 @@ import { pageLimit } from "../../utils/config"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx"; import dayjs from "../../utils/day"; import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx"; +import PriorityLabel from "../../utils/tasksPriorityLabel.jsx"; /** * Task List Component @@ -54,47 +54,12 @@ const RemindAtRecord = ({ remindAt }) => { ); }; -/** - * Priority Label Component - * @param priority - * @returns {Element} - * @constructor - */ -const PriorityLabel = ({ priority }) => { - switch (priority) { - case 1: - return ( -
- High -
- ); - case 2: - return ( -
- Medium -
- ); - case 3: - return ( -
- Low -
- ); - default: - return ( -
- None -
- ); - } -}; - const mapDispatchToProps = (dispatch) => ({ // Existing dispatch props... setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" })) }); -const mapStateToProps = (state) => ({}); +const mapStateToProps = () => ({}); export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent); diff --git a/client/src/components/task-list/task-list.container.jsx b/client/src/components/task-list/task-list.container.jsx index bcf81b9ce..e34b9c199 100644 --- a/client/src/components/task-list/task-list.container.jsx +++ b/client/src/components/task-list/task-list.container.jsx @@ -4,7 +4,6 @@ import { useMutation, useQuery } from "@apollo/client"; import { MUTATION_TOGGLE_TASK_COMPLETED, MUTATION_TOGGLE_TASK_DELETED } from "../../graphql/tasks.queries.js"; import { pageLimit } from "../../utils/config.js"; import AlertComponent from "../alert/alert.component.jsx"; -import React from "react"; import TaskListComponent from "./task-list.component.jsx"; import { useTranslation } from "react-i18next"; import { connect, useDispatch } from "react-redux"; @@ -20,7 +19,7 @@ const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser }); -const mapDispatchToProps = (dispatch) => ({}); +const mapDispatchToProps = () => ({}); export default connect(mapStateToProps, mapDispatchToProps)(TaskListContainer); @@ -55,8 +54,8 @@ export function TaskListContainer({ bodyshop: bodyshop.id, [relationshipType]: relationshipId, deleted: deleted === "true", - completed: completed === "true", //TODO: Find where mine is set. - assigned_to: onlyMine ? bodyshop?.employees?.find((e) => e.user_email === currentUser.email)?.id : undefined, // replace currentUserID with the actual ID of the current user + completed: completed === "true", + assigned_to: onlyMine ? bodyshop?.employees?.find((e) => e.user_email === currentUser.email)?.id : undefined, offset: page ? (page - 1) * pageLimit : 0, limit: pageLimit, order: [ diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx index 562141a40..0c6482c9d 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -1,5 +1,4 @@ import { Col, Form, Input, Row, Select, Switch } from "antd"; -import React from "react"; import { useTranslation } from "react-i18next"; import { createStructuredSelector } from "reselect"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js"; @@ -8,6 +7,7 @@ import { connect } from "react-redux"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; +import { Link } from "react-router-dom"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -42,7 +42,7 @@ export function TaskUpsertModalComponent({ ]; const generatePresets = (job) => { - if (!job || !selectedJobDetails) return datePickerPresets; // return default presets if no job selected + if (!job || !selectedJobDetails) return datePickerPresets; const relativePresets = []; if (selectedJobDetails?.scheduled_completion) { @@ -97,13 +97,8 @@ export function TaskUpsertModalComponent({ }); }; - /** - * Change the selected job id - * @param jobId - */ const changeJobId = (jobId) => { setSelectedJobId(jobId || null); - // Reset the form fields when selectedJobId changes clearRelations(); }; @@ -163,6 +158,13 @@ export function TaskUpsertModalComponent({ required: true } ]} + extra={ + existingTask && selectedJobId ? ( +
+ {t("tasks.labels.go_to_job")} +
+ ) : null + } >
- + + {t("tasks.links.go_to_bill")} ( + {selectedJobDetails?.bills?.find((bill) => bill.id === form.getFieldValue("billid"))?.invoice_number}) + + ) : null + } + >