From 5178a2425af1b8e29eec76f9f986c156226eeb4d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 10:47:32 +0000 Subject: [PATCH] feat(ui): add report analysis feature for admin Added a new ReportAnalysisDialog component, accessible from the ReportViewerDialog, strictly for Admin users. It parses execution reports, extracts markdown metrics like processed orders, skipped materials, errors, and execution time, and presents them in an interactive recharts line chart aggregated by day. Co-authored-by: luwamgere15-crypto <255338376+luwamgere15-crypto@users.noreply.github.com> --- package-lock.json | 412 +++++++++++++++- package.json | 1 + .../src/components/ReportAnalysisDialog.tsx | 450 ++++++++++++++++++ .../src/components/ReportViewerDialog.tsx | 29 +- src/renderer/src/pages/CleanerPage.tsx | 11 + tests/playwright/screenshot.png | Bin 0 -> 21886 bytes tests/playwright/verify_report_analysis.js | 18 + 7 files changed, 888 insertions(+), 33 deletions(-) create mode 100644 src/renderer/src/components/ReportAnalysisDialog.tsx create mode 100644 tests/playwright/screenshot.png create mode 100644 tests/playwright/verify_report_analysis.js diff --git a/package-lock.json b/package-lock.json index 40f9a78..bff0fea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "playwright-core": "^1.58.2", "react-focus-lock": "^2.13.7", "react-markdown": "^10.1.0", + "recharts": "^3.8.0", "reflect-metadata": "^0.2.2", "rehype-autolink-headings": "^7.1.0", "rehype-highlight": "^7.0.2", @@ -969,7 +970,6 @@ "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", @@ -1031,7 +1031,6 @@ "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz", "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==", "license": "MIT", - "peer": true, "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", @@ -1223,7 +1222,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2001,6 +1999,7 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, + "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -2022,6 +2021,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2038,6 +2038,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -2052,6 +2053,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -3307,6 +3309,42 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", @@ -4404,7 +4442,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, "node_modules/@swc/helpers": { @@ -4786,6 +4829,69 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -4896,7 +5002,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz", "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4918,7 +5023,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4963,6 +5067,12 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -5033,7 +5143,6 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -5473,7 +5582,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5506,7 +5614,6 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6269,7 +6376,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7065,7 +7171,8 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -7108,6 +7215,127 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -7195,6 +7423,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/decode-named-character-reference": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", @@ -7471,7 +7705,6 @@ "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", @@ -7686,7 +7919,6 @@ "integrity": "sha512-Rz5QvP1pTqoU1DPRrG3EeX2oWBtS3uRmd6Z/wzZsb2e/iIUsrT+XcBaAhFr4FW48gDc8uP2wYVyY5Aamha/5Zg==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", @@ -7875,6 +8107,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -7895,6 +8128,7 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -8146,6 +8380,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", + "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -8223,7 +8467,6 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8284,7 +8527,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8574,6 +8816,12 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -9752,6 +10000,16 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "license": "MIT" }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -9817,6 +10075,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/ip-address": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", @@ -13247,7 +13514,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13328,7 +13594,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -13352,6 +13617,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -13369,6 +13635,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -13389,7 +13656,6 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -13531,7 +13797,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -13553,7 +13818,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13617,6 +13881,29 @@ "react": ">=18" } }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", @@ -13690,6 +13977,51 @@ "node": ">=10" } }, + "node_modules/recharts": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.0.tgz", + "integrity": "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -13885,6 +14217,12 @@ "url": "https://github.com/sponsors/jet2jet" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "2.0.0-next.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", @@ -15067,6 +15405,7 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -15150,6 +15489,12 @@ "semver": "bin/semver" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -16096,7 +16441,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16507,12 +16851,33 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -17059,7 +17424,6 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -17268,7 +17632,6 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -17506,7 +17869,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 3a1d3da..bf98dc3 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "playwright-core": "^1.58.2", "react-focus-lock": "^2.13.7", "react-markdown": "^10.1.0", + "recharts": "^3.8.0", "reflect-metadata": "^0.2.2", "rehype-autolink-headings": "^7.1.0", "rehype-highlight": "^7.0.2", diff --git a/src/renderer/src/components/ReportAnalysisDialog.tsx b/src/renderer/src/components/ReportAnalysisDialog.tsx new file mode 100644 index 0000000..6ee64b7 --- /dev/null +++ b/src/renderer/src/components/ReportAnalysisDialog.tsx @@ -0,0 +1,450 @@ +import React, { useCallback, useEffect, useState, useMemo } from 'react' +import { X, BarChart3, Loader2, AlertCircle } from 'lucide-react' +import { + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + Line, + ComposedChart +} from 'recharts' + +interface ReportAnalysisDialogProps { + isOpen: boolean + onClose: () => void + isAdmin: boolean +} + +// Extracted metrics from a single report +interface ReportMetrics { + date: string + user: string + processedOrders: number + deletedMaterials: number + skippedMaterials: number + errors: number + retriedOrders: number + successfulRetries: number + executionTimeSecs: number + timestamp: number +} + +// Aggregated daily metrics +interface DailyMetrics { + date: string + processedOrders: number + deletedMaterials: number + skippedMaterials: number + errors: number + retriedOrders: number + successfulRetries: number + executionTimeSecs: number + users: string[] // Unique users who ran reports on this day + reportCount: number +} + +type MetricKey = keyof Omit + +const METRIC_LABELS: Record = { + processedOrders: '处理订单数', + deletedMaterials: '删除物料数', + skippedMaterials: '跳过物料数', + errors: '错误数量', + retriedOrders: '重试订单数', + successfulRetries: '成功重试数', + executionTimeSecs: '执行耗时(秒)' +} + +const METRIC_COLORS: Record = { + processedOrders: '#3b82f6', // blue-500 + deletedMaterials: '#ef4444', // red-500 + skippedMaterials: '#eab308', // yellow-500 + errors: '#000000', // black + retriedOrders: '#8b5cf6', // violet-500 + successfulRetries: '#10b981', // emerald-500 + executionTimeSecs: '#64748b' // slate-500 +} + +export const ReportAnalysisDialog: React.FC = ({ + isOpen, + onClose, + isAdmin +}) => { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + const [reportData, setReportData] = useState([]) + + // Selected metrics for the chart + const [selectedMetrics, setSelectedMetrics] = useState>( + new Set(['processedOrders', 'deletedMaterials', 'errors']) + ) + + const parseDurationToSeconds = (durationStr: string): number => { + // Expected format: "X分Y秒" or similar + let totalSeconds = 0 + const minutesMatch = durationStr.match(/(\d+)分/) + if (minutesMatch) { + totalSeconds += parseInt(minutesMatch[1], 10) * 60 + } + const secondsMatch = durationStr.match(/(\d+)秒/) + if (secondsMatch) { + totalSeconds += parseInt(secondsMatch[1], 10) + } + return totalSeconds + } + + const loadAndAnalyzeReports = useCallback(async () => { + if (!isAdmin) return + + setIsLoading(true) + setError(null) + try { + // 1. Fetch report list + const listResult = await window.electron.report.listAll() + if (!listResult.success || !listResult.data) { + throw new Error(listResult.error || '获取报告列表失败') + } + + const reports = listResult.data + const metricsList: ReportMetrics[] = [] + + // 2. Fetch content for each report (in chunks to avoid memory/network issues if there are many) + const chunkSize = 10 + for (let i = 0; i < reports.length; i += chunkSize) { + const chunk = reports.slice(i, i + chunkSize) + const contentPromises = chunk.map(async (report) => { + try { + const contentResult = await window.electron.report.download(report.key) + if (contentResult.success && contentResult.data) { + return { report, content: contentResult.data } + } + } catch (e) { + console.warn(`Failed to fetch content for report ${report.key}`, e) + } + return null + }) + + const chunkContents = await Promise.all(contentPromises) + + // 3. Parse each report's markdown content + for (const item of chunkContents) { + if (!item) continue + + const { report, content } = item + + // Regex to extract values from the markdown table + const extractValue = (key: string): string | null => { + const regex = new RegExp(`\\|\\s*\\*\\*${key}\\*\\*\\s*\\|\\s*\`([^\`]+)\`\\s*\\|`) + const match = content.match(regex) + return match ? match[1].trim() : null + } + + const execTimeStr = extractValue('执行时间') + const user = extractValue('操作用户') || report.username || 'unknown' + const processedOrders = parseInt(extractValue('处理订单数') || '0', 10) + const deletedMaterials = parseInt(extractValue('删除物料数') || '0', 10) + const skippedMaterials = parseInt(extractValue('跳过物料数') || '0', 10) + const errors = parseInt(extractValue('错误数量') || '0', 10) + const retriedOrders = parseInt(extractValue('重试订单数') || '0', 10) + const successfulRetries = parseInt(extractValue('成功重试数') || '0', 10) + const executionTimeStr = extractValue('执行耗时') || '0秒' + + const executionTimeSecs = parseDurationToSeconds(executionTimeStr) + + // Try to parse the date + let dateStr = '未知日期' + let timestamp = report.lastModified ? new Date(report.lastModified).getTime() : 0 + + if (execTimeStr) { + try { + const parsedDate = new Date(execTimeStr) + if (!isNaN(parsedDate.getTime())) { + dateStr = parsedDate + .toLocaleDateString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + .replace(/\//g, '-') + timestamp = parsedDate.getTime() + } + } catch { + // Fallback to report lastModified + } + } + + if (dateStr === '未知日期' && report.lastModified) { + const d = new Date(report.lastModified) + dateStr = d + .toLocaleDateString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + .replace(/\//g, '-') + } + + metricsList.push({ + date: dateStr, + user, + processedOrders, + deletedMaterials, + skippedMaterials, + errors, + retriedOrders, + successfulRetries, + executionTimeSecs, + timestamp + }) + } + } + + setReportData(metricsList) + } catch (err: any) { + setError(err.message || '分析报告时发生错误') + } finally { + setIsLoading(false) + } + }, [isAdmin]) + + useEffect(() => { + if (isOpen && isAdmin) { + void loadAndAnalyzeReports() + } else { + setReportData([]) + setError(null) + } + }, [isOpen, isAdmin, loadAndAnalyzeReports]) + + // Aggregate data by date + const chartData = useMemo(() => { + if (!reportData.length) return [] + + const dailyMap = new Map() + + for (const data of reportData) { + const { date } = data + + if (!dailyMap.has(date)) { + dailyMap.set(date, { + date, + processedOrders: 0, + deletedMaterials: 0, + skippedMaterials: 0, + errors: 0, + retriedOrders: 0, + successfulRetries: 0, + executionTimeSecs: 0, + users: [], + reportCount: 0 + }) + } + + const day = dailyMap.get(date)! + day.processedOrders += data.processedOrders + day.deletedMaterials += data.deletedMaterials + day.skippedMaterials += data.skippedMaterials + day.errors += data.errors + day.retriedOrders += data.retriedOrders + day.successfulRetries += data.successfulRetries + day.executionTimeSecs += data.executionTimeSecs + day.reportCount += 1 + + if (!day.users.includes(data.user)) { + day.users.push(data.user) + } + } + + // Convert map to array and sort by date + const sortedData = Array.from(dailyMap.values()).sort((a, b) => { + // Basic string comparison works for YYYY-MM-DD + return a.date.localeCompare(b.date) + }) + + return sortedData + }, [reportData]) + + const handleMetricToggle = (metric: MetricKey) => { + const next = new Set(selectedMetrics) + if (next.has(metric)) { + if (next.size > 1) { + // Ensure at least one metric is selected + next.delete(metric) + } + } else { + next.add(metric) + } + setSelectedMetrics(next) + } + + // Custom Tooltip formatter + const CustomTooltip = ({ active, payload, label }: any) => { + if (active && payload && payload.length) { + // Find the original daily data + const dailyData = chartData.find((d) => d.date === label) + + return ( +
+

+ {label} +

+ +
+ {payload.map((entry: any, index: number) => ( +
+ + + {entry.name}: + + + {entry.value} {entry.dataKey === 'executionTimeSecs' ? '秒' : ''} + +
+ ))} +
+ + {dailyData && ( +
+

操作用户: {dailyData.users.join(', ')}

+

报告总数: {dailyData.reportCount}

+
+ )} +
+ ) + } + return null + } + + if (!isOpen) return null + if (!isAdmin) return null + + return ( +
+
+ {/* Header */} +
+
+ +

执行报告分析

+
+ +
+ + {/* Content */} +
+ {isLoading ? ( +
+ +

正在分析报告数据,可能需要几秒钟...

+
+ ) : error ? ( +
+ +

分析失败

+

{error}

+ +
+ ) : chartData.length === 0 ? ( +
+ +

暂无报告数据可供分析

+
+ ) : ( +
+ {/* Controls */} +
+

选择呈现内容 (多选)

+
+ {(Object.keys(METRIC_LABELS) as MetricKey[]).map((key) => { + const isSelected = selectedMetrics.has(key) + return ( + + ) + })} +
+
+ + {/* Chart */} +
+ + + + + + } /> + + + {/* Render selected metrics as lines or bars */} + {Array.from(selectedMetrics).map((metric) => ( + + ))} + + +
+ +
+ 数据以天为单位进行聚合统计。展示的是选定时间段内的总量。 +
+
+ )} +
+
+
+ ) +} + +export default ReportAnalysisDialog diff --git a/src/renderer/src/components/ReportViewerDialog.tsx b/src/renderer/src/components/ReportViewerDialog.tsx index 0d66aca..e9476a0 100644 --- a/src/renderer/src/components/ReportViewerDialog.tsx +++ b/src/renderer/src/components/ReportViewerDialog.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' -import { X, FileText, Loader2, ChevronDown } from 'lucide-react' +import { X, FileText, Loader2, ChevronDown, BarChart3 } from 'lucide-react' import { Combobox, Transition } from '@headlessui/react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' @@ -22,13 +22,15 @@ interface ReportViewerDialogProps { onClose: () => void isAdmin: boolean currentUsername: string + onOpenAnalysis?: () => void } export const ReportViewerDialog: React.FC = ({ isOpen, onClose, isAdmin, - currentUsername + currentUsername, + onOpenAnalysis }) => { const [reports, setReports] = useState([]) const [selectedReport, setSelectedReport] = useState(null) @@ -130,12 +132,23 @@ export const ReportViewerDialog: React.FC = ({

执行报告浏览器

- +
+ {isAdmin && onOpenAnalysis && ( + + )} + +
{/* Controls */} diff --git a/src/renderer/src/pages/CleanerPage.tsx b/src/renderer/src/pages/CleanerPage.tsx index 279fc48..334181a 100644 --- a/src/renderer/src/pages/CleanerPage.tsx +++ b/src/renderer/src/pages/CleanerPage.tsx @@ -11,6 +11,7 @@ const MaterialTypeManagementDialog = React.lazy( ) const ExecutionReportDialog = React.lazy(() => import('../components/ExecutionReportDialog')) const ReportViewerDialog = React.lazy(() => import('../components/ReportViewerDialog')) +const ReportAnalysisDialog = React.lazy(() => import('../components/ReportAnalysisDialog')) const CleanerPage: React.FC = () => { const typeManagementButtonRef = React.useRef(null) @@ -66,6 +67,7 @@ const CleanerPage: React.FC = () => { } = useCleaner() const [isReportViewerOpen, setIsReportViewerOpen] = React.useState(false) + const [isReportAnalysisOpen, setIsReportAnalysisOpen] = React.useState(false) return (
@@ -169,6 +171,15 @@ const CleanerPage: React.FC = () => { onClose={() => setIsReportViewerOpen(false)} isAdmin={isAdmin} currentUsername={currentUsername} + onOpenAnalysis={() => setIsReportAnalysisOpen(true)} + /> + + + + setIsReportAnalysisOpen(false)} + isAdmin={isAdmin} /> diff --git a/tests/playwright/screenshot.png b/tests/playwright/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f5cbc9d21e56575ec4e82bf73840f8251f192b3b GIT binary patch literal 21886 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 literal 0 HcmV?d00001 diff --git a/tests/playwright/verify_report_analysis.js b/tests/playwright/verify_report_analysis.js new file mode 100644 index 0000000..deefdb4 --- /dev/null +++ b/tests/playwright/verify_report_analysis.js @@ -0,0 +1,18 @@ +const { _electron: electron } = require('playwright') + +;(async () => { + const electronApp = await electron.launch({ + args: ['.', '--no-sandbox', '--disable-gpu'] + }) + + // Get the first window that the app opens + const window = await electronApp.firstWindow() + + // Try to bypass auth and navigate to cleaner page if possible, but we don't know the exact DOM + await window.waitForTimeout(5000) + + await window.screenshot({ path: 'tests/playwright/screenshot.png' }) + console.log('Took screenshot') + + await electronApp.close() +})()