| | |
| | | <encoding>UTF-8</encoding> |
| | | </configuration> |
| | | </plugin> |
| | | <plugin> |
| | | <groupId>org.apache.maven.plugins</groupId> |
| | | <artifactId>maven-compiler-plugin</artifactId> |
| | | <configuration> |
| | | <source>17</source> |
| | | <target>17</target> |
| | | </configuration> |
| | | </plugin> |
| | | </plugins> |
| | | </build> |
| | | |
| | |
| | | "lodash": "^4.17.21", |
| | | "papaparse": "^5.4.1", |
| | | "pixi.js": "^7.4.0", |
| | | "qrcode": "^1.5.4", |
| | | "react": "^18.3.0", |
| | | "react-admin": "^5.1.0", |
| | | "react-dom": "^18.3.0", |
| | |
| | | "version": "5.0.1", |
| | | "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", |
| | | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | |
| | | "node": ">=6" |
| | | } |
| | | }, |
| | | "node_modules/camelcase": { |
| | | "version": "5.3.1", |
| | | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", |
| | | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=6" |
| | | } |
| | | }, |
| | | "node_modules/caniuse-lite": { |
| | | "version": "1.0.30001651", |
| | | "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", |
| | |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/cliui": { |
| | | "version": "6.0.0", |
| | | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", |
| | | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", |
| | | "license": "ISC", |
| | | "dependencies": { |
| | | "string-width": "^4.2.0", |
| | | "strip-ansi": "^6.0.0", |
| | | "wrap-ansi": "^6.2.0" |
| | | } |
| | | }, |
| | | "node_modules/clsx": { |
| | |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/decamelize": { |
| | | "version": "1.2.0", |
| | | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", |
| | | "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/decode-uri-component": { |
| | | "version": "0.2.2", |
| | | "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz", |
| | |
| | | "node": ">=0.4.0" |
| | | } |
| | | }, |
| | | "node_modules/dijkstrajs": { |
| | | "version": "1.0.3", |
| | | "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", |
| | | "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/dir-glob": { |
| | | "version": "3.0.1", |
| | | "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", |
| | |
| | | "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", |
| | | "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/emoji-regex": { |
| | | "version": "8.0.0", |
| | | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", |
| | | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/error-ex": { |
| | | "version": "1.3.2", |
| | |
| | | "node": ">=6.9.0" |
| | | } |
| | | }, |
| | | "node_modules/get-caller-file": { |
| | | "version": "2.0.5", |
| | | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", |
| | | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", |
| | | "license": "ISC", |
| | | "engines": { |
| | | "node": "6.* || 8.* || >= 10.*" |
| | | } |
| | | }, |
| | | "node_modules/get-intrinsic": { |
| | | "version": "1.2.4", |
| | | "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz", |
| | |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/is-fullwidth-code-point": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", |
| | | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/is-generator-function": { |
| | |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/p-try": { |
| | | "version": "2.2.0", |
| | | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", |
| | | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=6" |
| | | } |
| | | }, |
| | | "node_modules/papaparse": { |
| | | "version": "5.4.1", |
| | | "resolved": "https://registry.npmmirror.com/papaparse/-/papaparse-5.4.1.tgz", |
| | |
| | | "version": "4.0.0", |
| | | "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", |
| | | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/pixijs" |
| | | } |
| | | }, |
| | | "node_modules/pngjs": { |
| | | "version": "5.0.0", |
| | | "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", |
| | | "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=10.13.0" |
| | | } |
| | | }, |
| | | "node_modules/possible-typed-array-names": { |
| | |
| | | "dev": true, |
| | | "engines": { |
| | | "node": ">=6" |
| | | } |
| | | }, |
| | | "node_modules/qrcode": { |
| | | "version": "1.5.4", |
| | | "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", |
| | | "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "dijkstrajs": "^1.0.1", |
| | | "pngjs": "^5.0.0", |
| | | "yargs": "^15.3.1" |
| | | }, |
| | | "bin": { |
| | | "qrcode": "bin/qrcode" |
| | | }, |
| | | "engines": { |
| | | "node": ">=10.13.0" |
| | | } |
| | | }, |
| | | "node_modules/qs": { |
| | |
| | | "resolved": "https://registry.npmmirror.com/remove-accents/-/remove-accents-0.4.4.tgz", |
| | | "integrity": "sha512-EpFcOa/ISetVHEXqu+VwI96KZBmq+a8LJnGkaeFw45epGlxIZz5dhEEnNZMsQXgORu3qaMoLX4qJCzOik6ytAg==" |
| | | }, |
| | | "node_modules/require-directory": { |
| | | "version": "2.1.1", |
| | | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", |
| | | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/require-main-filename": { |
| | | "version": "2.0.0", |
| | | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", |
| | | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/resolve": { |
| | | "version": "2.0.0-next.5", |
| | | "resolved": "https://registry.npmmirror.com/resolve/-/resolve-2.0.0-next.5.tgz", |
| | |
| | | "node": ">=10" |
| | | } |
| | | }, |
| | | "node_modules/set-blocking": { |
| | | "version": "2.0.0", |
| | | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", |
| | | "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/set-function-length": { |
| | | "version": "1.2.2", |
| | | "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", |
| | |
| | | "node": ">=4" |
| | | } |
| | | }, |
| | | "node_modules/string-width": { |
| | | "version": "4.2.3", |
| | | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", |
| | | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "emoji-regex": "^8.0.0", |
| | | "is-fullwidth-code-point": "^3.0.0", |
| | | "strip-ansi": "^6.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/string.prototype.matchall": { |
| | | "version": "4.0.11", |
| | | "resolved": "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", |
| | |
| | | "version": "6.0.1", |
| | | "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", |
| | | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "ansi-regex": "^5.0.1" |
| | | }, |
| | |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/which-module": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", |
| | | "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/which-typed-array": { |
| | | "version": "1.1.15", |
| | | "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.15.tgz", |
| | |
| | | "node": ">=0.10.0" |
| | | } |
| | | }, |
| | | "node_modules/wrap-ansi": { |
| | | "version": "6.2.0", |
| | | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", |
| | | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "ansi-styles": "^4.0.0", |
| | | "string-width": "^4.1.0", |
| | | "strip-ansi": "^6.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/wrap-ansi/node_modules/ansi-styles": { |
| | | "version": "4.3.0", |
| | | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", |
| | | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "color-convert": "^2.0.1" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/chalk/ansi-styles?sponsor=1" |
| | | } |
| | | }, |
| | | "node_modules/wrap-ansi/node_modules/color-convert": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", |
| | | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "color-name": "~1.1.4" |
| | | }, |
| | | "engines": { |
| | | "node": ">=7.0.0" |
| | | } |
| | | }, |
| | | "node_modules/wrap-ansi/node_modules/color-name": { |
| | | "version": "1.1.4", |
| | | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", |
| | | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/wrappy": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", |
| | |
| | | "node": ">=0.4" |
| | | } |
| | | }, |
| | | "node_modules/y18n": { |
| | | "version": "4.0.3", |
| | | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", |
| | | "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/yallist": { |
| | | "version": "3.1.1", |
| | | "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", |
| | |
| | | "node": ">= 6" |
| | | } |
| | | }, |
| | | "node_modules/yargs": { |
| | | "version": "15.4.1", |
| | | "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", |
| | | "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "cliui": "^6.0.0", |
| | | "decamelize": "^1.2.0", |
| | | "find-up": "^4.1.0", |
| | | "get-caller-file": "^2.0.1", |
| | | "require-directory": "^2.1.1", |
| | | "require-main-filename": "^2.0.0", |
| | | "set-blocking": "^2.0.0", |
| | | "string-width": "^4.2.0", |
| | | "which-module": "^2.0.0", |
| | | "y18n": "^4.0.0", |
| | | "yargs-parser": "^18.1.2" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/yargs-parser": { |
| | | "version": "18.1.3", |
| | | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", |
| | | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", |
| | | "license": "ISC", |
| | | "dependencies": { |
| | | "camelcase": "^5.0.0", |
| | | "decamelize": "^1.2.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=6" |
| | | } |
| | | }, |
| | | "node_modules/yargs/node_modules/find-up": { |
| | | "version": "4.1.0", |
| | | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", |
| | | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "locate-path": "^5.0.0", |
| | | "path-exists": "^4.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/yargs/node_modules/locate-path": { |
| | | "version": "5.0.0", |
| | | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", |
| | | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "p-locate": "^4.1.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/yargs/node_modules/p-limit": { |
| | | "version": "2.3.0", |
| | | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", |
| | | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "p-try": "^2.0.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=6" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/yargs/node_modules/p-locate": { |
| | | "version": "4.1.0", |
| | | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", |
| | | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "p-limit": "^2.2.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/yocto-queue": { |
| | | "version": "0.1.0", |
| | | "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", |
| | |
| | | "lodash": "^4.17.21", |
| | | "papaparse": "^5.4.1", |
| | | "pixi.js": "^7.4.0", |
| | | "qrcode": "^1.5.4", |
| | | "react": "^18.3.0", |
| | | "react-admin": "^5.1.0", |
| | | "react-dom": "^18.3.0", |
| New file |
| | |
| | | import React, { useMemo } from 'react'; |
| | | import { useTranslate, useRedirect } from 'react-admin'; |
| | | import { |
| | | Box, |
| | | Paper, |
| | | Typography, |
| | | Stack, |
| | | Grid, |
| | | Chip, |
| | | Button, |
| | | Skeleton, |
| | | useTheme, |
| | | } from '@mui/material'; |
| | | import { alpha } from '@mui/material/styles'; |
| | | import OpenInNewIcon from '@mui/icons-material/OpenInNew'; |
| | | import BoolValueIcon from '../BoolValueIcon'; |
| | | import { rotationToNum } from '../../tool'; |
| | | import CodeQr from './CodeQr'; |
| | | |
| | | const DIR_RULE_ANGLES = [0, 90, 180, 270]; |
| | | const MAX_RELATION_ITEMS = 8; |
| | | |
| | | const CodeMain = ({ sprite, codeInfo, loading, code }) => { |
| | | const translate = useTranslate(); |
| | | const redirect = useRedirect(); |
| | | const theme = useTheme(); |
| | | |
| | | const info = codeInfo || {}; |
| | | |
| | | const displayCode = info?.data ?? info?.code ?? sprite?.data?.no ?? code; |
| | | const zoneName = info?.zoneName ?? info?.zoneId$ ?? sprite?.data?.zoneName; |
| | | const statusBool = detectBoolean(info?.statusBool ?? info?.status); |
| | | const statusLabel = info?.status$ || translate(statusBool ? 'common.enums.statusTrue' : 'common.enums.statusFalse'); |
| | | const cornerBool = detectBoolean(info?.cornerBool ?? info?.corner ?? sprite?.data?.corner); |
| | | const spinText = getSpinText(info?.spin, translate); |
| | | const mapMetrics = useMemo(() => { |
| | | if (!sprite) { |
| | | return null; |
| | | } |
| | | const posX = sprite.position?.x; |
| | | const posY = sprite.position?.y; |
| | | const rotation = rotationToNum(sprite.rotation || 0); |
| | | const scaleX = sprite.scale?.x; |
| | | const scaleY = sprite.scale?.y; |
| | | return { |
| | | posX: isFiniteNumber(posX) ? posX : null, |
| | | posY: isFiniteNumber(posY) ? posY : null, |
| | | rotation: isFiniteNumber(rotation) ? rotation : null, |
| | | scaleX: isFiniteNumber(scaleX) ? scaleX : null, |
| | | scaleY: isFiniteNumber(scaleY) ? scaleY : null, |
| | | }; |
| | | }, [sprite]); |
| | | |
| | | const routeRelations = useMemo( |
| | | () => extractRelationItems(info, ['routeList', 'routes', 'routeRefs']), |
| | | [info] |
| | | ); |
| | | const funcStaBool = detectBoolean( |
| | | info?.funcStaBool ?? info?.isFuncSta ?? info?.funcStation ?? info?.funcStaFlag |
| | | ); |
| | | const funcStaText = info?.funcSta$ || info?.funcStaName || info?.funcStaType || info?.funcStaCode || info?.funcStaUuid || info?.funcSta; |
| | | |
| | | const ruleList = useMemo(() => normalizeDirRule(info?.dirRule), [info?.dirRule]); |
| | | |
| | | const spatialItems = [ |
| | | { label: translate('table.field.code.x'), value: formatNumber(info?.x, 0), hideWhenEmpty: true }, |
| | | { label: translate('table.field.code.y'), value: formatNumber(info?.y, 0), hideWhenEmpty: true }, |
| | | { |
| | | label: translate('page.map.insight.code.fields.mapPosition', { _: '地图坐标' }), |
| | | render: () => ( |
| | | <CoordinatePair |
| | | x={formatNumber(mapMetrics?.posX, 0)} |
| | | y={formatNumber(mapMetrics?.posY, 0)} |
| | | /> |
| | | ), |
| | | always: mapMetrics?.posX != null || mapMetrics?.posY != null, |
| | | hideWhenEmpty: true, |
| | | }, |
| | | { |
| | | label: translate('page.map.insight.code.fields.rotation', { _: '旋转角度(°)' }), |
| | | value: mapMetrics?.rotation != null ? `${formatNumber(mapMetrics.rotation, 1)}°` : null, |
| | | hideWhenEmpty: true |
| | | }, |
| | | { |
| | | label: translate('table.field.code.corner', { _: '拐角' }), |
| | | render: () => ( |
| | | <BooleanDisplay |
| | | value={cornerBool} |
| | | label={ |
| | | cornerBool == null |
| | | ? translate('common.enums.na') |
| | | : translate(cornerBool ? 'common.enums.true' : 'common.enums.false') |
| | | } |
| | | /> |
| | | ), |
| | | always: true, |
| | | }, |
| | | ]; |
| | | |
| | | const handleOpenDetail = () => { |
| | | if (info?.id) { |
| | | redirect('edit', 'code', info.id); |
| | | } |
| | | }; |
| | | |
| | | const funcStaLabel = funcStaBool == null |
| | | ? translate('common.enums.na') |
| | | : translate(funcStaBool ? 'common.enums.true' : 'common.enums.false'); |
| | | |
| | | return ( |
| | | <Box sx={{ pr: 1, pb: 3 }}> |
| | | <Grid container spacing={3} alignItems="flex-start"> |
| | | <Grid item xs={12} md={5}> |
| | | <Stack spacing={3}> |
| | | <QrPreview |
| | | translate={translate} |
| | | value={displayCode} |
| | | loading={loading} |
| | | statusLabel={statusLabel} |
| | | statusBool={statusBool} |
| | | /> |
| | | <Paper |
| | | sx={{ |
| | | borderRadius: 4, |
| | | border: '1px solid', |
| | | borderColor: 'divider', |
| | | backgroundColor: theme.palette.background.paper, |
| | | p: { xs: 2.5, md: 3 }, |
| | | }} |
| | | > |
| | | <Stack spacing={2}> |
| | | {zoneName && ( |
| | | <Chip |
| | | label={zoneName} |
| | | variant="outlined" |
| | | size="small" |
| | | color="primary" |
| | | sx={{ alignSelf: 'flex-start' }} |
| | | /> |
| | | )} |
| | | <FieldGrid items={spatialItems} loading={loading} /> |
| | | <Button |
| | | variant="contained" |
| | | color="primary" |
| | | startIcon={<OpenInNewIcon />} |
| | | onClick={handleOpenDetail} |
| | | disabled={!info?.id} |
| | | sx={{ alignSelf: 'flex-start', textTransform: 'none', px: 3 }} |
| | | > |
| | | {translate('page.map.insight.code.actions.openDetail', { _: '进入 Code 页面' })} |
| | | </Button> |
| | | </Stack> |
| | | </Paper> |
| | | </Stack> |
| | | </Grid> |
| | | <Grid item xs={12} md={7}> |
| | | <Paper |
| | | sx={{ |
| | | borderRadius: 4, |
| | | border: '1px solid', |
| | | borderColor: 'divider', |
| | | backgroundColor: theme.palette.background.paper, |
| | | p: { xs: 2.5, md: 3 }, |
| | | }} |
| | | > |
| | | <Stack spacing={3}> |
| | | <InfoPanel title={translate('page.map.insight.code.sections.rules', { _: '通行规则' })}> |
| | | <RulesSection |
| | | loading={loading} |
| | | cornerBool={cornerBool} |
| | | spinText={spinText} |
| | | rules={ruleList} |
| | | translate={translate} |
| | | /> |
| | | </InfoPanel> |
| | | <InfoPanel title={translate('page.map.insight.code.relations.routes', { _: '关联路线' })}> |
| | | <RelationsChips |
| | | items={routeRelations} |
| | | emptyLabel={translate('page.map.insight.code.relations.empty', { _: '暂无关联信息' })} |
| | | /> |
| | | </InfoPanel> |
| | | <InfoPanel title={translate('menu.funcSta', { _: '功能站' })}> |
| | | <Stack spacing={1}> |
| | | <BooleanDisplay |
| | | value={funcStaBool} |
| | | label={funcStaLabel} |
| | | /> |
| | | {funcStaText && funcStaText !== funcStaLabel && ( |
| | | <Typography variant="body2" color="text.secondary"> |
| | | {funcStaText} |
| | | </Typography> |
| | | )} |
| | | </Stack> |
| | | </InfoPanel> |
| | | </Stack> |
| | | </Paper> |
| | | </Grid> |
| | | </Grid> |
| | | </Box> |
| | | ); |
| | | }; |
| | | |
| | | const InfoPanel = ({ title, children }) => ( |
| | | <Box |
| | | sx={{ |
| | | border: '1px solid', |
| | | borderColor: 'divider', |
| | | borderRadius: 3, |
| | | p: { xs: 2, sm: 2.5 }, |
| | | backgroundColor: 'background.paper', |
| | | }} |
| | | > |
| | | <Typography variant="subtitle2" color="text.secondary" sx={{ mb: 1 }}> |
| | | {title} |
| | | </Typography> |
| | | {children} |
| | | </Box> |
| | | ); |
| | | |
| | | const RelationsChips = ({ items, emptyLabel }) => { |
| | | if (!items?.length) { |
| | | return ( |
| | | <Typography variant="body2" color="text.disabled"> |
| | | {emptyLabel} |
| | | </Typography> |
| | | ); |
| | | } |
| | | return ( |
| | | <Stack direction="row" spacing={1} flexWrap="wrap"> |
| | | {items.slice(0, MAX_RELATION_ITEMS).map((item, index) => ( |
| | | <Chip |
| | | key={`${getRelationKey(item)}-${index}`} |
| | | label={getRelationLabel(item)} |
| | | variant="outlined" |
| | | size="small" |
| | | /> |
| | | ))} |
| | | {items.length > MAX_RELATION_ITEMS && ( |
| | | <Chip |
| | | label={`+${items.length - MAX_RELATION_ITEMS}`} |
| | | size="small" |
| | | /> |
| | | )} |
| | | </Stack> |
| | | ); |
| | | }; |
| | | |
| | | const QrPreview = ({ translate, value, loading, statusLabel, statusBool }) => ( |
| | | <Paper |
| | | elevation={3} |
| | | sx={{ |
| | | borderRadius: 4, |
| | | px: 3, |
| | | py: 3, |
| | | width: '100%', |
| | | maxWidth: 320, |
| | | alignSelf: { xs: 'center', lg: 'flex-start' }, |
| | | minHeight: 320, |
| | | display: 'flex', |
| | | flexDirection: 'column', |
| | | alignItems: 'center', |
| | | justifyContent: 'center', |
| | | gap: 1, |
| | | }} |
| | | > |
| | | <Typography variant="caption" color="text.secondary" sx={{ letterSpacing: 1, textTransform: 'uppercase' }}> |
| | | {translate('page.map.insight.code.summary.qr', { _: '二维码预览' })} |
| | | </Typography> |
| | | <CodeQr value={value} loading={loading} /> |
| | | <Typography variant="caption" color="text.secondary"> |
| | | QR CODE |
| | | </Typography> |
| | | {!loading && ( |
| | | <Stack spacing={0.5} alignItems="center" sx={{ mt: 0.5 }}> |
| | | <Typography variant="caption" color="text.secondary"> |
| | | {translate('common.field.status', { _: '状态' })} |
| | | </Typography> |
| | | <Chip |
| | | label={statusLabel} |
| | | color={statusBool ? 'success' : 'default'} |
| | | variant="outlined" |
| | | size="small" |
| | | sx={{ fontWeight: 500 }} |
| | | /> |
| | | </Stack> |
| | | )} |
| | | </Paper> |
| | | ); |
| | | |
| | | const FieldGrid = ({ items, loading }) => { |
| | | const candidates = (loading ? items : items.filter(item => { |
| | | if (!item) { |
| | | return false; |
| | | } |
| | | if (item.always) { |
| | | return true; |
| | | } |
| | | if (item.hideWhenEmpty) { |
| | | return hasDisplayValue(item.value); |
| | | } |
| | | return true; |
| | | })).filter(Boolean); |
| | | |
| | | if (!candidates.length) { |
| | | return loading ? <Skeleton width="40%" /> : ( |
| | | <Typography variant="body2" color="text.disabled"> |
| | | - |
| | | </Typography> |
| | | ); |
| | | } |
| | | |
| | | return ( |
| | | <Box |
| | | sx={{ |
| | | display: 'grid', |
| | | gap: 2, |
| | | gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))', |
| | | }} |
| | | > |
| | | {candidates.map((item, index) => ( |
| | | <Stack key={`${item.label}-${index}`} spacing={0.5}> |
| | | <Typography variant="caption" color="text.secondary"> |
| | | {item.label} |
| | | </Typography> |
| | | <Box> |
| | | {item.render |
| | | ? item.render({ loading }) |
| | | : loading |
| | | ? <Skeleton width="60%" /> |
| | | : renderDisplayValue(item.value)} |
| | | </Box> |
| | | </Stack> |
| | | ))} |
| | | </Box> |
| | | ); |
| | | }; |
| | | |
| | | const renderDisplayValue = (value) => { |
| | | if (React.isValidElement(value)) { |
| | | return value; |
| | | } |
| | | if (value === null || value === undefined || value === '') { |
| | | return ( |
| | | <Typography variant="body2" color="text.disabled"> |
| | | - |
| | | </Typography> |
| | | ); |
| | | } |
| | | return ( |
| | | <Typography variant="subtitle1" sx={{ wordBreak: 'break-word', fontWeight: 600 }}> |
| | | {value} |
| | | </Typography> |
| | | ); |
| | | }; |
| | | |
| | | const hasDisplayValue = (value) => { |
| | | if (value === 0 || value === false) { |
| | | return true; |
| | | } |
| | | if (typeof value === 'number') { |
| | | return !Number.isNaN(value); |
| | | } |
| | | if (value instanceof Date) { |
| | | return true; |
| | | } |
| | | return value !== null && value !== undefined && String(value).trim() !== ''; |
| | | }; |
| | | |
| | | const BooleanDisplay = ({ value, label }) => { |
| | | if (value == null) { |
| | | return ( |
| | | <Typography variant="body2" color="text.disabled"> |
| | | {label} |
| | | </Typography> |
| | | ); |
| | | } |
| | | return ( |
| | | <Stack direction="row" alignItems="center" spacing={1}> |
| | | <BoolValueIcon value={value} /> |
| | | <Typography variant="body2">{label}</Typography> |
| | | </Stack> |
| | | ); |
| | | }; |
| | | |
| | | const CoordinatePair = ({ x, y }) => { |
| | | if (!hasDisplayValue(x) && !hasDisplayValue(y)) { |
| | | return renderDisplayValue(null); |
| | | } |
| | | |
| | | return ( |
| | | <Stack direction="row" spacing={2.5} alignItems="center" flexWrap="wrap"> |
| | | <Stack spacing={0.25}> |
| | | <Typography variant="caption" color="text.secondary"> |
| | | X |
| | | </Typography> |
| | | <Typography variant="subtitle1" sx={{ fontWeight: 600 }}> |
| | | {hasDisplayValue(x) ? x : '-'} |
| | | </Typography> |
| | | </Stack> |
| | | <Stack spacing={0.25}> |
| | | <Typography variant="caption" color="text.secondary"> |
| | | Y |
| | | </Typography> |
| | | <Typography variant="subtitle1" sx={{ fontWeight: 600 }}> |
| | | {hasDisplayValue(y) ? y : '-'} |
| | | </Typography> |
| | | </Stack> |
| | | </Stack> |
| | | ); |
| | | }; |
| | | |
| | | const RulesSection = ({ cornerBool, spinText, rules, translate, loading }) => ( |
| | | <Stack spacing={2}> |
| | | <Box |
| | | sx={{ |
| | | display: 'grid', |
| | | gridTemplateColumns: { xs: '1fr', sm: 'repeat(2, minmax(0, 1fr))' }, |
| | | gap: 2, |
| | | }} |
| | | > |
| | | <RuleMetric |
| | | label={translate('table.field.code.corner')} |
| | | value={ |
| | | <BooleanDisplay |
| | | value={cornerBool} |
| | | label={cornerBool == null |
| | | ? translate('common.enums.na') |
| | | : translate(cornerBool ? 'common.enums.true' : 'common.enums.false')} |
| | | /> |
| | | } |
| | | /> |
| | | <RuleMetric |
| | | label={translate('table.field.code.spin')} |
| | | value={ |
| | | <Chip |
| | | label={spinText} |
| | | size="small" |
| | | color="primary" |
| | | variant="outlined" |
| | | /> |
| | | } |
| | | /> |
| | | </Box> |
| | | {loading ? ( |
| | | <Skeleton variant="rounded" height={220} /> |
| | | ) : ( |
| | | <DirectionRuleCompass rules={rules} translate={translate} /> |
| | | )} |
| | | </Stack> |
| | | ); |
| | | |
| | | const DirectionRuleCompass = ({ rules, translate }) => { |
| | | const theme = useTheme(); |
| | | const enabledCount = rules.filter(rule => rule.enabled).length; |
| | | const placement = { |
| | | 0: { gridColumn: 2, gridRow: 1 }, |
| | | 90: { gridColumn: 3, gridRow: 2 }, |
| | | 180: { gridColumn: 2, gridRow: 3 }, |
| | | 270: { gridColumn: 1, gridRow: 2 }, |
| | | }; |
| | | |
| | | return ( |
| | | <Box |
| | | sx={{ |
| | | display: 'grid', |
| | | gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', |
| | | gridTemplateRows: 'repeat(3, minmax(58px, auto))', |
| | | gap: 1, |
| | | alignItems: 'stretch', |
| | | }} |
| | | > |
| | | <Box |
| | | sx={{ |
| | | gridColumn: 2, |
| | | gridRow: 2, |
| | | borderRadius: 3, |
| | | border: '1px dashed', |
| | | borderColor: 'divider', |
| | | backgroundColor: alpha(theme.palette.primary.main, 0.04), |
| | | px: 1, |
| | | py: 0.75, |
| | | display: 'flex', |
| | | alignItems: 'center', |
| | | justifyContent: 'center', |
| | | textAlign: 'center', |
| | | }} |
| | | > |
| | | <Typography variant="h6" sx={{ lineHeight: 1.1, fontWeight: 700 }}> |
| | | {enabledCount}/{rules.length} |
| | | </Typography> |
| | | </Box> |
| | | {rules.map(rule => { |
| | | const position = placement[rule.angle] || { gridColumn: 'auto', gridRow: 'auto' }; |
| | | const statusText = translate( |
| | | rule.enabled ? 'page.code.dirRule.status.enabled' : 'page.code.dirRule.status.disabled' |
| | | ); |
| | | |
| | | return ( |
| | | <Box |
| | | key={rule.angle} |
| | | sx={{ |
| | | ...position, |
| | | borderRadius: 3, |
| | | border: '1px solid', |
| | | borderColor: rule.enabled ? 'success.light' : 'error.light', |
| | | backgroundColor: rule.enabled |
| | | ? alpha(theme.palette.success.main, 0.08) |
| | | : alpha(theme.palette.error.main, 0.08), |
| | | px: 1, |
| | | py: 0.75, |
| | | minHeight: 60, |
| | | display: 'flex', |
| | | alignItems: 'center', |
| | | justifyContent: 'center', |
| | | textAlign: 'center', |
| | | }} |
| | | > |
| | | <Stack spacing={0.35}> |
| | | <Typography variant="subtitle2" sx={{ fontWeight: 700, lineHeight: 1 }}> |
| | | {rule.angle}° |
| | | </Typography> |
| | | <Typography |
| | | variant="caption" |
| | | sx={{ |
| | | lineHeight: 1.1, |
| | | color: rule.enabled ? 'success.dark' : 'error.dark', |
| | | fontWeight: 600, |
| | | }} |
| | | > |
| | | {statusText} |
| | | </Typography> |
| | | </Stack> |
| | | </Box> |
| | | ); |
| | | })} |
| | | </Box> |
| | | ); |
| | | }; |
| | | |
| | | const RuleMetric = ({ label, value }) => ( |
| | | <Stack spacing={0.5}> |
| | | <Typography variant="caption" color="text.secondary"> |
| | | {label} |
| | | </Typography> |
| | | {value} |
| | | </Stack> |
| | | ); |
| | | |
| | | const normalizeDirRule = (value) => { |
| | | if (!value) { |
| | | return DIR_RULE_ANGLES.map(angle => ({ angle, enabled: true })); |
| | | } |
| | | let parsed = value; |
| | | if (typeof parsed === 'string') { |
| | | try { |
| | | parsed = JSON.parse(parsed); |
| | | } catch (error) { |
| | | parsed = []; |
| | | } |
| | | } |
| | | if (!Array.isArray(parsed)) { |
| | | parsed = [parsed]; |
| | | } |
| | | const normalized = DIR_RULE_ANGLES.map(angle => ({ angle, enabled: true })); |
| | | parsed.forEach(rule => { |
| | | const angle = typeof rule?.angle === 'number' ? rule.angle : Number(rule?.angle); |
| | | if (!Number.isFinite(angle)) { |
| | | return; |
| | | } |
| | | const normalizedAngle = ((angle % 360) + 360) % 360; |
| | | const target = normalized.find(item => item.angle === normalizedAngle); |
| | | if (target) { |
| | | const disabled = rule?.enabled === false || rule?.enabled === 'false' || rule?.value === false || rule?.value === 'false'; |
| | | target.enabled = !disabled; |
| | | } |
| | | }); |
| | | return normalized; |
| | | }; |
| | | |
| | | const getRelationKey = (item) => { |
| | | if (item && typeof item === 'object') { |
| | | return item.id || item.uuid || item.code || item.data || item.no || JSON.stringify(item); |
| | | } |
| | | return item ?? 'relation'; |
| | | }; |
| | | |
| | | const getRelationLabel = (item) => { |
| | | if (item == null) { |
| | | return '-'; |
| | | } |
| | | if (typeof item === 'string' || typeof item === 'number') { |
| | | return item; |
| | | } |
| | | if (typeof item === 'object') { |
| | | return item.name || item.code || item.data || item.no || item.uuid || item.locNo || item.staNo || JSON.stringify(item); |
| | | } |
| | | return String(item); |
| | | }; |
| | | |
| | | const extractRelationItems = (info, keys) => { |
| | | if (!info) { |
| | | return []; |
| | | } |
| | | const dataset = keys |
| | | .map(key => info?.[key]) |
| | | .find(value => Array.isArray(value) && value.length); |
| | | return Array.isArray(dataset) ? dataset : []; |
| | | }; |
| | | |
| | | const detectBoolean = (value) => { |
| | | if (typeof value === 'boolean') { |
| | | return value; |
| | | } |
| | | if (value === 1 || value === '1') { |
| | | return true; |
| | | } |
| | | if (value === 0 || value === '0') { |
| | | return false; |
| | | } |
| | | if (value === 'true') { |
| | | return true; |
| | | } |
| | | if (value === 'false') { |
| | | return false; |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const getSpinText = (spin, translate) => { |
| | | switch (spin) { |
| | | case 1: |
| | | return translate('page.code.enums.spin.cw'); |
| | | case 2: |
| | | return translate('page.code.enums.spin.ccw'); |
| | | case 0: |
| | | return translate('page.code.enums.spin.na'); |
| | | default: |
| | | return translate('page.code.enums.spin.na'); |
| | | } |
| | | }; |
| | | |
| | | const isFiniteNumber = (value) => Number.isFinite(Number(value)); |
| | | |
| | | const formatNumber = (value, precision = 2) => { |
| | | if (value === null || value === undefined || value === '') { |
| | | return null; |
| | | } |
| | | const num = Number(value); |
| | | if (!Number.isFinite(num)) { |
| | | return value; |
| | | } |
| | | if (precision === null) { |
| | | return num; |
| | | } |
| | | return num.toFixed(precision); |
| | | }; |
| | | |
| | | export default CodeMain; |
| New file |
| | |
| | | import React, { useEffect, useState } from 'react'; |
| | | import { toDataURL } from 'qrcode'; |
| | | import { Box, Skeleton, Typography, useTheme } from '@mui/material'; |
| | | |
| | | const CodeQr = ({ value, size = 160, loading }) => { |
| | | const [dataUrl, setDataUrl] = useState(null); |
| | | const theme = useTheme(); |
| | | |
| | | useEffect(() => { |
| | | let ignore = false; |
| | | if (!value) { |
| | | setDataUrl(null); |
| | | return () => { ignore = true; }; |
| | | } |
| | | const darkColor = theme.palette.mode === 'dark' ? '#f5f6fa' : '#111111'; |
| | | const lightColor = theme.palette.mode === 'dark' ? '#1b1b1b' : '#fafafa'; |
| | | toDataURL(String(value), { |
| | | width: size, |
| | | margin: 2, |
| | | color: { |
| | | dark: darkColor, |
| | | light: lightColor |
| | | }, |
| | | }).then((url) => { |
| | | if (!ignore) { |
| | | setDataUrl(url); |
| | | } |
| | | }).catch((error) => { |
| | | console.error('QR render failed', error); |
| | | if (!ignore) { |
| | | setDataUrl(null); |
| | | } |
| | | }); |
| | | return () => { |
| | | ignore = true; |
| | | }; |
| | | }, [value, size, theme.palette.mode]); |
| | | |
| | | if (loading) { |
| | | return <Skeleton variant="rectangular" height={size} width={size} sx={{ borderRadius: 2 }} />; |
| | | } |
| | | |
| | | if (!value || !dataUrl) { |
| | | return ( |
| | | <Typography variant="body2" color="text.disabled"> |
| | | - |
| | | </Typography> |
| | | ); |
| | | } |
| | | |
| | | return ( |
| | | <Box |
| | | component="img" |
| | | src={dataUrl} |
| | | alt="Code QR" |
| | | sx={{ |
| | | width: size, |
| | | height: size, |
| | | borderRadius: 2, |
| | | boxShadow: (theme) => theme.shadows[3], |
| | | p: 1, |
| | | backgroundColor: 'background.paper' |
| | | }} |
| | | /> |
| | | ); |
| | | }; |
| | | |
| | | export default CodeQr; |
| New file |
| | |
| | | import React, { useState, useEffect, useCallback } from 'react'; |
| | | import { useTranslate } from 'react-admin'; |
| | | import { Box, Tabs, Tab, Divider } from '@mui/material'; |
| | | import JsonShow from '../../JsonShow'; |
| | | import { getPointInfo } from '../../http'; |
| | | import CodeMain from './CodeMain'; |
| | | |
| | | const CodeInsight = ({ sprite, setTitle }) => { |
| | | const translate = useTranslate(); |
| | | const [activeTab, setActiveTab] = useState(0); |
| | | const [currentCode, setCurrentCode] = useState(null); |
| | | const [codeInfo, setCodeInfo] = useState(null); |
| | | const [loading, setLoading] = useState(false); |
| | | |
| | | const codeLabel = translate('page.map.devices.code', { |
| | | _: translate('page.map.devices.point', { _: 'Code' }) |
| | | }); |
| | | |
| | | const fetchCodeInfo = useCallback(async (code, options = {}) => { |
| | | if (!code) { |
| | | return; |
| | | } |
| | | if (!options?.silent) { |
| | | setLoading(true); |
| | | } |
| | | setTitle(`${codeLabel} - ${code}`); |
| | | setCurrentCode(code); |
| | | try { |
| | | await getPointInfo(code, (response) => { |
| | | setCodeInfo(response || null); |
| | | }); |
| | | } finally { |
| | | if (!options?.silent) { |
| | | setLoading(false); |
| | | } |
| | | } |
| | | }, [codeLabel, setTitle]); |
| | | |
| | | useEffect(() => { |
| | | if (sprite?.data?.no) { |
| | | fetchCodeInfo(sprite.data.no); |
| | | setActiveTab(0); |
| | | } else { |
| | | setCodeInfo(null); |
| | | setCurrentCode(null); |
| | | setTitle(null); |
| | | } |
| | | return () => { |
| | | setCodeInfo(null); |
| | | setCurrentCode(null); |
| | | setTitle(null); |
| | | }; |
| | | }, [sprite, fetchCodeInfo, setTitle]); |
| | | |
| | | const handleTabChange = (_, newValue) => { |
| | | setActiveTab(newValue); |
| | | }; |
| | | |
| | | return ( |
| | | <Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}> |
| | | <Tabs |
| | | value={activeTab} |
| | | onChange={handleTabChange} |
| | | centered |
| | | sx={{ mb: 0 }} |
| | | > |
| | | <Tab label={translate('page.map.insight.title')} /> |
| | | <Tab label={'JSON'} /> |
| | | </Tabs> |
| | | |
| | | <Divider /> |
| | | |
| | | <Box flex={1} pt={2} sx={{ overflow: 'hidden' }}> |
| | | {activeTab === 0 && ( |
| | | <CodeMain |
| | | sprite={sprite} |
| | | codeInfo={codeInfo} |
| | | loading={loading} |
| | | code={currentCode} |
| | | /> |
| | | )} |
| | | {activeTab === 1 && ( |
| | | <JsonShow |
| | | data={codeInfo || sprite?.data} |
| | | height={550} |
| | | /> |
| | | )} |
| | | </Box> |
| | | </Box> |
| | | ); |
| | | }; |
| | | |
| | | export default CodeInsight; |
| | |
| | | import JsonShow from '../JsonShow'; |
| | | import ShelfInsight from './shelf'; |
| | | import AgvInsight from './agv'; |
| | | import PointInsight from './point'; |
| | | import CodeInsight from './code'; |
| | | |
| | | const Insight = (props) => { |
| | | const { open, onCancel, sprite, width = PAGE_DRAWER_WIDTH } = props; |
| | |
| | | ) |
| | | case DEVICE_TYPE.POINT: |
| | | return ( |
| | | <PointInsight |
| | | <CodeInsight |
| | | sprite={sprite} |
| | | setTitle={setTitle} |
| | | /> |