From 0bb4ea6678a1c4c6db6e59ab818d8699c80e7f84 Mon Sep 17 00:00:00 2001 From: Nicolas Cantu Date: Wed, 10 Sep 2025 17:50:42 +0200 Subject: [PATCH] feat: complete UI implementation with Material-UI and full functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add complete TypeScript types for all document entities - Implement Redux store with document slice and async thunks - Create comprehensive API services layer with external integrations - Build complete UI views with Material-UI components: * UploadView: drag&drop with file preview and status * ExtractionView: structured data display (identities, addresses, properties, contracts) * AnalyseView: CNI verification, credibility scoring, recommendations * ContexteView: external data sources (Cadastre, Géorisques, BODACC, etc.) * ConseilView: LLM analysis with risks and next steps - Add Layout component with navigation tabs - Configure environment variables for backend integration - Fix all TypeScript compilation errors - Replace Grid with Box for better compatibility - Add comprehensive error handling and loading states --- .env | 13 + .env.exemple | 13 + package-lock.json | 785 ++++++++++++++++++++++++++++-- package.json | 5 + src/components/Layout.tsx | 36 ++ src/components/NavigationTabs.tsx | 41 ++ src/router/index.tsx | 18 +- src/services/api.ts | 105 +++- src/store/documentSlice.ts | 108 ++++ src/store/index.ts | 3 +- src/types/index.ts | 94 ++++ src/views/AnalyseView.tsx | 258 +++++++++- src/views/ConseilView.tsx | 247 +++++++++- src/views/ContexteView.tsx | 302 +++++++++++- src/views/ExtractionView.tsx | 292 ++++++++++- src/views/UploadView.tsx | 164 ++++++- 16 files changed, 2399 insertions(+), 85 deletions(-) create mode 100644 .env create mode 100644 .env.exemple create mode 100644 src/components/Layout.tsx create mode 100644 src/components/NavigationTabs.tsx create mode 100644 src/store/documentSlice.ts create mode 100644 src/types/index.ts diff --git a/.env b/.env new file mode 100644 index 0000000..da8cab9 --- /dev/null +++ b/.env @@ -0,0 +1,13 @@ +# Configuration API Backend +VITE_API_URL=http://localhost:8000 + +# Configuration pour le développement +VITE_APP_NAME=4NK IA Front Notarial +VITE_APP_VERSION=0.1.0 + +# Configuration des services externes (optionnel) +VITE_CADASTRE_API_URL=https://apicarto.ign.fr/api/cadastre +VITE_GEORISQUES_API_URL=https://www.georisques.gouv.fr/api +VITE_GEOFONCIER_API_URL=https://api2.geofoncier.fr +VITE_BODACC_API_URL=https://bodacc-datadila.opendatasoft.com/api +VITE_INFOGREFFE_API_URL=https://entreprise.api.gouv.fr diff --git a/.env.exemple b/.env.exemple new file mode 100644 index 0000000..da8cab9 --- /dev/null +++ b/.env.exemple @@ -0,0 +1,13 @@ +# Configuration API Backend +VITE_API_URL=http://localhost:8000 + +# Configuration pour le développement +VITE_APP_NAME=4NK IA Front Notarial +VITE_APP_VERSION=0.1.0 + +# Configuration des services externes (optionnel) +VITE_CADASTRE_API_URL=https://apicarto.ign.fr/api/cadastre +VITE_GEORISQUES_API_URL=https://www.georisques.gouv.fr/api +VITE_GEOFONCIER_API_URL=https://api2.geofoncier.fr +VITE_BODACC_API_URL=https://bodacc-datadila.opendatasoft.com/api +VITE_INFOGREFFE_API_URL=https://entreprise.api.gouv.fr diff --git a/package-lock.json b/package-lock.json index 65d7777..5da5387 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,15 @@ "name": "4nk-ia-front4nk", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.2", + "@mui/material": "^7.3.2", "@reduxjs/toolkit": "^2.9.0", "axios": "^1.11.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-dropzone": "^14.3.8", "react-redux": "^9.2.0", "react-router-dom": "^7.8.2" }, @@ -85,7 +90,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -138,7 +142,6 @@ "version": "7.28.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", - "dev": true, "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", @@ -170,7 +173,6 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -179,7 +181,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -218,7 +219,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -227,7 +227,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -258,7 +257,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", - "dev": true, "dependencies": { "@babel/types": "^7.28.4" }, @@ -303,7 +301,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -313,7 +310,6 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -327,7 +323,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -345,7 +340,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -479,6 +473,158 @@ "node": ">=18" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", @@ -1141,7 +1287,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -1161,7 +1306,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -1169,19 +1313,262 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.30", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.2.tgz", + "integrity": "sha512-AOyfHjyDKVPGJJFtxOlept3EYEdLoar/RvssBTWVAvDJGIE676dLi2oT/Kx+FoVXFoA/JdV7DEMq/BVWV3KHRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.2.tgz", + "integrity": "sha512-TZWazBjWXBjR6iGcNkbKklnwodcwj0SrChCNHc9BhD9rBgET22J1eFhHsEmvSvru9+opDy3umqAimQjokhfJlQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.3.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.2.tgz", + "integrity": "sha512-qXvbnawQhqUVfH1LMgMaiytP+ZpGoYhnGl7yYq2x57GYzcFL/iPzSZ3L30tlbwEjSVKNYcbiKO8tANR1tadjUg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@mui/core-downloads-tracker": "^7.3.2", + "@mui/system": "^7.3.2", + "@mui/types": "^7.4.6", + "@mui/utils": "^7.3.2", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.2.tgz", + "integrity": "sha512-ha7mFoOyZGJr75xeiO9lugS3joRROjc8tG1u4P50dH0KR7bwhHznVMcYg7MouochUy0OxooJm/OOSpJ7gKcMvg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@mui/utils": "^7.3.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.2.tgz", + "integrity": "sha512-PkJzW+mTaek4e0nPYZ6qLnW5RGa0KN+eRTf5FA2nc7cFZTeM+qebmGibaTLrgQBy3UpcpemaqfzToBNkzuxqew==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.2.tgz", + "integrity": "sha512-9d8JEvZW+H6cVkaZ+FK56R53vkJe3HsTpcjMUtH8v1xK6Y1TjzHdZ7Jck02mGXJsE6MQGWVs3ogRHTQmS9Q/rA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@mui/private-theming": "^7.3.2", + "@mui/styled-engine": "^7.3.2", + "@mui/types": "^7.4.6", + "@mui/utils": "^7.3.2", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.6.tgz", + "integrity": "sha512-NVBbIw+4CDMMppNamVxyTccNv0WxtDb7motWDlMeSC8Oy95saj1TIZMGynPpFLePt3yOD8TskzumeqORCgRGWw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.2.tgz", + "integrity": "sha512-4DMWQGenOdLnM3y/SdFQFwKsCLM+mqxzvoWp9+x2XdEzXapkznauHLiXtSohHs/mc0+5/9UACt1GdugCX2te5g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@mui/types": "^7.4.6", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1228,6 +1615,16 @@ "node": ">=14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz", @@ -1737,11 +2134,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", - "devOptional": true, "dependencies": { "csstype": "^3.0.2" } @@ -1755,6 +2163,15 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", @@ -2317,6 +2734,15 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", @@ -2328,6 +2754,21 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2415,7 +2856,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -2516,6 +2956,15 @@ "node": ">= 16" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2577,6 +3026,31 @@ "node": ">=18" } }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2615,8 +3089,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-urls": { "version": "5.0.0", @@ -2636,7 +3109,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -2737,6 +3209,16 @@ "license": "MIT", "peer": true }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2784,6 +3266,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2890,7 +3381,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -3169,6 +3659,18 @@ "node": ">=16.0.0" } }, + "node_modules/file-selector": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "license": "MIT", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3181,6 +3683,12 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3468,6 +3976,21 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -3552,7 +4075,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3619,6 +4141,27 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -3767,8 +4310,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -3826,7 +4368,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -3840,6 +4381,12 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3930,6 +4477,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -3961,6 +4514,18 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -4772,8 +5337,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "3.3.11", @@ -4812,6 +5376,15 @@ "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4870,7 +5443,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -4898,6 +5470,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -4942,6 +5532,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, "node_modules/path-scurry": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", @@ -4969,6 +5565,15 @@ "node": "20 || >=22" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -4989,8 +5594,7 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -5087,6 +5691,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -5151,6 +5772,23 @@ "react": "^19.1.1" } }, + "node_modules/react-dropzone": { + "version": "14.3.8", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", + "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -5229,6 +5867,22 @@ "react-dom": ">=18" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -5264,11 +5918,30 @@ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -5463,6 +6136,15 @@ "url": "https://github.com/sponsors/cyyynthia" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5628,6 +6310,12 @@ "dev": true, "license": "MIT" }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5640,6 +6328,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -5908,6 +6608,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6454,6 +7160,21 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 076b21f..1a3d134 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,15 @@ "test:ui": "vitest" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.2", + "@mui/material": "^7.3.2", "@reduxjs/toolkit": "^2.9.0", "axios": "^1.11.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-dropzone": "^14.3.8", "react-redux": "^9.2.0", "react-router-dom": "^7.8.2" }, diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx new file mode 100644 index 0000000..ed3ca1c --- /dev/null +++ b/src/components/Layout.tsx @@ -0,0 +1,36 @@ +import React from 'react' +import { AppBar, Toolbar, Typography, Container, Box } from '@mui/material' +import { useNavigate, useLocation } from 'react-router-dom' +import { NavigationTabs } from './NavigationTabs' + +interface LayoutProps { + children: React.ReactNode +} + +export const Layout: React.FC = ({ children }) => { + const navigate = useNavigate() + const location = useLocation() + + return ( + + + + navigate('/')} + > + 4NK IA - Front Notarial + + + + + + + + {children} + + + ) +} diff --git a/src/components/NavigationTabs.tsx b/src/components/NavigationTabs.tsx new file mode 100644 index 0000000..0df78cc --- /dev/null +++ b/src/components/NavigationTabs.tsx @@ -0,0 +1,41 @@ +import React from 'react' +import { Tabs, Tab, Box } from '@mui/material' +import { useNavigate } from 'react-router-dom' + +interface NavigationTabsProps { + currentPath: string +} + +export const NavigationTabs: React.FC = ({ currentPath }) => { + const navigate = useNavigate() + + const tabs = [ + { label: 'Téléversement', path: '/' }, + { label: 'Extraction', path: '/extraction' }, + { label: 'Analyse', path: '/analyse' }, + { label: 'Contexte', path: '/contexte' }, + { label: 'Conseil', path: '/conseil' }, + ] + + const currentTabIndex = tabs.findIndex(tab => tab.path === currentPath) + + const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { + navigate(tabs[newValue].path) + } + + return ( + + = 0 ? currentTabIndex : 0} + onChange={handleTabChange} + aria-label="navigation tabs" + variant="scrollable" + scrollButtons="auto" + > + {tabs.map((tab, index) => ( + + ))} + + + ) +} diff --git a/src/router/index.tsx b/src/router/index.tsx index 042c16e..7f3de58 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -1,5 +1,6 @@ import { lazy, Suspense } from 'react' import { createBrowserRouter, RouterProvider } from 'react-router-dom' +import { Box, CircularProgress, Typography } from '@mui/material' const UploadView = lazy(() => import('../views/UploadView')) const ExtractionView = lazy(() => import('../views/ExtractionView')) @@ -7,12 +8,19 @@ const AnalyseView = lazy(() => import('../views/AnalyseView')) const ContexteView = lazy(() => import('../views/ContexteView')) const ConseilView = lazy(() => import('../views/ConseilView')) +const LoadingFallback = () => ( + + + Chargement... + +) + const router = createBrowserRouter([ - { path: '/', element: Chargement…}> }, - { path: '/extraction', element: Chargement…}> }, - { path: '/analyse', element: Chargement…}> }, - { path: '/contexte', element: Chargement…}> }, - { path: '/conseil', element: Chargement…}> }, + { path: '/', element: }> }, + { path: '/extraction', element: }> }, + { path: '/analyse', element: }> }, + { path: '/contexte', element: }> }, + { path: '/conseil', element: }> }, ]) export const AppRouter = () => { diff --git a/src/services/api.ts b/src/services/api.ts index 381395e..9bc236d 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -1,4 +1,5 @@ import axios from 'axios' +import type { Document, ExtractionResult, AnalysisResult, ContextResult, ConseilResult } from '../types' const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' @@ -7,13 +8,101 @@ export const apiClient = axios.create({ timeout: 60000, }) -export type DetectionResult = { type: string; confidence: number } +// Intercepteur pour les erreurs +apiClient.interceptors.response.use( + (response) => response, + (error) => { + console.error('API Error:', error) + return Promise.reject(error) + } +) -export const detectDocumentType = async (file: File) => { - const form = new FormData() - form.append('file', file) - const { data } = await apiClient.post('/api/ocr/detect', form, { - headers: { 'Content-Type': 'multipart/form-data' }, - }) - return data +// Services API pour les documents +export const documentApi = { + // Téléversement de document + upload: async (file: File): Promise => { + const formData = new FormData() + formData.append('file', file) + const { data } = await apiClient.post('/api/documents/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + return data + }, + + // Extraction des données + extract: async (documentId: string): Promise => { + const { data } = await apiClient.get(`/api/documents/${documentId}/extract`) + return data + }, + + // Analyse du document + analyze: async (documentId: string): Promise => { + const { data } = await apiClient.get(`/api/documents/${documentId}/analyze`) + return data + }, + + // Données contextuelles + getContext: async (documentId: string): Promise => { + const { data } = await apiClient.get(`/api/documents/${documentId}/context`) + return data + }, + + // Conseil LLM + getConseil: async (documentId: string): Promise => { + const { data } = await apiClient.get(`/api/documents/${documentId}/conseil`) + return data + }, + + // Détection du type de document + detectType: async (file: File): Promise<{ type: string; confidence: number }> => { + const formData = new FormData() + formData.append('file', file) + const { data } = await apiClient.post('/api/ocr/detect', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + return data + }, +} + +// Services API pour les données externes +export const externalApi = { + // Cadastre + cadastre: async (address: string) => { + const cadastreUrl = import.meta.env.VITE_CADASTRE_API_URL + if (!cadastreUrl) throw new Error('Cadastre API URL not configured') + const { data } = await axios.get(`${cadastreUrl}/parcelle`, { params: { q: address } }) + return data + }, + + // Géorisques + georisques: async (coordinates: { lat: number; lng: number }) => { + const georisquesUrl = import.meta.env.VITE_GEORISQUES_API_URL + if (!georisquesUrl) throw new Error('Géorisques API URL not configured') + const { data } = await axios.get(`${georisquesUrl}/risques`, { params: coordinates }) + return data + }, + + // Géofoncier + geofoncier: async (address: string) => { + const geofoncierUrl = import.meta.env.VITE_GEOFONCIER_API_URL + if (!geofoncierUrl) throw new Error('Géofoncier API URL not configured') + const { data } = await axios.get(`${geofoncierUrl}/dossiers`, { params: { address } }) + return data + }, + + // BODACC + bodacc: async (companyName: string) => { + const bodaccUrl = import.meta.env.VITE_BODACC_API_URL + if (!bodaccUrl) throw new Error('BODACC API URL not configured') + const { data } = await axios.get(`${bodaccUrl}/annonces`, { params: { q: companyName } }) + return data + }, + + // Infogreffe + infogreffe: async (siren: string) => { + const infogreffeUrl = import.meta.env.VITE_INFOGREFFE_API_URL + if (!infogreffeUrl) throw new Error('Infogreffe API URL not configured') + const { data } = await axios.get(`${infogreffeUrl}/infogreffe/rcs/extrait`, { params: { siren } }) + return data + }, } diff --git a/src/store/documentSlice.ts b/src/store/documentSlice.ts new file mode 100644 index 0000000..a943aa0 --- /dev/null +++ b/src/store/documentSlice.ts @@ -0,0 +1,108 @@ +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' +import type { Document, ExtractionResult, AnalysisResult, ContextResult, ConseilResult } from '../types' +import { documentApi } from '../services/api' + +interface DocumentState { + documents: Document[] + currentDocument: Document | null + extractionResult: ExtractionResult | null + analysisResult: AnalysisResult | null + contextResult: ContextResult | null + conseilResult: ConseilResult | null + loading: boolean + error: string | null +} + +const initialState: DocumentState = { + documents: [], + currentDocument: null, + extractionResult: null, + analysisResult: null, + contextResult: null, + conseilResult: null, + loading: false, + error: null, +} + +export const uploadDocument = createAsyncThunk( + 'document/upload', + async (file: File) => { + return await documentApi.upload(file) + } +) + +export const extractDocument = createAsyncThunk( + 'document/extract', + async (documentId: string) => { + return await documentApi.extract(documentId) + } +) + +export const analyzeDocument = createAsyncThunk( + 'document/analyze', + async (documentId: string) => { + return await documentApi.analyze(documentId) + } +) + +export const getContextData = createAsyncThunk( + 'document/context', + async (documentId: string) => { + return await documentApi.getContext(documentId) + } +) + +export const getConseil = createAsyncThunk( + 'document/conseil', + async (documentId: string) => { + return await documentApi.getConseil(documentId) + } +) + +const documentSlice = createSlice({ + name: 'document', + initialState, + reducers: { + setCurrentDocument: (state, action: PayloadAction) => { + state.currentDocument = action.payload + }, + clearResults: (state) => { + state.extractionResult = null + state.analysisResult = null + state.contextResult = null + state.conseilResult = null + }, + }, + extraReducers: (builder) => { + builder + .addCase(uploadDocument.pending, (state) => { + state.loading = true + state.error = null + }) + .addCase(uploadDocument.fulfilled, (state, action) => { + state.loading = false + state.documents.push(action.payload) + state.currentDocument = action.payload + }) + .addCase(uploadDocument.rejected, (state, action) => { + state.loading = false + state.error = action.error.message || 'Erreur lors du téléversement' + }) + .addCase(extractDocument.fulfilled, (state, action) => { + state.extractionResult = action.payload + }) + .addCase(analyzeDocument.fulfilled, (state, action) => { + state.analysisResult = action.payload + }) + .addCase(getContextData.fulfilled, (state, action) => { + state.contextResult = action.payload + }) + .addCase(getConseil.fulfilled, (state, action) => { + state.conseilResult = action.payload + }) + }, +}) + +export const { setCurrentDocument, clearResults } = documentSlice.actions +export const documentReducer = documentSlice.reducer diff --git a/src/store/index.ts b/src/store/index.ts index 0ac2339..102780d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,11 +2,12 @@ import { configureStore } from '@reduxjs/toolkit' import { useDispatch, useSelector } from 'react-redux' import type { TypedUseSelectorHook } from 'react-redux' import { appReducer } from './appSlice' +import { documentReducer } from './documentSlice' -// Root placeholder slice will be added later as features grow export const store = configureStore({ reducer: { app: appReducer, + document: documentReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..b3f1b19 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,94 @@ +export interface Document { + id: string + name: string + type: string + size: number + uploadDate: Date + status: 'uploading' | 'processing' | 'completed' | 'error' +} + +export interface Identity { + id: string + type: 'person' | 'company' + firstName?: string + lastName?: string + companyName?: string + birthDate?: string + nationality?: string + address?: Address + confidence: number +} + +export interface Address { + street: string + city: string + postalCode: string + country: string + coordinates?: { lat: number; lng: number } +} + +export interface Property { + id: string + type: 'house' | 'apartment' | 'land' | 'commercial' + address: Address + surface?: number + cadastralReference?: string + value?: number +} + +export interface Contract { + id: string + type: 'sale' | 'rent' | 'inheritance' | 'donation' + parties: Identity[] + property?: Property + amount?: number + date?: string + clauses: string[] +} + +export interface ExtractionResult { + documentId: string + text: string + language: string + documentType: string + identities: Identity[] + addresses: Address[] + properties: Property[] + contracts: Contract[] + signatures: string[] + confidence: number +} + +export interface AnalysisResult { + documentId: string + documentType: string + isCNI: boolean + country?: string + verificationResult?: { + numberValid: boolean + formatValid: boolean + checksumValid: boolean + } + credibilityScore: number + summary: string + recommendations: string[] +} + +export interface ContextResult { + documentId: string + cadastreData?: any + georisquesData?: any + geofoncierData?: any + bodaccData?: any + infogreffeData?: any + lastUpdated: Date +} + +export interface ConseilResult { + documentId: string + analysis: string + recommendations: string[] + risks: string[] + nextSteps: string[] + generatedAt: Date +} diff --git a/src/views/AnalyseView.tsx b/src/views/AnalyseView.tsx index c2f6c77..d8139d3 100644 --- a/src/views/AnalyseView.tsx +++ b/src/views/AnalyseView.tsx @@ -1,8 +1,254 @@ +import { useEffect } from 'react' +import { + Box, + Typography, + Paper, + Card, + CardContent, + Chip, + List, + ListItem, + ListItemText, + ListItemIcon, + Alert, + LinearProgress, +} from '@mui/material' +import { + CheckCircle, + Error, + Warning, + Flag, + Security, + Assessment, + Info, +} from '@mui/icons-material' +import { useAppDispatch, useAppSelector } from '../store' +import { analyzeDocument } from '../store/documentSlice' +import { Layout } from '../components/Layout' + export default function AnalyseView() { - return ( -
-

Analyse

-

Cas CNI: pays, vérification numéro, score de vraisemblance, avis. Autres: synthèse et score.

-
+ const dispatch = useAppDispatch() + const { currentDocument, analysisResult, loading } = useAppSelector( + (state) => state.document ) -} + + useEffect(() => { + if (currentDocument && !analysisResult) { + dispatch(analyzeDocument(currentDocument.id)) + } + }, [currentDocument, analysisResult, dispatch]) + + if (!currentDocument) { + return ( + + + Veuillez d'abord téléverser et sélectionner un document. + + + ) + } + + if (loading) { + return ( + + + + Analyse en cours... + + + ) + } + + if (!analysisResult) { + return ( + + + Aucun résultat d'analyse disponible. + + + ) + } + + const getScoreColor = (score: number) => { + if (score >= 0.8) return 'success' + if (score >= 0.6) return 'warning' + return 'error' + } + + const getScoreIcon = (score: number) => { + if (score >= 0.8) return + if (score >= 0.6) return + return + } + + return ( + + + Analyse du document + + + + {/* Résumé général */} + + + Résumé de l'analyse + + + } + label={`Score de vraisemblance: ${(analysisResult.credibilityScore * 100).toFixed(1)}%`} + color={getScoreColor(analysisResult.credibilityScore) as any} + variant="filled" + /> + } + label={`Type: ${analysisResult.documentType}`} + color="primary" + variant="outlined" + /> + {analysisResult.isCNI && ( + } + label={`Pays: ${analysisResult.country}`} + color="secondary" + variant="outlined" + /> + )} + + + + {/* Cas CNI */} + {analysisResult.isCNI && ( + + + + + Vérification CNI + + {analysisResult.verificationResult && ( + + + + {analysisResult.verificationResult.numberValid ? ( + + ) : ( + + )} + + + + + + {analysisResult.verificationResult.formatValid ? ( + + ) : ( + + )} + + + + + + {analysisResult.verificationResult.checksumValid ? ( + + ) : ( + + )} + + + + + )} + + + )} + + + {/* Score de vraisemblance */} + + + + + Score de vraisemblance + + + {getScoreIcon(analysisResult.credibilityScore)} + + {(analysisResult.credibilityScore * 100).toFixed(1)}% + + + + + {analysisResult.credibilityScore >= 0.8 + ? 'Document très fiable' + : analysisResult.credibilityScore >= 0.6 + ? 'Document moyennement fiable' + : 'Document peu fiable - vérification recommandée'} + + + + + + {/* Synthèse */} + + + + + Synthèse + + + {analysisResult.summary} + + + + + + + {/* Recommandations */} + + + + Recommandations + + + {analysisResult.recommendations.map((recommendation, index) => ( + + + + + + + ))} + + + + + + ) +} \ No newline at end of file diff --git a/src/views/ConseilView.tsx b/src/views/ConseilView.tsx index 11f0a39..5368850 100644 --- a/src/views/ConseilView.tsx +++ b/src/views/ConseilView.tsx @@ -1,8 +1,243 @@ +import { useEffect } from 'react' +import { + Box, + Typography, + Paper, + Card, + CardContent, + List, + ListItem, + ListItemText, + ListItemIcon, + Alert, + Chip, + Button, + CircularProgress, +} from '@mui/material' +import { + Lightbulb, + Warning, + CheckCircle, + TrendingUp, + Schedule, + Psychology, +} from '@mui/icons-material' +import { useAppDispatch, useAppSelector } from '../store' +import { getConseil } from '../store/documentSlice' +import { Layout } from '../components/Layout' + export default function ConseilView() { - return ( -
-

Conseil

-

Restitution LLM d'une analyse du document et recommandations.

-
+ const dispatch = useAppDispatch() + const { currentDocument, conseilResult, loading } = useAppSelector( + (state) => state.document ) -} + + useEffect(() => { + if (currentDocument && !conseilResult) { + dispatch(getConseil(currentDocument.id)) + } + }, [currentDocument, conseilResult, dispatch]) + + if (!currentDocument) { + return ( + + + Veuillez d'abord téléverser et sélectionner un document. + + + ) + } + + if (loading) { + return ( + + + + Génération des conseils LLM... + + + ) + } + + if (!conseilResult) { + return ( + + + Aucun conseil disponible. + + + ) + } + + const getRiskColor = (risk: string) => { + if (risk.toLowerCase().includes('élevé') || risk.toLowerCase().includes('critique')) { + return 'error' + } + if (risk.toLowerCase().includes('moyen') || risk.toLowerCase().includes('modéré')) { + return 'warning' + } + return 'info' + } + + return ( + + + + Conseil LLM + + + + {/* Analyse LLM */} + + + + + Analyse LLM + + + + {conseilResult.analysis} + + + + Généré le {new Date(conseilResult.generatedAt).toLocaleString()} + + + + + + {/* Recommandations */} + + + + + + Recommandations ({conseilResult.recommendations.length}) + + + {conseilResult.recommendations.map((recommendation, index) => ( + + + + + + + ))} + + + + + + {/* Risques identifiés */} + + + + + + Risques identifiés ({conseilResult.risks.length}) + + + {conseilResult.risks.map((risk, index) => ( + + + + + + + ))} + + + + + + + {/* Prochaines étapes */} + + + + + Prochaines étapes recommandées + + + {conseilResult.nextSteps.map((step, index) => ( + + + + + + + ))} + + + + + {/* Actions */} + + + + Actions + + + + + + + + + + {/* Résumé exécutif */} + + + Résumé exécutif + + + + + + + + Cette analyse LLM a été générée automatiquement et doit être validée par un expert notarial. + + + + + ) +} \ No newline at end of file diff --git a/src/views/ContexteView.tsx b/src/views/ContexteView.tsx index 1d2c322..20d46f1 100644 --- a/src/views/ContexteView.tsx +++ b/src/views/ContexteView.tsx @@ -1,8 +1,298 @@ +import { useEffect } from 'react' +import { + Box, + Typography, + Paper, + Card, + CardContent, + Chip, + Alert, + Button, + Accordion, + AccordionSummary, + AccordionDetails, + CircularProgress, +} from '@mui/material' +import { + ExpandMore, + LocationOn, + Warning, + CheckCircle, + Error, + Public, + Business, + Home, +} from '@mui/icons-material' +import { useAppDispatch, useAppSelector } from '../store' +import { getContextData } from '../store/documentSlice' +import { Layout } from '../components/Layout' + export default function ContexteView() { - return ( -
-

Contexte

-

Recherche d'informations contextuelles (cadastre, géorisques, géofoncier, etc.).

-
+ const dispatch = useAppDispatch() + const { currentDocument, contextResult, loading } = useAppSelector( + (state) => state.document ) -} + + useEffect(() => { + if (currentDocument && !contextResult) { + dispatch(getContextData(currentDocument.id)) + } + }, [currentDocument, contextResult, dispatch]) + + if (!currentDocument) { + return ( + + + Veuillez d'abord téléverser et sélectionner un document. + + + ) + } + + if (loading) { + return ( + + + + Recherche d'informations contextuelles... + + + ) + } + + if (!contextResult) { + return ( + + + Aucune donnée contextuelle disponible. + + + ) + } + + const getStatusIcon = (hasData: boolean) => { + return hasData ? : + } + + const getStatusColor = (hasData: boolean) => { + return hasData ? 'success' : 'error' + } + + return ( + + + Informations contextuelles + + + + {/* Résumé des sources */} + + + Sources de données consultées + + + + + + + + + + Dernière mise à jour: {new Date(contextResult.lastUpdated).toLocaleString()} + + + + {/* Données cadastrales */} + + }> + + + Données cadastrales + + + + + {contextResult.cadastreData ? ( + + + {JSON.stringify(contextResult.cadastreData, null, 2)} + + + ) : ( + + Aucune donnée cadastrale trouvée pour ce document. + + )} + + + + {/* Données Géorisques */} + + }> + + + Données Géorisques + + + + + {contextResult.georisquesData ? ( + + + {JSON.stringify(contextResult.georisquesData, null, 2)} + + + ) : ( + + Aucune donnée Géorisques trouvée pour ce document. + + )} + + + + {/* Données Géofoncier */} + + }> + + + Données Géofoncier + + + + + {contextResult.geofoncierData ? ( + + + {JSON.stringify(contextResult.geofoncierData, null, 2)} + + + ) : ( + + Aucune donnée Géofoncier trouvée pour ce document. + + )} + + + + {/* Données BODACC */} + + }> + + + Données BODACC + + + + + {contextResult.bodaccData ? ( + + + {JSON.stringify(contextResult.bodaccData, null, 2)} + + + ) : ( + + Aucune donnée BODACC trouvée pour ce document. + + )} + + + + {/* Données Infogreffe */} + + }> + + + Données Infogreffe + + + + + {contextResult.infogreffeData ? ( + + + {JSON.stringify(contextResult.infogreffeData, null, 2)} + + + ) : ( + + Aucune donnée Infogreffe trouvée pour ce document. + + )} + + + + {/* Actions */} + + + + Actions + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/views/ExtractionView.tsx b/src/views/ExtractionView.tsx index b739bde..e7c4ab1 100644 --- a/src/views/ExtractionView.tsx +++ b/src/views/ExtractionView.tsx @@ -1,8 +1,288 @@ +import { useEffect } from 'react' +import { + Box, + Typography, + Paper, + Card, + CardContent, + Chip, + List, + ListItem, + ListItemText, + Alert, + CircularProgress, +} from '@mui/material' +import { + Person, + LocationOn, + Home, + Description, + Language, + Verified, +} from '@mui/icons-material' +import { useAppDispatch, useAppSelector } from '../store' +import { extractDocument } from '../store/documentSlice' +import { Layout } from '../components/Layout' + export default function ExtractionView() { - return ( -
-

Extraction

-

Affichage des informations extraites, identités, lieux, biens, clauses, signatures, langue, tags.

-
+ const dispatch = useAppDispatch() + const { currentDocument, extractionResult, loading } = useAppSelector( + (state) => state.document ) -} + + useEffect(() => { + if (currentDocument && !extractionResult) { + dispatch(extractDocument(currentDocument.id)) + } + }, [currentDocument, extractionResult, dispatch]) + + if (!currentDocument) { + return ( + + + Veuillez d'abord téléverser et sélectionner un document. + + + ) + } + + if (loading) { + return ( + + + + Extraction en cours... + + + ) + } + + if (!extractionResult) { + return ( + + + Aucun résultat d'extraction disponible. + + + ) + } + + return ( + + + Extraction des données + + + + {/* Informations générales */} + + + Informations générales + + + } + label={`Langue: ${extractionResult.language}`} + color="primary" + variant="outlined" + /> + } + label={`Type: ${extractionResult.documentType}`} + color="secondary" + variant="outlined" + /> + } + label={`Confiance: ${(extractionResult.confidence * 100).toFixed(1)}%`} + color={extractionResult.confidence > 0.8 ? 'success' : 'warning'} + variant="outlined" + /> + + + + + {/* Identités */} + + + + + + Identités ({extractionResult.identities.length}) + + + {extractionResult.identities.map((identity, index) => ( + + + + Type: {identity.type} + + {identity.birthDate && ( + + Naissance: {identity.birthDate} + + )} + {identity.nationality && ( + + Nationalité: {identity.nationality} + + )} + + Confiance: {(identity.confidence * 100).toFixed(1)}% + + + } + /> + + ))} + + + + + + {/* Adresses */} + + + + + + Adresses ({extractionResult.addresses.length}) + + + {extractionResult.addresses.map((address, index) => ( + + + + ))} + + + + + + + + {/* Biens */} + + + + + + Biens ({extractionResult.properties.length}) + + + {extractionResult.properties.map((property, index) => ( + + + + {property.address.street} + + {property.surface && ( + + Surface: {property.surface} m² + + )} + {property.cadastralReference && ( + + Cadastre: {property.cadastralReference} + + )} + + } + /> + + ))} + + + + + + {/* Contrats */} + + + + + + Contrats ({extractionResult.contracts.length}) + + + {extractionResult.contracts.map((contract, index) => ( + + + + Parties: {contract.parties.length} + + {contract.date && ( + + Date: {contract.date} + + )} + + Clauses: {contract.clauses.length} + + + } + /> + + ))} + + + + + + + {/* Signatures */} + + + + Signatures détectées ({extractionResult.signatures.length}) + + + {extractionResult.signatures.map((signature, index) => ( + + + + ))} + + + + + {/* Texte extrait */} + + + + Texte extrait + + + + {extractionResult.text} + + + + + + + ) +} \ No newline at end of file diff --git a/src/views/UploadView.tsx b/src/views/UploadView.tsx index 2a706c1..cd8d342 100644 --- a/src/views/UploadView.tsx +++ b/src/views/UploadView.tsx @@ -1,25 +1,159 @@ -import { useState } from 'react' +import { useCallback } from 'react' +import { useDropzone } from 'react-dropzone' +import { + Box, + Typography, + Paper, + List, + ListItem, + ListItemText, + ListItemIcon, + CircularProgress, + Alert, + Button, + Chip, +} from '@mui/material' +import { + CloudUpload, + CheckCircle, + Error, + HourglassEmpty, +} from '@mui/icons-material' +import { useAppDispatch, useAppSelector } from '../store' +import { uploadDocument, setCurrentDocument } from '../store/documentSlice' +import { Layout } from '../components/Layout' export default function UploadView() { - const [files, setFiles] = useState([]) + const dispatch = useAppDispatch() + const { documents, error } = useAppSelector((state) => state.document) - function onFilesSelected(event: React.ChangeEvent) { - if (event.target.files) { - setFiles(Array.from(event.target.files)) + const onDrop = useCallback( + (acceptedFiles: File[]) => { + acceptedFiles.forEach((file) => { + dispatch(uploadDocument(file)) + }) + }, + [dispatch] + ) + + const { getRootProps, getInputProps, isDragActive } = useDropzone({ + onDrop, + accept: { + 'application/pdf': ['.pdf'], + 'image/*': ['.png', '.jpg', '.jpeg', '.tiff'], + }, + multiple: true, + }) + + const getStatusIcon = (status: string) => { + switch (status) { + case 'completed': + return + case 'error': + return + case 'processing': + return + default: + return + } + } + + const getStatusColor = (status: string) => { + switch (status) { + case 'completed': + return 'success' + case 'error': + return 'error' + case 'processing': + return 'warning' + default: + return 'default' } } return ( -
-

Téléversement de documents

- - {files.length > 0 && ( -
    - {files.map((file) => ( -
  • {file.name}
  • - ))} -
+ + + Téléversement de documents + + + + + + + {isDragActive + ? 'Déposez les fichiers ici...' + : 'Glissez-déposez vos documents ou cliquez pour sélectionner'} + + + Formats acceptés: PDF, PNG, JPG, JPEG, TIFF + + + + {error && ( + + {error} + )} -
+ + {documents.length > 0 && ( + + + Documents téléversés ({documents.length}) + + + {documents.map((doc) => ( + dispatch(setCurrentDocument(doc))} + disabled={doc.status !== 'completed'} + > + Analyser + + } + > + {getStatusIcon(doc.status)} + + + + + {(doc.size / 1024 / 1024).toFixed(2)} MB + + + } + /> + + ))} + + + )} + ) }