mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-12-09 00:05:21 +01:00
Merge branch 'dev' into copilot/add-ai-settings-page
This commit is contained in:
commit
7f615d578e
66 changed files with 10792 additions and 370 deletions
214
src-ui/package-lock.json
generated
214
src-ui/package-lock.json
generated
|
|
@ -1026,6 +1026,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.9",
|
||||
"version": "0.2003.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.10.tgz",
|
||||
"integrity": "sha512-2SWetxJzS8gRX6OKQstkWx37VRvZVgcEBDLsDSaeTjpnwh81A+niZQjAVRdwL0NEt1Wixk/RxfeUuCmdyyHvhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"rxjs": "7.8.2"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -1038,6 +1045,9 @@
|
|||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.9.tgz",
|
||||
"integrity": "sha512-DCzHY+EQ98u0h1n8s9add1KVSNWco1RW/Rl8TRkEuGmRQ43MpOfTIZQvlnnqaeMcNH0fZ4zkybVBDj7korJbZg==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.10.tgz",
|
||||
"integrity": "sha512-SWGh1ASXEXtzFv/OSlmYGsYlIWHNeZRWkwkBe6mPfxZMX4JZ4HKbxmMtKV9hifvFdITU393IxPH5JXlFZJpZhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -1046,6 +1056,10 @@
|
|||
"@angular-devkit/build-webpack": "0.2003.9",
|
||||
"@angular-devkit/core": "20.3.9",
|
||||
"@angular/build": "20.3.9",
|
||||
"@angular-devkit/architect": "0.2003.10",
|
||||
"@angular-devkit/build-webpack": "0.2003.10",
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"@angular/build": "20.3.10",
|
||||
"@babel/core": "7.28.3",
|
||||
"@babel/generator": "7.28.3",
|
||||
"@babel/helper-annotate-as-pure": "7.27.3",
|
||||
|
|
@ -1057,6 +1071,7 @@
|
|||
"@babel/runtime": "7.28.3",
|
||||
"@discoveryjs/json-ext": "0.6.3",
|
||||
"@ngtools/webpack": "20.3.9",
|
||||
"@ngtools/webpack": "20.3.10",
|
||||
"ansi-colors": "4.1.3",
|
||||
"autoprefixer": "10.4.21",
|
||||
"babel-loader": "10.0.0",
|
||||
|
|
@ -1112,6 +1127,7 @@
|
|||
"@angular/platform-server": "^20.0.0",
|
||||
"@angular/service-worker": "^20.0.0",
|
||||
"@angular/ssr": "^20.3.9",
|
||||
"@angular/ssr": "^20.3.10",
|
||||
"@web/test-runner": "^0.20.0",
|
||||
"browser-sync": "^3.0.2",
|
||||
"jest": "^29.5.0 || ^30.2.0",
|
||||
|
|
@ -1278,6 +1294,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.2003.9",
|
||||
"version": "0.2003.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.10.tgz",
|
||||
"integrity": "sha512-/e76O5MnoAplV+LW6XAWyd8e1KR1HqRTCSTngLMO+VMADbcQkD4i01ouridlxVLKkGDg83hvASUz2M6x0duZ9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.2003.10",
|
||||
"rxjs": "7.8.2"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -1294,6 +1317,9 @@
|
|||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.9.tgz",
|
||||
"integrity": "sha512-bXsAGIUb4p60x548YmvnMvjwd3FwWz6re1uTM7dV0XH8nQn3XMhOQ3Q3sAckzJHxkDuaRhB3K/a4kupoOmVfTQ==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.10.tgz",
|
||||
"integrity": "sha512-COOT2eVebDwHhwENk12VR6m0wjL8D7p0dncEHF15zaBt1IXEnVhGESjSrs5klnPnt5T55qCBKyCTaeK7i/cS8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -1326,6 +1352,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.9",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.10.tgz",
|
||||
"integrity": "sha512-2N2WF9lj+kr3uCG4+vFadYCL5hAT4dxMgzwScSdOqSd0O+GZD0CzKbDzlfvWIWC/ZealC5Sh4dFEQaRfmy72xA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"magic-string": "0.30.17",
|
||||
"ora": "8.2.0",
|
||||
|
|
@ -1446,11 +1479,15 @@
|
|||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.9.tgz",
|
||||
"integrity": "sha512-Ulimvg6twPSCraaZECEmENfKBlD4M1yqeHlg6dCzFNM4xcwaGUnuG6O3cIQD59DaEvaG73ceM2y8ftYdxAwFow==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.10.tgz",
|
||||
"integrity": "sha512-nQrj1nMNZygYDilThc7hPrD6/NIWF/BOSgMfE4VkXQp8d0QronP3HFJ/h77MeoughMRFRhix0pqQSlXJQ2SGTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "2.3.0",
|
||||
"@angular-devkit/architect": "0.2003.9",
|
||||
"@angular-devkit/architect": "0.2003.10",
|
||||
"@babel/core": "7.28.3",
|
||||
"@babel/helper-annotate-as-pure": "7.27.3",
|
||||
"@babel/helper-split-export-declaration": "7.24.7",
|
||||
|
|
@ -1493,6 +1530,7 @@
|
|||
"@angular/platform-server": "^20.0.0",
|
||||
"@angular/service-worker": "^20.0.0",
|
||||
"@angular/ssr": "^20.3.9",
|
||||
"@angular/ssr": "^20.3.10",
|
||||
"karma": "^6.4.0",
|
||||
"less": "^4.2.0",
|
||||
"ng-packagr": "^20.0.0",
|
||||
|
|
@ -1545,6 +1583,9 @@
|
|||
"version": "20.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.12.tgz",
|
||||
"integrity": "sha512-hz8GtiMy3N9/e8407ZfrByHD5GEC4SkWtxyUknWuTM9P88AOie0jDZ6CfQg9gQ0OJX+6BAbJV3RpYZA1uzNUqA==",
|
||||
"version": "20.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.13.tgz",
|
||||
"integrity": "sha512-h1jTkCmJ/rEQQMkxgKFMCBOrMfjZEnppgdekNmSTerwdVp4vdosTDTzFH/kwiOGFeRClffmvqQ2XLG8mQOKOtA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parse5": "^8.0.0",
|
||||
|
|
@ -1570,6 +1611,19 @@
|
|||
"@listr2/prompt-adapter-inquirer": "3.0.1",
|
||||
"@modelcontextprotocol/sdk": "1.17.3",
|
||||
"@schematics/angular": "20.3.9",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.10.tgz",
|
||||
"integrity": "sha512-CQzXScurBXSuMMn0jf6UYDItdggaM3bHYERKL4cUG1z5JqSozVFin1+TB1EjWYkddwdgC10R5xQurdMb+ahRNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.2003.10",
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"@angular-devkit/schematics": "20.3.10",
|
||||
"@inquirer/prompts": "7.8.2",
|
||||
"@listr2/prompt-adapter-inquirer": "3.0.1",
|
||||
"@modelcontextprotocol/sdk": "1.17.3",
|
||||
"@schematics/angular": "20.3.10",
|
||||
"@yarnpkg/lockfile": "1.1.0",
|
||||
"algoliasearch": "5.35.0",
|
||||
"ini": "5.0.0",
|
||||
|
|
@ -1595,6 +1649,9 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.10.tgz",
|
||||
"integrity": "sha512-12fEzvKbEqjqy1fSk9DMYlJz6dF1MJVXuC5BB+oWWJpd+2lfh4xJ62pkvvLGAICI89hfM5n9Cy5kWnXwnqPZsA==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.12.tgz",
|
||||
"integrity": "sha512-rFcDfe67ffrb435C6t2lc27WGbizeOcgce30tUhH0iezwEvU+kHHWezXXX6Ylx3TFgqGkhcxL0fliuFYrpM1Vw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -1604,6 +1661,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "20.3.10",
|
||||
"@angular/core": "20.3.12",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -1611,6 +1669,9 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.10.tgz",
|
||||
"integrity": "sha512-cW939Lr8GZjPSYfbQKIDNrUaHWmn2M+zBbERThfq5skLuY+xM60bJFv4NqBekfX6YqKLCY62ilUZlnImYIXaqA==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.12.tgz",
|
||||
"integrity": "sha512-bGESKz97nWiEQ/sydTq/Lzv3zlLvDb8t0msLG5Xti7Ch1EdLddXS8d2D/zFsjiGbAUKVsT6RgPCLHYoi4ocbhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -1623,6 +1684,9 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.10.tgz",
|
||||
"integrity": "sha512-9BemvpFxA26yIVdu8ROffadMkEdlk/AQQ2Jb486w7RPkrvUQ0pbEJukhv9aryJvhbMopT66S5H/j4ipOUMzmzQ==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.12.tgz",
|
||||
"integrity": "sha512-3SJkexqsydYjIs0iLiJr5AdwkvumpzvjJM6s76iaxXHkRll5k/vM0wqkXLlSIwieBrecO9D4J73lDLWDevXl5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.28.3",
|
||||
|
|
@ -1643,6 +1707,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.3.10",
|
||||
"@angular/compiler": "20.3.12",
|
||||
"typescript": ">=5.8 <6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
|
|
@ -1655,6 +1720,9 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.10.tgz",
|
||||
"integrity": "sha512-g99Qe+NOVo72OLxowVF9NjCckswWYHmvO7MgeiZTDJbTjF9tXH96dMx7AWq76/GUinV10sNzDysVW16NoAbCRQ==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.12.tgz",
|
||||
"integrity": "sha512-K7vibMr55a7+EsuDhkg4Pk+ELuMm12olllwqL/CiQUcHXZ9Zgc4KYGTUuxWB69qJCG90gdSZS7tm5Dx0wDcyjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -1664,6 +1732,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "20.3.10",
|
||||
"@angular/compiler": "20.3.12",
|
||||
"rxjs": "^6.5.3 || ^7.4.0",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
|
|
@ -1680,6 +1749,9 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.10.tgz",
|
||||
"integrity": "sha512-9yWr51EUauTEINB745AaHwZNTHLpXIm4uxuykxzOg+g2QskEgVfH26uS8G2ogdNuwYpB8wnsXWr34qhM3qgOWw==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.12.tgz",
|
||||
"integrity": "sha512-O0Jy8ScaN3qVipDfR4s0SIxGrz/+MbCdmR05ZYVWf1W5P3dvETKt9WNjX9fYYV47GdgSveyFjuCR2NvWlv94zA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -1691,6 +1763,9 @@
|
|||
"@angular/common": "20.3.10",
|
||||
"@angular/core": "20.3.10",
|
||||
"@angular/platform-browser": "20.3.10",
|
||||
"@angular/common": "20.3.12",
|
||||
"@angular/core": "20.3.12",
|
||||
"@angular/platform-browser": "20.3.12",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -1698,6 +1773,9 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.10.tgz",
|
||||
"integrity": "sha512-kw9yypjUdZP2uEknpNJq8Dryj4xAjwK0aIun0Wz2ZlnP8J6yH0U56qqKRQaqusKjt7fe1OFmJ2XbFEb0LrNlMw==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.12.tgz",
|
||||
"integrity": "sha512-wolRAeaWCh6kLNZitrlnQYm9nPaGQ2OwO04I10p1dEY2gC/nCMdJvh3umaOHTD2lN64ebZUxS5gJS8+PPTOcmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.28.3",
|
||||
|
|
@ -1722,6 +1800,14 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.10.tgz",
|
||||
"integrity": "sha512-UV8CGoB5P3FmJciI3/I/n3L7C3NVgGh7bIlZ1BaB/qJDtv0Wq0rRAGwmT/Z3gwmrRtfHZWme7/CeQ2CYJmMyUQ==",
|
||||
"@angular/compiler": "20.3.12",
|
||||
"@angular/compiler-cli": "20.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser": {
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.12.tgz",
|
||||
"integrity": "sha512-14KQsXZyaQhbRwFz1W58CtbXQc9L+mfuHBgwQjQo99422Yk0ye5WVMb6DHH7dH671qFVqL0XL7zdOPBebaAnJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -1733,6 +1819,9 @@
|
|||
"@angular/animations": "20.3.10",
|
||||
"@angular/common": "20.3.10",
|
||||
"@angular/core": "20.3.10"
|
||||
"@angular/animations": "20.3.12",
|
||||
"@angular/common": "20.3.12",
|
||||
"@angular/core": "20.3.12"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/animations": {
|
||||
|
|
@ -1744,6 +1833,9 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.10.tgz",
|
||||
"integrity": "sha512-gtZPCuxfxxkMzHYBdTU9tJeTiHj+Aty3C408DJGtGU+7rZgKt9hDC14vQN9OVzB9Ly9Jwj2yr8u7AH80TxxCJw==",
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.12.tgz",
|
||||
"integrity": "sha512-VviTUCpcbwErQjWd+EZklQf1Fw1FtXui6ey4rEb9g9mCEJ/o08LkM7mWV5IoE6QNCfbgkfgNjEJSJvWe409Mow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -1762,6 +1854,16 @@
|
|||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.10.tgz",
|
||||
"integrity": "sha512-Z03cfH1jgQ7XMDJj4R8qAGqivcvhdG3wYBwaiN1K1ODBgPhbFKNeD4stKqYp7xBNtswmM2O2jMxrL/Djwju4Gg==",
|
||||
"@angular/common": "20.3.12",
|
||||
"@angular/compiler": "20.3.12",
|
||||
"@angular/core": "20.3.12",
|
||||
"@angular/platform-browser": "20.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/router": {
|
||||
"version": "20.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.12.tgz",
|
||||
"integrity": "sha512-hUipb9JI/Euy3bdlhzkcWlw3cTyssPTVTDwSvyGxWO4i+UKATQYmxh8EDOrDYzFp6Aexiy0Hff/H8umdsn6ZdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
|
|
@ -1773,6 +1875,9 @@
|
|||
"@angular/common": "20.3.10",
|
||||
"@angular/core": "20.3.10",
|
||||
"@angular/platform-browser": "20.3.10",
|
||||
"@angular/common": "20.3.12",
|
||||
"@angular/core": "20.3.12",
|
||||
"@angular/platform-browser": "20.3.12",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -3906,6 +4011,9 @@
|
|||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz",
|
||||
"integrity": "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==",
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
|
||||
"integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
|
|
@ -3918,6 +4026,9 @@
|
|||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz",
|
||||
"integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==",
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
|
||||
"integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
|
|
@ -4667,11 +4778,15 @@
|
|||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.1.tgz",
|
||||
"integrity": "sha512-rOcLotrptYIy59SGQhKlU0xBg1vvcVl2FdPIEclUvKHh0wo12OfGkId/01PIMJ/V+EimJ77t085YabgnQHBa5A==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz",
|
||||
"integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^1.0.2",
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/figures": "^1.0.15",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
|
|
@ -4714,6 +4829,9 @@
|
|||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.1.tgz",
|
||||
"integrity": "sha512-hzGKIkfomGFPgxKmnKEKeA+uCYBqC+TKtRx5LgyHRCrF6S2MliwRIjp3sUaWwVzMp7ZXVs8elB0Tfe682Rpg4w==",
|
||||
"version": "10.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz",
|
||||
"integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -4722,6 +4840,7 @@
|
|||
"@inquirer/type": "^3.0.10",
|
||||
"cli-width": "^4.1.0",
|
||||
"mute-stream": "^3.0.0",
|
||||
"mute-stream": "^2.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"wrap-ansi": "^6.2.0",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
|
|
@ -4746,6 +4865,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"version": "4.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz",
|
||||
"integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/external-editor": "^1.0.3",
|
||||
"@inquirer/type": "^3.0.10"
|
||||
},
|
||||
|
|
@ -4769,6 +4895,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"version": "4.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz",
|
||||
"integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
},
|
||||
|
|
@ -4824,6 +4957,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz",
|
||||
"integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -4846,6 +4986,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"version": "3.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz",
|
||||
"integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -4864,11 +5011,15 @@
|
|||
"version": "4.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.22.tgz",
|
||||
"integrity": "sha512-CbdqK1ioIr0Y3akx03k/+Twf+KSlHjn05hBL+rmubMll7PsDTGH0R4vfFkr+XrkB0FOHrjIwVP9crt49dgt+1g==",
|
||||
"version": "4.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz",
|
||||
"integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^1.0.2",
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -4921,6 +5072,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz",
|
||||
"integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
},
|
||||
|
|
@ -4944,6 +5102,13 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz",
|
||||
"integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/figures": "^1.0.15",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
|
|
@ -4964,11 +5129,15 @@
|
|||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.1.tgz",
|
||||
"integrity": "sha512-E9hbLU4XsNe2SAOSsFrtYtYQDVi1mfbqJrPDvXKnGlnRiApBdWMJz7r3J2Ff38AqULkPUD3XjQMD4492TymD7Q==",
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz",
|
||||
"integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@inquirer/ansi": "^1.0.2",
|
||||
"@inquirer/core": "^10.3.1",
|
||||
"@inquirer/core": "^10.3.2",
|
||||
"@inquirer/figures": "^1.0.15",
|
||||
"@inquirer/type": "^3.0.10",
|
||||
"yoctocolors-cjs": "^2.1.3"
|
||||
|
|
@ -5133,6 +5302,9 @@
|
|||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -6503,6 +6675,9 @@
|
|||
"version": "20.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.9.tgz",
|
||||
"integrity": "sha512-3h5laY9+kP7Tzociy3Lg5sMfpTTKMU+XbLQAHxnIvywHLD6r/fgVkwRli8GZf5JFMTwAkul0AQPKom9SCSWJLg==",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.10.tgz",
|
||||
"integrity": "sha512-W/+CGQFhmYEMJ/YgkC5p9khkxu2ocrvM0Pe0GxcUldrpBpdm1GCphEH1kTo7MeCupUK4/6rXGUt+GoA6PYchOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -7674,6 +7849,14 @@
|
|||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.9",
|
||||
"@angular-devkit/schematics": "20.3.9",
|
||||
"version": "20.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.10.tgz",
|
||||
"integrity": "sha512-F9ntS2CElpoWlENf4b03nwdTcN9Ri0Nb4SAE/pfRw3In09h2UHxYyf1ex9jqQt70xltDg4wvyuc3mMs+JlSx9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "20.3.10",
|
||||
"@angular-devkit/schematics": "20.3.10",
|
||||
"jsonc-parser": "3.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -7803,6 +7986,9 @@
|
|||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
|
@ -8229,6 +8415,9 @@
|
|||
"version": "17.0.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz",
|
||||
"integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==",
|
||||
"version": "17.0.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
|
||||
"integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -9559,6 +9748,9 @@
|
|||
"version": "2.8.27",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.27.tgz",
|
||||
"integrity": "sha512-2CXFpkjVnY2FT+B6GrSYxzYf65BJWEqz5tIRHCvNsZZ2F3CmsCB37h8SpYgKG7y9C4YAeTipIPWG7EmFmhAeXA==",
|
||||
"version": "2.8.28",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz",
|
||||
"integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
|
|
@ -10830,6 +11022,9 @@
|
|||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.3.0.tgz",
|
||||
"integrity": "sha512-Qq68+VkJlc8tjnPV1i7HtbIn7ohmjZa88qUvHMIK0ZKUXMCuV45cT7cEXALPUmeXCe0q1DWQkQTemHVaLIFSrg==",
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz",
|
||||
"integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -10847,6 +11042,9 @@
|
|||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz",
|
||||
"integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
|
||||
"integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -11092,6 +11290,9 @@
|
|||
"version": "1.5.250",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz",
|
||||
"integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==",
|
||||
"version": "1.5.253",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.253.tgz",
|
||||
"integrity": "sha512-O0tpQ/35rrgdiGQ0/OFWhy1itmd9A6TY9uQzlqj3hKSu/aYpe7UIn5d7CU2N9myH6biZiWF3VMZVuup8pw5U9w==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
|
|
@ -14545,6 +14746,9 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -15782,6 +15986,13 @@
|
|||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
|
|
@ -15993,6 +16204,9 @@
|
|||
"version": "3.80.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.80.0.tgz",
|
||||
"integrity": "sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==",
|
||||
"version": "3.85.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz",
|
||||
"integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~20.3.12",
|
||||
"@angular/cdk": "^20.2.6",
|
||||
"@angular/common": "~20.3.2",
|
||||
"@angular/compiler": "~20.3.2",
|
||||
|
|
|
|||
120
src-ui/pnpm-lock.yaml
generated
120
src-ui/pnpm-lock.yaml
generated
|
|
@ -8,6 +8,9 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
'@angular/animations':
|
||||
specifier: ~20.3.12
|
||||
version: 20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/cdk':
|
||||
specifier: ^20.2.6
|
||||
version: 20.2.6(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
|
|
@ -22,28 +25,28 @@ importers:
|
|||
version: 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/forms':
|
||||
specifier: ~20.3.2
|
||||
version: 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
version: 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/localize':
|
||||
specifier: ~20.3.2
|
||||
version: 20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)
|
||||
'@angular/platform-browser':
|
||||
specifier: ~20.3.2
|
||||
version: 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
version: 20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser-dynamic':
|
||||
specifier: ~20.3.2
|
||||
version: 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
version: 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@angular/router':
|
||||
specifier: ~20.3.2
|
||||
version: 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
version: 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@ng-bootstrap/ng-bootstrap':
|
||||
specifier: ^19.0.1
|
||||
version: 19.0.1(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@popperjs/core@2.11.8)(rxjs@7.8.2)
|
||||
version: 19.0.1(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@popperjs/core@2.11.8)(rxjs@7.8.2)
|
||||
'@ng-select/ng-select':
|
||||
specifier: ^20.6.3
|
||||
version: 20.6.3(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))
|
||||
version: 20.6.3(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))
|
||||
'@ngneat/dirty-check-forms':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/router@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(lodash-es@4.17.21)(rxjs@7.8.2)
|
||||
version: 3.0.3(291c247a225ddc29ee470ed21e444e55)
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
|
|
@ -73,7 +76,7 @@ importers:
|
|||
version: 10.1.0(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
ngx-ui-tour-ng-bootstrap:
|
||||
specifier: ^17.0.1
|
||||
version: 17.0.1(a51ec0d773a3e93ac3d51d20ca771021)
|
||||
version: 17.0.1(f8db16ccbb0d6be45bab4b8410cc9846)
|
||||
rxjs:
|
||||
specifier: ^7.8.2
|
||||
version: 7.8.2
|
||||
|
|
@ -92,10 +95,10 @@ importers:
|
|||
devDependencies:
|
||||
'@angular-builders/custom-webpack':
|
||||
specifier: ^20.0.0
|
||||
version: 20.0.0(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
version: 20.0.0(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-builders/jest':
|
||||
specifier: ^20.0.0
|
||||
version: 20.0.0(617e23274585616dcf62fd78c9140eac)
|
||||
version: 20.0.0(496b29fc4599be2dae83ff2679fdbd16)
|
||||
'@angular-devkit/core':
|
||||
specifier: ^20.3.3
|
||||
version: 20.3.3(chokidar@4.0.3)
|
||||
|
|
@ -119,7 +122,7 @@ importers:
|
|||
version: 20.3.0(eslint@9.36.0(jiti@1.21.7))(typescript@5.8.3)
|
||||
'@angular/build':
|
||||
specifier: ^20.3.3
|
||||
version: 20.3.3(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
version: 20.3.3(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/cli':
|
||||
specifier: ~20.3.3
|
||||
version: 20.3.3(@types/node@24.6.1)(chokidar@4.0.3)
|
||||
|
|
@ -161,7 +164,7 @@ importers:
|
|||
version: 16.0.0
|
||||
jest-preset-angular:
|
||||
specifier: ^15.0.2
|
||||
version: 15.0.2(ccefccc315e3e4bd30d78eb49c90d46a)
|
||||
version: 15.0.2(83827844341020d1e6edc9d0e74e3f3d)
|
||||
jest-websocket-mock:
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0
|
||||
|
|
@ -403,6 +406,12 @@ packages:
|
|||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '*'
|
||||
|
||||
'@angular/animations@20.3.12':
|
||||
resolution: {integrity: sha512-tkzruF0pbcOrC2lwsPKjkp5btazs6vcX4At7kyVFjjuPbgI6RNG+MoFXHpN9ypenscYtTAhDcPSmjBnzoDaXhQ==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
peerDependencies:
|
||||
'@angular/core': 20.3.12
|
||||
|
||||
'@angular/build@20.0.4':
|
||||
resolution: {integrity: sha512-SIYLg2st05Q5hgFrxwj6L4i9j2j2JNWYoYgacXp+mw9YVhFiC02Ymbakc9fq+3+sWlm0XTX5JgrupV2ac1ytNQ==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
||||
|
|
@ -7096,13 +7105,13 @@ snapshots:
|
|||
- chokidar
|
||||
- typescript
|
||||
|
||||
'@angular-builders/custom-webpack@20.0.0(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)':
|
||||
dependencies:
|
||||
? '@angular-builders/custom-webpack@20.0.0(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)'
|
||||
: dependencies:
|
||||
'@angular-builders/common': 4.0.0(@types/node@24.6.1)(chokidar@4.0.3)(typescript@5.8.3)
|
||||
'@angular-devkit/architect': 0.2000.4(chokidar@4.0.3)
|
||||
'@angular-devkit/build-angular': 20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-devkit/build-angular': 20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-devkit/core': 20.3.3(chokidar@4.0.3)
|
||||
'@angular/build': 20.3.3(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/build': 20.3.3(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/compiler-cli': 20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3)
|
||||
lodash: 4.17.21
|
||||
webpack-merge: 6.0.1
|
||||
|
|
@ -7150,17 +7159,17 @@ snapshots:
|
|||
- webpack-cli
|
||||
- yaml
|
||||
|
||||
'@angular-builders/jest@20.0.0(617e23274585616dcf62fd78c9140eac)':
|
||||
'@angular-builders/jest@20.0.0(496b29fc4599be2dae83ff2679fdbd16)':
|
||||
dependencies:
|
||||
'@angular-builders/common': 4.0.0(@types/node@24.6.1)(chokidar@4.0.3)(typescript@5.8.3)
|
||||
'@angular-devkit/architect': 0.2000.4(chokidar@4.0.3)
|
||||
'@angular-devkit/build-angular': 20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-devkit/build-angular': 20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-devkit/core': 20.3.3(chokidar@4.0.3)
|
||||
'@angular/compiler-cli': 20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser-dynamic': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@angular/platform-browser-dynamic': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
jest: 30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3))
|
||||
jest-preset-angular: 14.6.0(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(canvas@3.0.0)(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jsdom@26.1.0(canvas@3.0.0))(typescript@5.8.3)
|
||||
jest-preset-angular: 14.6.0(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(canvas@3.0.0)(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jsdom@26.1.0(canvas@3.0.0))(typescript@5.8.3)
|
||||
lodash: 4.17.21
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
|
|
@ -7192,13 +7201,13 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- chokidar
|
||||
|
||||
'@angular-devkit/build-angular@20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)':
|
||||
'@angular-devkit/build-angular@20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.5(@types/node@24.6.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@angular-devkit/architect': 0.2000.4(chokidar@4.0.3)
|
||||
'@angular-devkit/build-webpack': 0.2000.4(chokidar@4.0.3)(webpack-dev-server@5.2.1(webpack@5.102.0))(webpack@5.99.8(esbuild@0.25.5))
|
||||
'@angular-devkit/core': 20.0.4(chokidar@4.0.3)
|
||||
'@angular/build': 20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/build': 20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/compiler-cli': 20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3)
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/generator': 7.27.1
|
||||
|
|
@ -7254,7 +7263,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/localize': 20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)
|
||||
'@angular/platform-browser': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
esbuild: 0.25.5
|
||||
jest: 30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3))
|
||||
jest-environment-jsdom: 30.2.0(canvas@3.0.0)
|
||||
|
|
@ -7386,7 +7395,12 @@ snapshots:
|
|||
eslint: 9.36.0(jiti@1.21.7)
|
||||
typescript: 5.8.3
|
||||
|
||||
'@angular/build@20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)':
|
||||
'@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))':
|
||||
dependencies:
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@angular/build@20.0.4(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@angular-devkit/architect': 0.2000.4(chokidar@4.0.3)
|
||||
|
|
@ -7421,7 +7435,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/localize': 20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)
|
||||
'@angular/platform-browser': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
less: 4.3.0
|
||||
lmdb: 3.3.0
|
||||
postcss: 8.5.3
|
||||
|
|
@ -7438,7 +7452,7 @@ snapshots:
|
|||
- tsx
|
||||
- yaml
|
||||
|
||||
'@angular/build@20.3.3(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)':
|
||||
'@angular/build@20.3.3(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.6.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@angular-devkit/architect': 0.2003.3(chokidar@4.0.3)
|
||||
|
|
@ -7473,7 +7487,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/localize': 20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)
|
||||
'@angular/platform-browser': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
less: 4.3.0
|
||||
lmdb: 3.4.2
|
||||
postcss: 8.5.3
|
||||
|
|
@ -7557,11 +7571,11 @@ snapshots:
|
|||
'@angular/compiler': 20.3.2
|
||||
zone.js: 0.15.1
|
||||
|
||||
'@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
|
||||
'@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
||||
|
|
@ -7576,25 +7590,27 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@angular/platform-browser-dynamic@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))':
|
||||
'@angular/platform-browser-dynamic@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/compiler': 20.3.2
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
tslib: 2.8.1
|
||||
|
||||
'@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))':
|
||||
'@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@angular/animations': 20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
|
||||
'@angular/router@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
|
||||
'@angular/router@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
||||
|
|
@ -9403,28 +9419,28 @@ snapshots:
|
|||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@ng-bootstrap/ng-bootstrap@19.0.1(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@popperjs/core@2.11.8)(rxjs@7.8.2)':
|
||||
'@ng-bootstrap/ng-bootstrap@19.0.1(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@popperjs/core@2.11.8)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/forms': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/forms': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/localize': 20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2)
|
||||
'@popperjs/core': 2.11.8
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@ng-select/ng-select@20.6.3(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))':
|
||||
'@ng-select/ng-select@20.6.3(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/forms': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/forms': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
tslib: 2.8.1
|
||||
|
||||
? '@ngneat/dirty-check-forms@3.0.3(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/router@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(lodash-es@4.17.21)(rxjs@7.8.2)'
|
||||
: dependencies:
|
||||
'@ngneat/dirty-check-forms@3.0.3(291c247a225ddc29ee470ed21e444e55)':
|
||||
dependencies:
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/forms': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/router': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/forms': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/router': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
lodash-es: 4.17.21
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
|
@ -12158,11 +12174,11 @@ snapshots:
|
|||
optionalDependencies:
|
||||
jest-resolve: 30.2.0
|
||||
|
||||
jest-preset-angular@14.6.0(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(canvas@3.0.0)(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jsdom@26.1.0(canvas@3.0.0))(typescript@5.8.3):
|
||||
jest-preset-angular@14.6.0(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(canvas@3.0.0)(jest@30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3)))(jsdom@26.1.0(canvas@3.0.0))(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@angular/compiler-cli': 20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser-dynamic': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@angular/platform-browser-dynamic': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
bs-logger: 0.2.6
|
||||
esbuild-wasm: 0.25.10
|
||||
jest: 30.2.0(@types/node@24.6.1)(ts-node@10.9.2(@types/node@24.6.1)(typescript@5.8.3))
|
||||
|
|
@ -12184,12 +12200,12 @@ snapshots:
|
|||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
jest-preset-angular@15.0.2(ccefccc315e3e4bd30d78eb49c90d46a):
|
||||
jest-preset-angular@15.0.2(83827844341020d1e6edc9d0e74e3f3d):
|
||||
dependencies:
|
||||
'@angular/compiler-cli': 20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser-dynamic': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@angular/platform-browser': 20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser-dynamic': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.2)(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@jest/environment-jsdom-abstract': 30.2.0(canvas@3.0.0)(jsdom@26.1.0(canvas@3.0.0))
|
||||
bs-logger: 0.2.6
|
||||
esbuild-wasm: 0.25.10
|
||||
|
|
@ -12883,20 +12899,20 @@ snapshots:
|
|||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
ngx-ui-tour-core@15.0.0(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2):
|
||||
ngx-ui-tour-core@15.0.0(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2):
|
||||
dependencies:
|
||||
'@angular/common': 20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/router': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/router': 20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
||||
ngx-ui-tour-ng-bootstrap@17.0.1(a51ec0d773a3e93ac3d51d20ca771021):
|
||||
ngx-ui-tour-ng-bootstrap@17.0.1(f8db16ccbb0d6be45bab4b8410cc9846):
|
||||
dependencies:
|
||||
'@angular/common': 20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@ng-bootstrap/ng-bootstrap': 19.0.1(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@popperjs/core@2.11.8)(rxjs@7.8.2)
|
||||
ngx-ui-tour-core: 15.0.0(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2)
|
||||
'@ng-bootstrap/ng-bootstrap': 19.0.1(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.2(@angular/compiler-cli@20.3.2(@angular/compiler@20.3.2)(typescript@5.8.3))(@angular/compiler@20.3.2))(@popperjs/core@2.11.8)(rxjs@7.8.2)
|
||||
ngx-ui-tour-core: 15.0.0(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.3.2(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.2(@angular/animations@20.3.12(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@20.3.2(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.2(@angular/compiler@20.3.2)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2)
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- '@angular/router'
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { TrashComponent } from './components/admin/trash/trash.component'
|
|||
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
|
||||
import { AppFrameComponent } from './components/app-frame/app-frame.component'
|
||||
import { DashboardComponent } from './components/dashboard/dashboard.component'
|
||||
import { DeletionRequestsComponent } from './components/deletion-requests/deletion-requests.component'
|
||||
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
|
||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
|
||||
import { DocumentListComponent } from './components/document-list/document-list.component'
|
||||
|
|
@ -174,6 +175,18 @@ export const routes: Routes = [
|
|||
componentName: 'TrashComponent',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'deletion-requests',
|
||||
component: DeletionRequestsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: {
|
||||
requiredPermission: {
|
||||
action: PermissionAction.View,
|
||||
type: PermissionType.Document,
|
||||
},
|
||||
componentName: 'DeletionRequestsComponent',
|
||||
},
|
||||
},
|
||||
// redirect old paths
|
||||
{
|
||||
path: 'settings/mail',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
@if (hasSuggestions) {
|
||||
<div class="ai-suggestions-panel card shadow-sm mb-3" [@slideIn]>
|
||||
<div class="card-header bg-primary text-white d-flex align-items-center justify-content-between" role="button" (click)="toggleCollapse()">
|
||||
<div class="d-flex align-items-center">
|
||||
<i-bs name="magic" width="1.2em" height="1.2em" class="me-2"></i-bs>
|
||||
<strong i18n>AI Suggestions</strong>
|
||||
<span class="badge bg-light text-primary ms-2">{{ pendingSuggestions.length }}</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@if (appliedCount > 0) {
|
||||
<span class="badge bg-success" i18n>{{ appliedCount }} applied</span>
|
||||
}
|
||||
@if (rejectedCount > 0) {
|
||||
<span class="badge bg-danger" i18n>{{ rejectedCount }} rejected</span>
|
||||
}
|
||||
<i-bs [name]="isCollapsed ? 'chevron-down' : 'chevron-up'" width="1em" height="1em"></i-bs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [ngbCollapse]="isCollapsed" class="card-body">
|
||||
<div class="mb-3 pb-2 border-bottom">
|
||||
<p class="text-muted small mb-2" i18n>
|
||||
<i-bs name="info-circle" width="0.9em" height="0.9em" class="me-1"></i-bs>
|
||||
AI has analyzed this document and suggests the following metadata. Review and apply or reject each suggestion.
|
||||
</p>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success"
|
||||
[disabled]="disabled || pendingSuggestions.length === 0"
|
||||
(click)="applyAll()"
|
||||
i18n>
|
||||
<i-bs name="check-all" width="1em" height="1em" class="me-1"></i-bs>
|
||||
Apply All
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
[disabled]="disabled || pendingSuggestions.length === 0"
|
||||
(click)="rejectAll()"
|
||||
i18n>
|
||||
<i-bs name="x-circle" width="1em" height="1em" class="me-1"></i-bs>
|
||||
Reject All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="suggestions-container">
|
||||
@for (type of suggestionTypes; track type) {
|
||||
<div class="suggestion-group mb-3">
|
||||
<div class="suggestion-group-header d-flex align-items-center mb-2">
|
||||
<i-bs [name]="getTypeIcon(type)" width="1.1em" height="1.1em" class="me-2 text-primary"></i-bs>
|
||||
<strong class="text-secondary">{{ getTypeLabel(type) }}</strong>
|
||||
<span class="badge bg-secondary ms-2">{{ groupedSuggestions.get(type)?.length }}</span>
|
||||
</div>
|
||||
|
||||
<div class="suggestion-items">
|
||||
@for (suggestion of groupedSuggestions.get(type); track suggestion.id) {
|
||||
<div
|
||||
class="suggestion-item card mb-2"
|
||||
[@fadeInOut]
|
||||
[class.suggestion-applying]="suggestion.status === AISuggestionStatus.Applied"
|
||||
[class.suggestion-rejecting]="suggestion.status === AISuggestionStatus.Rejected">
|
||||
<div class="card-body p-2">
|
||||
<div class="d-flex align-items-start justify-content-between gap-2">
|
||||
<div class="flex-grow-1">
|
||||
<div class="suggestion-value fw-medium mb-1">
|
||||
@if (suggestion.type === AISuggestionType.CustomField && suggestion.field_name) {
|
||||
<span class="text-muted small me-1">{{ suggestion.field_name }}:</span>
|
||||
}
|
||||
{{ getLabel(suggestion) }}
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<span
|
||||
class="confidence-badge badge"
|
||||
[ngClass]="getConfidenceClass(suggestion.confidence)">
|
||||
<i-bs [name]="getConfidenceIcon(suggestion.confidence)" width="0.8em" height="0.8em" class="me-1"></i-bs>
|
||||
{{ getConfidenceLabel(suggestion.confidence) }}
|
||||
</span>
|
||||
@if (suggestion.created_at) {
|
||||
<span class="text-muted small">
|
||||
<i-bs name="clock" width="0.8em" height="0.8em" class="me-1"></i-bs>
|
||||
{{ suggestion.created_at | date:'short' }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="suggestion-actions d-flex gap-1 flex-shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success"
|
||||
[disabled]="disabled"
|
||||
(click)="applySuggestion(suggestion)"
|
||||
i18n-title
|
||||
title="Apply this suggestion">
|
||||
<i-bs name="check-lg" width="1em" height="1em"></i-bs>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
[disabled]="disabled"
|
||||
(click)="rejectSuggestion(suggestion)"
|
||||
i18n-title
|
||||
title="Reject this suggestion">
|
||||
<i-bs name="x-lg" width="1em" height="1em"></i-bs>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (pendingSuggestions.length === 0) {
|
||||
<div class="text-center text-muted py-3">
|
||||
<i-bs name="check-circle" width="2em" height="2em" class="mb-2"></i-bs>
|
||||
<p i18n>All suggestions have been processed</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
.ai-suggestions-panel {
|
||||
border: 2px solid var(--bs-primary);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s ease;
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-primary) !important;
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestions-container {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
// Custom scrollbar styles
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #555;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-group {
|
||||
.suggestion-group-header {
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
|
||||
strong {
|
||||
font-size: 0.95rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-items {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
border-left: 3px solid var(--bs-primary);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
border-left-color: var(--bs-success);
|
||||
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
&.suggestion-applying {
|
||||
animation: applyAnimation 0.5s ease;
|
||||
border-left-color: var(--bs-success);
|
||||
background-color: rgba(25, 135, 84, 0.1);
|
||||
}
|
||||
|
||||
&.suggestion-rejecting {
|
||||
animation: rejectAnimation 0.5s ease;
|
||||
border-left-color: var(--bs-danger);
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
}
|
||||
|
||||
.suggestion-value {
|
||||
color: #333;
|
||||
font-size: 0.95rem;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.confidence-badge {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&.confidence-high {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.confidence-medium {
|
||||
background-color: #ffc107;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&.confidence-low {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-actions {
|
||||
.btn {
|
||||
min-width: 36px;
|
||||
padding: 0.25rem 0.5rem;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animations
|
||||
@keyframes applyAnimation {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(40px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rejectAnimation {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
transform: translateX(-20px) rotate(-5deg);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(-40px) rotate(-10deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive design
|
||||
@media (max-width: 768px) {
|
||||
.ai-suggestions-panel {
|
||||
.card-header {
|
||||
padding: 0.5rem 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.badge {
|
||||
font-size: 0.65rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestions-container {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.suggestion-group {
|
||||
.suggestion-items {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
.d-flex {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem !important;
|
||||
}
|
||||
|
||||
.suggestion-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.ai-suggestions-panel {
|
||||
.card-header {
|
||||
.d-flex {
|
||||
flex-direction: column;
|
||||
align-items: flex-start !important;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
.suggestion-value {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.confidence-badge {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { provideAnimations } from '@angular/platform-browser/animations'
|
||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { of } from 'rxjs'
|
||||
import {
|
||||
AISuggestion,
|
||||
AISuggestionStatus,
|
||||
AISuggestionType,
|
||||
} from 'src/app/data/ai-suggestion'
|
||||
import { Correspondent } from 'src/app/data/correspondent'
|
||||
import { DocumentType } from 'src/app/data/document-type'
|
||||
import { StoragePath } from 'src/app/data/storage-path'
|
||||
import { Tag } from 'src/app/data/tag'
|
||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { AiSuggestionsPanelComponent } from './ai-suggestions-panel.component'
|
||||
|
||||
const mockTags: Tag[] = [
|
||||
{ id: 1, name: 'Invoice', colour: '#ff0000', text_colour: '#ffffff' },
|
||||
{ id: 2, name: 'Receipt', colour: '#00ff00', text_colour: '#000000' },
|
||||
]
|
||||
|
||||
const mockCorrespondents: Correspondent[] = [
|
||||
{ id: 1, name: 'Acme Corp' },
|
||||
{ id: 2, name: 'TechStart LLC' },
|
||||
]
|
||||
|
||||
const mockDocumentTypes: DocumentType[] = [
|
||||
{ id: 1, name: 'Invoice' },
|
||||
{ id: 2, name: 'Contract' },
|
||||
]
|
||||
|
||||
const mockStoragePaths: StoragePath[] = [
|
||||
{ id: 1, name: '/invoices', path: '/invoices' },
|
||||
{ id: 2, name: '/contracts', path: '/contracts' },
|
||||
]
|
||||
|
||||
const mockSuggestions: AISuggestion[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: AISuggestionType.Tag,
|
||||
value: 1,
|
||||
confidence: 0.85,
|
||||
status: AISuggestionStatus.Pending,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: AISuggestionType.Correspondent,
|
||||
value: 1,
|
||||
confidence: 0.75,
|
||||
status: AISuggestionStatus.Pending,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: AISuggestionType.DocumentType,
|
||||
value: 1,
|
||||
confidence: 0.90,
|
||||
status: AISuggestionStatus.Pending,
|
||||
},
|
||||
]
|
||||
|
||||
describe('AiSuggestionsPanelComponent', () => {
|
||||
let component: AiSuggestionsPanelComponent
|
||||
let fixture: ComponentFixture<AiSuggestionsPanelComponent>
|
||||
let tagService: TagService
|
||||
let correspondentService: CorrespondentService
|
||||
let documentTypeService: DocumentTypeService
|
||||
let storagePathService: StoragePathService
|
||||
let customFieldsService: CustomFieldsService
|
||||
let toastService: ToastService
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AiSuggestionsPanelComponent,
|
||||
NgbCollapseModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
provideAnimations(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
tagService = TestBed.inject(TagService)
|
||||
correspondentService = TestBed.inject(CorrespondentService)
|
||||
documentTypeService = TestBed.inject(DocumentTypeService)
|
||||
storagePathService = TestBed.inject(StoragePathService)
|
||||
customFieldsService = TestBed.inject(CustomFieldsService)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
|
||||
jest.spyOn(tagService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: mockTags.map((t) => t.id),
|
||||
count: mockTags.length,
|
||||
results: mockTags,
|
||||
})
|
||||
)
|
||||
|
||||
jest.spyOn(correspondentService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: mockCorrespondents.map((c) => c.id),
|
||||
count: mockCorrespondents.length,
|
||||
results: mockCorrespondents,
|
||||
})
|
||||
)
|
||||
|
||||
jest.spyOn(documentTypeService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: mockDocumentTypes.map((dt) => dt.id),
|
||||
count: mockDocumentTypes.length,
|
||||
results: mockDocumentTypes,
|
||||
})
|
||||
)
|
||||
|
||||
jest.spyOn(storagePathService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: mockStoragePaths.map((sp) => sp.id),
|
||||
count: mockStoragePaths.length,
|
||||
results: mockStoragePaths,
|
||||
})
|
||||
)
|
||||
|
||||
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||
of({
|
||||
all: [],
|
||||
count: 0,
|
||||
results: [],
|
||||
})
|
||||
)
|
||||
|
||||
fixture = TestBed.createComponent(AiSuggestionsPanelComponent)
|
||||
component = fixture.componentInstance
|
||||
})
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should process suggestions on input change', () => {
|
||||
component.suggestions = mockSuggestions
|
||||
component.ngOnChanges({
|
||||
suggestions: {
|
||||
currentValue: mockSuggestions,
|
||||
previousValue: [],
|
||||
firstChange: true,
|
||||
isFirstChange: () => true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(component.pendingSuggestions.length).toBe(3)
|
||||
expect(component.appliedCount).toBe(0)
|
||||
expect(component.rejectedCount).toBe(0)
|
||||
})
|
||||
|
||||
it('should group suggestions by type', () => {
|
||||
component.suggestions = mockSuggestions
|
||||
component.ngOnChanges({
|
||||
suggestions: {
|
||||
currentValue: mockSuggestions,
|
||||
previousValue: [],
|
||||
firstChange: true,
|
||||
isFirstChange: () => true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(component.groupedSuggestions.size).toBe(3)
|
||||
expect(component.groupedSuggestions.get(AISuggestionType.Tag)?.length).toBe(
|
||||
1
|
||||
)
|
||||
expect(
|
||||
component.groupedSuggestions.get(AISuggestionType.Correspondent)?.length
|
||||
).toBe(1)
|
||||
expect(
|
||||
component.groupedSuggestions.get(AISuggestionType.DocumentType)?.length
|
||||
).toBe(1)
|
||||
})
|
||||
|
||||
it('should apply a suggestion', () => {
|
||||
component.suggestions = mockSuggestions
|
||||
component.ngOnChanges({
|
||||
suggestions: {
|
||||
currentValue: mockSuggestions,
|
||||
previousValue: [],
|
||||
firstChange: true,
|
||||
isFirstChange: () => true,
|
||||
},
|
||||
})
|
||||
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const applySpy = jest.spyOn(component.apply, 'emit')
|
||||
|
||||
const suggestion = component.pendingSuggestions[0]
|
||||
component.applySuggestion(suggestion)
|
||||
|
||||
expect(suggestion.status).toBe(AISuggestionStatus.Applied)
|
||||
expect(applySpy).toHaveBeenCalledWith(suggestion)
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should reject a suggestion', () => {
|
||||
component.suggestions = mockSuggestions
|
||||
component.ngOnChanges({
|
||||
suggestions: {
|
||||
currentValue: mockSuggestions,
|
||||
previousValue: [],
|
||||
firstChange: true,
|
||||
isFirstChange: () => true,
|
||||
},
|
||||
})
|
||||
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const rejectSpy = jest.spyOn(component.reject, 'emit')
|
||||
|
||||
const suggestion = component.pendingSuggestions[0]
|
||||
component.rejectSuggestion(suggestion)
|
||||
|
||||
expect(suggestion.status).toBe(AISuggestionStatus.Rejected)
|
||||
expect(rejectSpy).toHaveBeenCalledWith(suggestion)
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should apply all suggestions', () => {
|
||||
component.suggestions = mockSuggestions
|
||||
component.ngOnChanges({
|
||||
suggestions: {
|
||||
currentValue: mockSuggestions,
|
||||
previousValue: [],
|
||||
firstChange: true,
|
||||
isFirstChange: () => true,
|
||||
},
|
||||
})
|
||||
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const applySpy = jest.spyOn(component.apply, 'emit')
|
||||
|
||||
component.applyAll()
|
||||
|
||||
expect(applySpy).toHaveBeenCalledTimes(3)
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should reject all suggestions', () => {
|
||||
component.suggestions = mockSuggestions
|
||||
component.ngOnChanges({
|
||||
suggestions: {
|
||||
currentValue: mockSuggestions,
|
||||
previousValue: [],
|
||||
firstChange: true,
|
||||
isFirstChange: () => true,
|
||||
},
|
||||
})
|
||||
|
||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const rejectSpy = jest.spyOn(component.reject, 'emit')
|
||||
|
||||
component.rejectAll()
|
||||
|
||||
expect(rejectSpy).toHaveBeenCalledTimes(3)
|
||||
expect(toastSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return correct confidence class', () => {
|
||||
expect(component.getConfidenceClass(0.9)).toBe('confidence-high')
|
||||
expect(component.getConfidenceClass(0.7)).toBe('confidence-medium')
|
||||
expect(component.getConfidenceClass(0.5)).toBe('confidence-low')
|
||||
})
|
||||
|
||||
it('should return correct confidence label', () => {
|
||||
expect(component.getConfidenceLabel(0.85)).toContain('85%')
|
||||
expect(component.getConfidenceLabel(0.65)).toContain('65%')
|
||||
expect(component.getConfidenceLabel(0.45)).toContain('45%')
|
||||
})
|
||||
|
||||
it('should toggle collapse', () => {
|
||||
expect(component.isCollapsed).toBe(false)
|
||||
component.toggleCollapse()
|
||||
expect(component.isCollapsed).toBe(true)
|
||||
component.toggleCollapse()
|
||||
expect(component.isCollapsed).toBe(false)
|
||||
})
|
||||
|
||||
it('should respect disabled state', () => {
|
||||
component.suggestions = mockSuggestions
|
||||
component.disabled = true
|
||||
component.ngOnChanges({
|
||||
suggestions: {
|
||||
currentValue: mockSuggestions,
|
||||
previousValue: [],
|
||||
firstChange: true,
|
||||
isFirstChange: () => true,
|
||||
},
|
||||
})
|
||||
|
||||
const applySpy = jest.spyOn(component.apply, 'emit')
|
||||
const suggestion = component.pendingSuggestions[0]
|
||||
component.applySuggestion(suggestion)
|
||||
|
||||
expect(applySpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not render panel when there are no suggestions', () => {
|
||||
component.suggestions = []
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(component.hasSuggestions).toBe(false)
|
||||
})
|
||||
|
||||
it('should render panel when there are suggestions', () => {
|
||||
component.suggestions = mockSuggestions
|
||||
component.ngOnChanges({
|
||||
suggestions: {
|
||||
currentValue: mockSuggestions,
|
||||
previousValue: [],
|
||||
firstChange: true,
|
||||
isFirstChange: () => true,
|
||||
},
|
||||
})
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(component.hasSuggestions).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,381 @@
|
|||
import { CommonModule } from '@angular/common'
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
transition,
|
||||
animate,
|
||||
} from '@angular/animations'
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
inject,
|
||||
} from '@angular/core'
|
||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import {
|
||||
AISuggestion,
|
||||
AISuggestionStatus,
|
||||
AISuggestionType,
|
||||
} from 'src/app/data/ai-suggestion'
|
||||
import { Correspondent } from 'src/app/data/correspondent'
|
||||
import { CustomField } from 'src/app/data/custom-field'
|
||||
import { DocumentType } from 'src/app/data/document-type'
|
||||
import { StoragePath } from 'src/app/data/storage-path'
|
||||
import { Tag } from 'src/app/data/tag'
|
||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-ai-suggestions-panel',
|
||||
templateUrl: './ai-suggestions-panel.component.html',
|
||||
styleUrls: ['./ai-suggestions-panel.component.scss'],
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgbCollapseModule,
|
||||
NgxBootstrapIconsModule,
|
||||
],
|
||||
animations: [
|
||||
trigger('slideIn', [
|
||||
transition(':enter', [
|
||||
style({ transform: 'translateY(-20px)', opacity: 0 }),
|
||||
animate('300ms ease-out', style({ transform: 'translateY(0)', opacity: 1 })),
|
||||
]),
|
||||
]),
|
||||
trigger('fadeInOut', [
|
||||
transition(':enter', [
|
||||
style({ opacity: 0, transform: 'scale(0.95)' }),
|
||||
animate('200ms ease-out', style({ opacity: 1, transform: 'scale(1)' })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
animate('200ms ease-in', style({ opacity: 0, transform: 'scale(0.95)' })),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class AiSuggestionsPanelComponent implements OnChanges {
|
||||
private tagService = inject(TagService)
|
||||
private correspondentService = inject(CorrespondentService)
|
||||
private documentTypeService = inject(DocumentTypeService)
|
||||
private storagePathService = inject(StoragePathService)
|
||||
private customFieldsService = inject(CustomFieldsService)
|
||||
private toastService = inject(ToastService)
|
||||
|
||||
@Input()
|
||||
suggestions: AISuggestion[] = []
|
||||
|
||||
@Input()
|
||||
disabled: boolean = false
|
||||
|
||||
@Output()
|
||||
apply = new EventEmitter<AISuggestion>()
|
||||
|
||||
@Output()
|
||||
reject = new EventEmitter<AISuggestion>()
|
||||
|
||||
public isCollapsed = false
|
||||
public pendingSuggestions: AISuggestion[] = []
|
||||
public groupedSuggestions: Map<AISuggestionType, AISuggestion[]> = new Map()
|
||||
public appliedCount = 0
|
||||
public rejectedCount = 0
|
||||
|
||||
private tags: Tag[] = []
|
||||
private correspondents: Correspondent[] = []
|
||||
private documentTypes: DocumentType[] = []
|
||||
private storagePaths: StoragePath[] = []
|
||||
private customFields: CustomField[] = []
|
||||
|
||||
public AISuggestionType = AISuggestionType
|
||||
public AISuggestionStatus = AISuggestionStatus
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['suggestions']) {
|
||||
this.processSuggestions()
|
||||
this.loadMetadata()
|
||||
}
|
||||
}
|
||||
|
||||
private processSuggestions(): void {
|
||||
this.pendingSuggestions = this.suggestions.filter(
|
||||
(s) => s.status === AISuggestionStatus.Pending
|
||||
)
|
||||
this.appliedCount = this.suggestions.filter(
|
||||
(s) => s.status === AISuggestionStatus.Applied
|
||||
).length
|
||||
this.rejectedCount = this.suggestions.filter(
|
||||
(s) => s.status === AISuggestionStatus.Rejected
|
||||
).length
|
||||
|
||||
// Group suggestions by type
|
||||
this.groupedSuggestions.clear()
|
||||
this.pendingSuggestions.forEach((suggestion) => {
|
||||
const group = this.groupedSuggestions.get(suggestion.type) || []
|
||||
group.push(suggestion)
|
||||
this.groupedSuggestions.set(suggestion.type, group)
|
||||
})
|
||||
}
|
||||
|
||||
private loadMetadata(): void {
|
||||
// Load tags if needed
|
||||
const tagSuggestions = this.pendingSuggestions.filter(
|
||||
(s) => s.type === AISuggestionType.Tag
|
||||
)
|
||||
if (tagSuggestions.length > 0) {
|
||||
this.tagService.listAll().subscribe((tags) => {
|
||||
this.tags = tags.results
|
||||
this.updateSuggestionLabels()
|
||||
})
|
||||
}
|
||||
|
||||
// Load correspondents if needed
|
||||
const correspondentSuggestions = this.pendingSuggestions.filter(
|
||||
(s) => s.type === AISuggestionType.Correspondent
|
||||
)
|
||||
if (correspondentSuggestions.length > 0) {
|
||||
this.correspondentService.listAll().subscribe((correspondents) => {
|
||||
this.correspondents = correspondents.results
|
||||
this.updateSuggestionLabels()
|
||||
})
|
||||
}
|
||||
|
||||
// Load document types if needed
|
||||
const documentTypeSuggestions = this.pendingSuggestions.filter(
|
||||
(s) => s.type === AISuggestionType.DocumentType
|
||||
)
|
||||
if (documentTypeSuggestions.length > 0) {
|
||||
this.documentTypeService.listAll().subscribe((documentTypes) => {
|
||||
this.documentTypes = documentTypes.results
|
||||
this.updateSuggestionLabels()
|
||||
})
|
||||
}
|
||||
|
||||
// Load storage paths if needed
|
||||
const storagePathSuggestions = this.pendingSuggestions.filter(
|
||||
(s) => s.type === AISuggestionType.StoragePath
|
||||
)
|
||||
if (storagePathSuggestions.length > 0) {
|
||||
this.storagePathService.listAll().subscribe((storagePaths) => {
|
||||
this.storagePaths = storagePaths.results
|
||||
this.updateSuggestionLabels()
|
||||
})
|
||||
}
|
||||
|
||||
// Load custom fields if needed
|
||||
const customFieldSuggestions = this.pendingSuggestions.filter(
|
||||
(s) => s.type === AISuggestionType.CustomField
|
||||
)
|
||||
if (customFieldSuggestions.length > 0) {
|
||||
this.customFieldsService.listAll().subscribe((customFields) => {
|
||||
this.customFields = customFields.results
|
||||
this.updateSuggestionLabels()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private updateSuggestionLabels(): void {
|
||||
this.pendingSuggestions.forEach((suggestion) => {
|
||||
if (!suggestion.label) {
|
||||
suggestion.label = this.getLabel(suggestion)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public getLabel(suggestion: AISuggestion): string {
|
||||
if (suggestion.label) {
|
||||
return suggestion.label
|
||||
}
|
||||
|
||||
switch (suggestion.type) {
|
||||
case AISuggestionType.Tag:
|
||||
const tag = this.tags.find((t) => t.id === suggestion.value)
|
||||
return tag ? tag.name : `Tag #${suggestion.value}`
|
||||
|
||||
case AISuggestionType.Correspondent:
|
||||
const correspondent = this.correspondents.find(
|
||||
(c) => c.id === suggestion.value
|
||||
)
|
||||
return correspondent
|
||||
? correspondent.name
|
||||
: `Correspondent #${suggestion.value}`
|
||||
|
||||
case AISuggestionType.DocumentType:
|
||||
const docType = this.documentTypes.find(
|
||||
(dt) => dt.id === suggestion.value
|
||||
)
|
||||
return docType ? docType.name : `Document Type #${suggestion.value}`
|
||||
|
||||
case AISuggestionType.StoragePath:
|
||||
const storagePath = this.storagePaths.find(
|
||||
(sp) => sp.id === suggestion.value
|
||||
)
|
||||
return storagePath ? storagePath.name : `Storage Path #${suggestion.value}`
|
||||
|
||||
case AISuggestionType.CustomField:
|
||||
return suggestion.field_name || 'Custom Field'
|
||||
|
||||
case AISuggestionType.Date:
|
||||
return new Date(suggestion.value).toLocaleDateString()
|
||||
|
||||
case AISuggestionType.Title:
|
||||
return suggestion.value
|
||||
|
||||
default:
|
||||
return String(suggestion.value)
|
||||
}
|
||||
}
|
||||
|
||||
public getTypeLabel(type: AISuggestionType): string {
|
||||
switch (type) {
|
||||
case AISuggestionType.Tag:
|
||||
return $localize`Tags`
|
||||
case AISuggestionType.Correspondent:
|
||||
return $localize`Correspondent`
|
||||
case AISuggestionType.DocumentType:
|
||||
return $localize`Document Type`
|
||||
case AISuggestionType.StoragePath:
|
||||
return $localize`Storage Path`
|
||||
case AISuggestionType.CustomField:
|
||||
return $localize`Custom Field`
|
||||
case AISuggestionType.Date:
|
||||
return $localize`Date`
|
||||
case AISuggestionType.Title:
|
||||
return $localize`Title`
|
||||
default:
|
||||
return String(type)
|
||||
}
|
||||
}
|
||||
|
||||
public getTypeIcon(type: AISuggestionType): string {
|
||||
switch (type) {
|
||||
case AISuggestionType.Tag:
|
||||
return 'tag'
|
||||
case AISuggestionType.Correspondent:
|
||||
return 'person'
|
||||
case AISuggestionType.DocumentType:
|
||||
return 'file-earmark-text'
|
||||
case AISuggestionType.StoragePath:
|
||||
return 'folder'
|
||||
case AISuggestionType.CustomField:
|
||||
return 'input-cursor-text'
|
||||
case AISuggestionType.Date:
|
||||
return 'calendar'
|
||||
case AISuggestionType.Title:
|
||||
return 'pencil'
|
||||
default:
|
||||
return 'lightbulb'
|
||||
}
|
||||
}
|
||||
|
||||
public getConfidenceClass(confidence: number): string {
|
||||
if (confidence >= 0.8) {
|
||||
return 'confidence-high'
|
||||
} else if (confidence >= 0.6) {
|
||||
return 'confidence-medium'
|
||||
} else {
|
||||
return 'confidence-low'
|
||||
}
|
||||
}
|
||||
|
||||
public getConfidenceLabel(confidence: number): string {
|
||||
const percentage = Math.round(confidence * 100)
|
||||
if (confidence >= 0.8) {
|
||||
return $localize`High (${percentage}%)`
|
||||
} else if (confidence >= 0.6) {
|
||||
return $localize`Medium (${percentage}%)`
|
||||
} else {
|
||||
return $localize`Low (${percentage}%)`
|
||||
}
|
||||
}
|
||||
|
||||
public getConfidenceIcon(confidence: number): string {
|
||||
if (confidence >= 0.8) {
|
||||
return 'check-circle-fill'
|
||||
} else if (confidence >= 0.6) {
|
||||
return 'exclamation-circle'
|
||||
} else {
|
||||
return 'question-circle'
|
||||
}
|
||||
}
|
||||
|
||||
public applySuggestion(suggestion: AISuggestion): void {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
suggestion.status = AISuggestionStatus.Applied
|
||||
this.apply.emit(suggestion)
|
||||
this.processSuggestions()
|
||||
|
||||
this.toastService.showInfo(
|
||||
$localize`Applied AI suggestion: ${this.getLabel(suggestion)}`
|
||||
)
|
||||
}
|
||||
|
||||
public rejectSuggestion(suggestion: AISuggestion): void {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
suggestion.status = AISuggestionStatus.Rejected
|
||||
this.reject.emit(suggestion)
|
||||
this.processSuggestions()
|
||||
|
||||
this.toastService.showInfo(
|
||||
$localize`Rejected AI suggestion: ${this.getLabel(suggestion)}`
|
||||
)
|
||||
}
|
||||
|
||||
public applyAll(): void {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const count = this.pendingSuggestions.length
|
||||
this.pendingSuggestions.forEach((suggestion) => {
|
||||
suggestion.status = AISuggestionStatus.Applied
|
||||
this.apply.emit(suggestion)
|
||||
})
|
||||
this.processSuggestions()
|
||||
|
||||
this.toastService.showInfo(
|
||||
$localize`Applied ${count} AI suggestions`
|
||||
)
|
||||
}
|
||||
|
||||
public rejectAll(): void {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const count = this.pendingSuggestions.length
|
||||
this.pendingSuggestions.forEach((suggestion) => {
|
||||
suggestion.status = AISuggestionStatus.Rejected
|
||||
this.reject.emit(suggestion)
|
||||
})
|
||||
this.processSuggestions()
|
||||
|
||||
this.toastService.showInfo(
|
||||
$localize`Rejected ${count} AI suggestions`
|
||||
)
|
||||
}
|
||||
|
||||
public toggleCollapse(): void {
|
||||
this.isCollapsed = !this.isCollapsed
|
||||
}
|
||||
|
||||
public get hasSuggestions(): boolean {
|
||||
return this.pendingSuggestions.length > 0
|
||||
}
|
||||
|
||||
public get suggestionTypes(): AISuggestionType[] {
|
||||
return Array.from(this.groupedSuggestions.keys())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<li class="nav-item ai-status-container">
|
||||
<button
|
||||
class="btn border-0 position-relative"
|
||||
[ngbPopover]="aiStatusPopover"
|
||||
popoverClass="ai-status-popover"
|
||||
placement="bottom"
|
||||
container="body"
|
||||
triggers="mouseenter:mouseleave">
|
||||
<i-bs
|
||||
width="1.3em"
|
||||
height="1.3em"
|
||||
[name]="iconName"
|
||||
[class]="iconClass">
|
||||
</i-bs>
|
||||
@if (hasAlerts) {
|
||||
<span class="badge rounded-pill bg-danger position-absolute top-0 end-0 translate-middle-y">
|
||||
{{ aiStatus.pending_deletion_requests }}
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<ng-template #aiStatusPopover>
|
||||
<div class="ai-status-tooltip">
|
||||
<h6 class="mb-2 border-bottom pb-2">
|
||||
<i-bs name="robot" width="1em" height="1em" class="me-1"></i-bs>
|
||||
<span i18n>AI Scanner Status</span>
|
||||
</h6>
|
||||
|
||||
<div class="status-info">
|
||||
<div class="status-row mb-2">
|
||||
<span class="status-label" i18n>Status:</span>
|
||||
<span [class]="aiStatus.active ? 'text-success' : 'text-muted'">
|
||||
{{ aiStatus.active ? 'Active' : 'Inactive' }}
|
||||
@if (aiStatus.processing) {
|
||||
<span class="badge bg-primary ms-1" i18n>Processing</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (aiStatus.active) {
|
||||
<div class="status-row mb-2">
|
||||
<span class="status-label" i18n>Scanned Today:</span>
|
||||
<span class="fw-bold">{{ aiStatus.documents_scanned_today }}</span>
|
||||
</div>
|
||||
|
||||
<div class="status-row mb-2">
|
||||
<span class="status-label" i18n>Suggestions Applied:</span>
|
||||
<span class="fw-bold">{{ aiStatus.suggestions_applied }}</span>
|
||||
</div>
|
||||
|
||||
@if (aiStatus.pending_deletion_requests > 0) {
|
||||
<div class="status-row mb-2">
|
||||
<span class="status-label text-danger" i18n>Pending Deletions:</span>
|
||||
<span class="fw-bold text-danger">{{ aiStatus.pending_deletion_requests }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (aiStatus.last_scan) {
|
||||
<div class="status-row mb-2 small text-muted">
|
||||
<span class="status-label" i18n>Last Scan:</span>
|
||||
<span>{{ aiStatus.last_scan | date: 'short' }}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mt-3 pt-2 border-top">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary w-100"
|
||||
(click)="navigateToSettings()"
|
||||
i18n>
|
||||
Configure AI Scanner
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
.ai-status-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ai-status-icon {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.inactive {
|
||||
opacity: 0.4;
|
||||
color: var(--bs-secondary);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--bs-success);
|
||||
}
|
||||
|
||||
&.processing {
|
||||
color: var(--bs-primary);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ai-status-tooltip {
|
||||
min-width: 250px;
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.status-label {
|
||||
font-weight: 500;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Badge positioning
|
||||
.badge.rounded-pill {
|
||||
font-size: 0.65rem;
|
||||
padding: 0.15rem 0.35rem;
|
||||
min-width: 1.2rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { Router } from '@angular/router'
|
||||
import { of } from 'rxjs'
|
||||
import { AIStatus } from 'src/app/data/ai-status'
|
||||
import { AIStatusService } from 'src/app/services/ai-status.service'
|
||||
import { AIStatusIndicatorComponent } from './ai-status-indicator.component'
|
||||
|
||||
describe('AIStatusIndicatorComponent', () => {
|
||||
let component: AIStatusIndicatorComponent
|
||||
let fixture: ComponentFixture<AIStatusIndicatorComponent>
|
||||
let aiStatusService: jasmine.SpyObj<AIStatusService>
|
||||
let router: jasmine.SpyObj<Router>
|
||||
|
||||
const mockAIStatus: AIStatus = {
|
||||
active: true,
|
||||
processing: false,
|
||||
documents_scanned_today: 42,
|
||||
suggestions_applied: 15,
|
||||
pending_deletion_requests: 2,
|
||||
last_scan: '2025-11-15T12:00:00Z',
|
||||
version: '1.0.0',
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
const aiStatusServiceSpy = jasmine.createSpyObj('AIStatusService', [
|
||||
'getStatus',
|
||||
'getCurrentStatus',
|
||||
'refresh',
|
||||
])
|
||||
const routerSpy = jasmine.createSpyObj('Router', ['navigate'])
|
||||
|
||||
aiStatusServiceSpy.getStatus.and.returnValue(of(mockAIStatus))
|
||||
aiStatusServiceSpy.getCurrentStatus.and.returnValue(mockAIStatus)
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AIStatusIndicatorComponent],
|
||||
providers: [
|
||||
{ provide: AIStatusService, useValue: aiStatusServiceSpy },
|
||||
{ provide: Router, useValue: routerSpy },
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
aiStatusService = TestBed.inject(
|
||||
AIStatusService
|
||||
) as jasmine.SpyObj<AIStatusService>
|
||||
router = TestBed.inject(Router) as jasmine.SpyObj<Router>
|
||||
|
||||
fixture = TestBed.createComponent(AIStatusIndicatorComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should subscribe to AI status on init', () => {
|
||||
expect(aiStatusService.getStatus).toHaveBeenCalled()
|
||||
expect(component.aiStatus).toEqual(mockAIStatus)
|
||||
})
|
||||
|
||||
it('should show robot icon', () => {
|
||||
expect(component.iconName).toBe('robot')
|
||||
})
|
||||
|
||||
it('should have active class when AI is active', () => {
|
||||
component.aiStatus = { ...mockAIStatus, active: true, processing: false }
|
||||
expect(component.iconClass).toContain('active')
|
||||
})
|
||||
|
||||
it('should have inactive class when AI is inactive', () => {
|
||||
component.aiStatus = { ...mockAIStatus, active: false }
|
||||
expect(component.iconClass).toContain('inactive')
|
||||
})
|
||||
|
||||
it('should have processing class when AI is processing', () => {
|
||||
component.aiStatus = { ...mockAIStatus, active: true, processing: true }
|
||||
expect(component.iconClass).toContain('processing')
|
||||
})
|
||||
|
||||
it('should show alerts when there are pending deletion requests', () => {
|
||||
component.aiStatus = { ...mockAIStatus, pending_deletion_requests: 2 }
|
||||
expect(component.hasAlerts).toBe(true)
|
||||
})
|
||||
|
||||
it('should not show alerts when there are no pending deletion requests', () => {
|
||||
component.aiStatus = { ...mockAIStatus, pending_deletion_requests: 0 }
|
||||
expect(component.hasAlerts).toBe(false)
|
||||
})
|
||||
|
||||
it('should navigate to settings when navigateToSettings is called', () => {
|
||||
component.navigateToSettings()
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/settings'], {
|
||||
fragment: 'ai-scanner',
|
||||
})
|
||||
})
|
||||
|
||||
it('should unsubscribe on destroy', () => {
|
||||
const subscription = component['subscription']
|
||||
spyOn(subscription, 'unsubscribe')
|
||||
component.ngOnDestroy()
|
||||
expect(subscription.unsubscribe).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import { DatePipe } from '@angular/common'
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
||||
import { Router, RouterModule } from '@angular/router'
|
||||
import {
|
||||
NgbPopoverModule,
|
||||
NgbTooltipModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { AIStatus } from 'src/app/data/ai-status'
|
||||
import { AIStatusService } from 'src/app/services/ai-status.service'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-ai-status-indicator',
|
||||
templateUrl: './ai-status-indicator.component.html',
|
||||
styleUrls: ['./ai-status-indicator.component.scss'],
|
||||
imports: [
|
||||
DatePipe,
|
||||
NgbPopoverModule,
|
||||
NgbTooltipModule,
|
||||
NgxBootstrapIconsModule,
|
||||
RouterModule,
|
||||
],
|
||||
})
|
||||
export class AIStatusIndicatorComponent implements OnInit, OnDestroy {
|
||||
private aiStatusService = inject(AIStatusService)
|
||||
private router = inject(Router)
|
||||
|
||||
private subscription: Subscription
|
||||
|
||||
public aiStatus: AIStatus = {
|
||||
active: false,
|
||||
processing: false,
|
||||
documents_scanned_today: 0,
|
||||
suggestions_applied: 0,
|
||||
pending_deletion_requests: 0,
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subscription = this.aiStatusService
|
||||
.getStatus()
|
||||
.subscribe((status) => {
|
||||
this.aiStatus = status
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription?.unsubscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate icon name based on AI status
|
||||
*/
|
||||
get iconName(): string {
|
||||
if (!this.aiStatus.active) {
|
||||
return 'robot' // Inactive
|
||||
}
|
||||
if (this.aiStatus.processing) {
|
||||
return 'robot' // Processing (will add animation via CSS)
|
||||
}
|
||||
return 'robot' // Active
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSS class for the icon based on status
|
||||
*/
|
||||
get iconClass(): string {
|
||||
const classes = ['ai-status-icon']
|
||||
|
||||
if (!this.aiStatus.active) {
|
||||
classes.push('inactive')
|
||||
} else if (this.aiStatus.processing) {
|
||||
classes.push('processing')
|
||||
} else {
|
||||
classes.push('active')
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to AI configuration settings
|
||||
*/
|
||||
navigateToSettings(): void {
|
||||
this.router.navigate(['/settings'], { fragment: 'ai-scanner' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any alerts (pending deletion requests)
|
||||
*/
|
||||
get hasAlerts(): boolean {
|
||||
return this.aiStatus.pending_deletion_requests > 0
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
</div>
|
||||
<ul ngbNav class="order-sm-3">
|
||||
<pngx-toasts-dropdown></pngx-toasts-dropdown>
|
||||
<pngx-ai-status-indicator></pngx-ai-status-indicator>
|
||||
<li ngbDropdown class="nav-item dropdown">
|
||||
<button class="btn ps-1 border-0" id="userDropdown" ngbDropdownToggle>
|
||||
<i-bs width="1.3em" height="1.3em" name="person-circle"></i-bs>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import { environment } from 'src/environments/environment'
|
|||
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
|
||||
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
|
||||
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||
import { AIStatusIndicatorComponent } from './ai-status-indicator/ai-status-indicator.component'
|
||||
import { GlobalSearchComponent } from './global-search/global-search.component'
|
||||
import { ToastsDropdownComponent } from './toasts-dropdown/toasts-dropdown.component'
|
||||
|
||||
|
|
@ -59,6 +60,7 @@ import { ToastsDropdownComponent } from './toasts-dropdown/toasts-dropdown.compo
|
|||
DocumentTitlePipe,
|
||||
IfPermissionsDirective,
|
||||
ToastsDropdownComponent,
|
||||
AIStatusIndicatorComponent,
|
||||
RouterModule,
|
||||
NgClass,
|
||||
NgbDropdownModule,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,244 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title" i18n>Deletion Request Details</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
aria-label="Close"
|
||||
(click)="activeModal.dismiss()"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Request Information -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" i18n>Request Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3"><strong i18n>ID:</strong></div>
|
||||
<div class="col-md-9">{{ deletionRequest.id }}</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3"><strong i18n>Status:</strong></div>
|
||||
<div class="col-md-9">
|
||||
<span
|
||||
class="badge"
|
||||
[ngClass]="{
|
||||
'bg-warning text-dark': deletionRequest.status === DeletionRequestStatus.Pending,
|
||||
'bg-success': deletionRequest.status === DeletionRequestStatus.Approved,
|
||||
'bg-danger': deletionRequest.status === DeletionRequestStatus.Rejected,
|
||||
'bg-info': deletionRequest.status === DeletionRequestStatus.Completed,
|
||||
'bg-secondary': deletionRequest.status === DeletionRequestStatus.Cancelled
|
||||
}"
|
||||
>
|
||||
{{ deletionRequest.status }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3"><strong i18n>Created:</strong></div>
|
||||
<div class="col-md-9">
|
||||
{{ deletionRequest.created_at | customDate:'medium' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3"><strong i18n>User:</strong></div>
|
||||
<div class="col-md-9">{{ deletionRequest.user_username }}</div>
|
||||
</div>
|
||||
@if (deletionRequest.reviewed_by_username) {
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3"><strong i18n>Reviewed By:</strong></div>
|
||||
<div class="col-md-9">{{ deletionRequest.reviewed_by_username }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (deletionRequest.reviewed_at) {
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-3"><strong i18n>Reviewed At:</strong></div>
|
||||
<div class="col-md-9">
|
||||
{{ deletionRequest.reviewed_at | customDate:'medium' }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Reason -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" i18n>AI Reason</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ deletionRequest.ai_reason }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Impact Analysis -->
|
||||
@if (deletionRequest.impact_summary) {
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5 class="mb-0" i18n>
|
||||
<i-bs name="exclamation-triangle"></i-bs> Impact Analysis
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-3 bg-light rounded">
|
||||
<h3 class="mb-0">{{ deletionRequest.impact_summary.document_count }}</h3>
|
||||
<small class="text-muted" i18n>Documents</small>
|
||||
</div>
|
||||
</div>
|
||||
@if (deletionRequest.impact_summary.affected_tags?.length > 0) {
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-3 bg-light rounded">
|
||||
<h3 class="mb-0">{{ deletionRequest.impact_summary.affected_tags.length }}</h3>
|
||||
<small class="text-muted" i18n>Affected Tags</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (deletionRequest.impact_summary.affected_correspondents?.length > 0) {
|
||||
<div class="col-md-4">
|
||||
<div class="text-center p-3 bg-light rounded">
|
||||
<h3 class="mb-0">{{ deletionRequest.impact_summary.affected_correspondents.length }}</h3>
|
||||
<small class="text-muted" i18n>Affected Correspondents</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (deletionRequest.impact_summary.affected_tags?.length > 0) {
|
||||
<div class="mb-3">
|
||||
<strong i18n>Affected Tags:</strong>
|
||||
<div class="mt-1">
|
||||
@for (tag of deletionRequest.impact_summary.affected_tags; track tag) {
|
||||
<span class="badge bg-secondary me-1">{{ tag }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (deletionRequest.impact_summary.affected_correspondents?.length > 0) {
|
||||
<div class="mb-3">
|
||||
<strong i18n>Affected Correspondents:</strong>
|
||||
<div class="mt-1">
|
||||
@for (correspondent of deletionRequest.impact_summary.affected_correspondents; track correspondent) {
|
||||
<span class="badge bg-info me-1">{{ correspondent }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (deletionRequest.impact_summary.affected_types?.length > 0) {
|
||||
<div class="mb-3">
|
||||
<strong i18n>Affected Document Types:</strong>
|
||||
<div class="mt-1">
|
||||
@for (type of deletionRequest.impact_summary.affected_types; track type) {
|
||||
<span class="badge bg-primary me-1">{{ type }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Documents List -->
|
||||
@if (deletionRequest.documents_detail && deletionRequest.documents_detail.length > 0) {
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" i18n>Documents to be Deleted</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" i18n>ID</th>
|
||||
<th scope="col" i18n>Title</th>
|
||||
<th scope="col" i18n>Correspondent</th>
|
||||
<th scope="col" i18n>Type</th>
|
||||
<th scope="col" i18n>Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (doc of deletionRequest.documents_detail; track doc.id) {
|
||||
<tr>
|
||||
<td>{{ doc.id }}</td>
|
||||
<td>{{ doc.title }}</td>
|
||||
<td>{{ doc.correspondent || '-' }}</td>
|
||||
<td>{{ doc.document_type || '-' }}</td>
|
||||
<td>
|
||||
@for (tag of doc.tags; track tag) {
|
||||
<span class="badge bg-secondary me-1">{{ tag }}</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Review Comment (if exists or editable) -->
|
||||
@if (deletionRequest.review_comment || canModify()) {
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0" i18n>Review Comment</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (deletionRequest.review_comment && !canModify()) {
|
||||
<p>{{ deletionRequest.review_comment }}</p>
|
||||
} @else if (canModify()) {
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="3"
|
||||
[(ngModel)]="reviewComment"
|
||||
placeholder="Add a comment (optional)"
|
||||
i18n-placeholder
|
||||
></textarea>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
@if (canModify()) {
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger"
|
||||
(click)="reject()"
|
||||
[disabled]="isProcessing"
|
||||
>
|
||||
@if (isProcessing) {
|
||||
<span class="spinner-border spinner-border-sm me-1"></span>
|
||||
}
|
||||
<i-bs name="x-circle"></i-bs>
|
||||
<ng-container i18n>Reject</ng-container>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-success"
|
||||
(click)="approve()"
|
||||
[disabled]="isProcessing"
|
||||
>
|
||||
@if (isProcessing) {
|
||||
<span class="spinner-border spinner-border-sm me-1"></span>
|
||||
}
|
||||
<i-bs name="check-circle"></i-bs>
|
||||
<ng-container i18n>Approve</ng-container>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="activeModal.dismiss()"
|
||||
[disabled]="isProcessing"
|
||||
i18n
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -0,0 +1 @@
|
|||
// Detail component styles
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { DeletionRequestDetailComponent } from './deletion-request-detail.component'
|
||||
import { DeletionRequestService } from 'src/app/services/rest/deletion-request.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
|
||||
describe('DeletionRequestDetailComponent', () => {
|
||||
let component: DeletionRequestDetailComponent
|
||||
let fixture: ComponentFixture<DeletionRequestDetailComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DeletionRequestDetailComponent, HttpClientTestingModule],
|
||||
providers: [NgbActiveModal, DeletionRequestService, ToastService],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(DeletionRequestDetailComponent)
|
||||
component = fixture.componentInstance
|
||||
component.deletionRequest = {
|
||||
id: 1,
|
||||
created_at: '2024-01-01',
|
||||
updated_at: '2024-01-01',
|
||||
requested_by_ai: true,
|
||||
ai_reason: 'Test reason',
|
||||
user: 1,
|
||||
user_username: 'testuser',
|
||||
status: 'pending' as any,
|
||||
documents: [1, 2],
|
||||
documents_detail: [],
|
||||
document_count: 2,
|
||||
impact_summary: {
|
||||
document_count: 2,
|
||||
documents: [],
|
||||
affected_tags: [],
|
||||
affected_correspondents: [],
|
||||
affected_types: [],
|
||||
},
|
||||
}
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import { CommonModule } from '@angular/common'
|
||||
import { Component, inject, Input } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import {
|
||||
DeletionRequest,
|
||||
DeletionRequestStatus,
|
||||
} from 'src/app/data/deletion-request'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { DeletionRequestService } from 'src/app/services/rest/deletion-request.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-deletion-request-detail',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgxBootstrapIconsModule,
|
||||
CustomDatePipe,
|
||||
],
|
||||
templateUrl: './deletion-request-detail.component.html',
|
||||
styleUrls: ['./deletion-request-detail.component.scss'],
|
||||
})
|
||||
export class DeletionRequestDetailComponent {
|
||||
@Input() deletionRequest: DeletionRequest
|
||||
|
||||
public DeletionRequestStatus = DeletionRequestStatus
|
||||
public activeModal = inject(NgbActiveModal)
|
||||
private deletionRequestService = inject(DeletionRequestService)
|
||||
private toastService = inject(ToastService)
|
||||
|
||||
public reviewComment: string = ''
|
||||
public isProcessing: boolean = false
|
||||
|
||||
approve(): void {
|
||||
if (this.isProcessing) return
|
||||
|
||||
this.isProcessing = true
|
||||
this.deletionRequestService
|
||||
.approve(this.deletionRequest.id, this.reviewComment)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.toastService.showInfo(
|
||||
$localize`Deletion request approved successfully`
|
||||
)
|
||||
this.isProcessing = false
|
||||
this.activeModal.close('approved')
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.showError(
|
||||
$localize`Error approving deletion request`,
|
||||
error
|
||||
)
|
||||
this.isProcessing = false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
reject(): void {
|
||||
if (this.isProcessing) return
|
||||
|
||||
this.isProcessing = true
|
||||
this.deletionRequestService
|
||||
.reject(this.deletionRequest.id, this.reviewComment)
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.toastService.showInfo(
|
||||
$localize`Deletion request rejected successfully`
|
||||
)
|
||||
this.isProcessing = false
|
||||
this.activeModal.close('rejected')
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.showError(
|
||||
$localize`Error rejecting deletion request`,
|
||||
error
|
||||
)
|
||||
this.isProcessing = false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
canModify(): boolean {
|
||||
return this.deletionRequest.status === DeletionRequestStatus.Pending
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
<pngx-page-header
|
||||
title="Deletion Requests"
|
||||
i18n-title
|
||||
info="Manage AI-initiated deletion requests. Review and approve or reject document deletions recommended by the AI system."
|
||||
i18n-info
|
||||
>
|
||||
@if (getPendingCount() > 0) {
|
||||
<div class="badge bg-warning text-dark ms-2">
|
||||
{{ getPendingCount() }} <ng-container i18n>pending</ng-container>
|
||||
</div>
|
||||
}
|
||||
</pngx-page-header>
|
||||
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs mb-3">
|
||||
<li [ngbNavItem]="DeletionRequestStatus.Pending">
|
||||
<button ngbNavLink (click)="onTabChange(DeletionRequestStatus.Pending)">
|
||||
<ng-container i18n>Pending</ng-container>
|
||||
@if (getStatusCount(DeletionRequestStatus.Pending) > 0) {
|
||||
<span class="badge bg-warning text-dark ms-1">
|
||||
{{ getStatusCount(DeletionRequestStatus.Pending) }}
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
</li>
|
||||
<li [ngbNavItem]="DeletionRequestStatus.Approved">
|
||||
<button ngbNavLink (click)="onTabChange(DeletionRequestStatus.Approved)">
|
||||
<ng-container i18n>Approved</ng-container>
|
||||
</button>
|
||||
</li>
|
||||
<li [ngbNavItem]="DeletionRequestStatus.Rejected">
|
||||
<button ngbNavLink (click)="onTabChange(DeletionRequestStatus.Rejected)">
|
||||
<ng-container i18n>Rejected</ng-container>
|
||||
</button>
|
||||
</li>
|
||||
<li [ngbNavItem]="DeletionRequestStatus.Completed">
|
||||
<button ngbNavLink (click)="onTabChange(DeletionRequestStatus.Completed)">
|
||||
<ng-container i18n>Completed</ng-container>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
|
||||
@if (deletionRequestService.loading) {
|
||||
<div class="text-center my-5">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden" i18n>Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
} @else if (filteredRequests.length === 0) {
|
||||
<div class="alert alert-info" i18n>
|
||||
No deletion requests found with status: {{ activeTab }}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-striped align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" i18n>ID</th>
|
||||
<th scope="col" i18n>Created</th>
|
||||
<th scope="col" i18n>Documents</th>
|
||||
<th scope="col" i18n>AI Reason</th>
|
||||
<th scope="col" i18n>Status</th>
|
||||
<th scope="col" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (request of filteredRequests | slice: (page-1) * pageSize : page * pageSize; track request.id) {
|
||||
<tr (click)="viewDetails(request)" style="cursor: pointer;">
|
||||
<td>{{ request.id }}</td>
|
||||
<td>{{ request.created_at | customDate:'short' }}</td>
|
||||
<td>
|
||||
<span class="badge bg-primary">
|
||||
{{ request.document_count }} <ng-container i18n>docs</ng-container>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-truncate" style="max-width: 300px;" [ngbTooltip]="request.ai_reason">
|
||||
{{ request.ai_reason }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" [ngClass]="getStatusBadgeClass(request.status)">
|
||||
{{ request.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
(click)="viewDetails(request); $event.stopPropagation()"
|
||||
i18n
|
||||
>
|
||||
<i-bs name="eye"></i-bs> View Details
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if (collectionSize > pageSize) {
|
||||
<div class="d-flex justify-content-center mt-3">
|
||||
<ngb-pagination
|
||||
[(page)]="page"
|
||||
[pageSize]="pageSize"
|
||||
[collectionSize]="collectionSize"
|
||||
[maxSize]="5"
|
||||
[rotate]="true"
|
||||
[boundaryLinks]="true"
|
||||
></ngb-pagination>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// Component-specific styles for deletion requests
|
||||
.text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { DeletionRequestsComponent } from './deletion-requests.component'
|
||||
import { DeletionRequestService } from 'src/app/services/rest/deletion-request.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
|
||||
describe('DeletionRequestsComponent', () => {
|
||||
let component: DeletionRequestsComponent
|
||||
let fixture: ComponentFixture<DeletionRequestsComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DeletionRequestsComponent, HttpClientTestingModule],
|
||||
providers: [DeletionRequestService, ToastService],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(DeletionRequestsComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
import { CommonModule } from '@angular/common'
|
||||
import { Component, OnInit, OnDestroy, inject } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import {
|
||||
NgbModal,
|
||||
NgbNavModule,
|
||||
NgbPaginationModule,
|
||||
NgbTooltipModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Subject, takeUntil } from 'rxjs'
|
||||
import {
|
||||
DeletionRequest,
|
||||
DeletionRequestStatus,
|
||||
} from 'src/app/data/deletion-request'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { DeletionRequestService } from 'src/app/services/rest/deletion-request.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { PageHeaderComponent } from '../common/page-header/page-header.component'
|
||||
import { LoadingComponentWithPermissions } from '../loading-component/loading.component'
|
||||
import { DeletionRequestDetailComponent } from './deletion-request-detail/deletion-request-detail.component'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-deletion-requests',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgbNavModule,
|
||||
NgbPaginationModule,
|
||||
NgbTooltipModule,
|
||||
NgxBootstrapIconsModule,
|
||||
PageHeaderComponent,
|
||||
CustomDatePipe,
|
||||
],
|
||||
templateUrl: './deletion-requests.component.html',
|
||||
styleUrls: ['./deletion-requests.component.scss'],
|
||||
})
|
||||
export class DeletionRequestsComponent
|
||||
extends LoadingComponentWithPermissions
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
public DeletionRequestStatus = DeletionRequestStatus
|
||||
|
||||
public deletionRequestService = inject(DeletionRequestService)
|
||||
private modalService = inject(NgbModal)
|
||||
private toastService = inject(ToastService)
|
||||
protected unsubscribeNotifier: Subject<void> = new Subject()
|
||||
|
||||
public deletionRequests: DeletionRequest[] = []
|
||||
public filteredRequests: DeletionRequest[] = []
|
||||
public activeTab: DeletionRequestStatus = DeletionRequestStatus.Pending
|
||||
public page: number = 1
|
||||
public pageSize: number = 25
|
||||
public collectionSize: number = 0
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadDeletionRequests()
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.unsubscribeNotifier.next()
|
||||
this.unsubscribeNotifier.complete()
|
||||
}
|
||||
|
||||
loadDeletionRequests(): void {
|
||||
this.deletionRequestService
|
||||
.listAll()
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.deletionRequests = result.results
|
||||
this.filterByStatus()
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.showError(
|
||||
$localize`Error loading deletion requests`,
|
||||
error
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
filterByStatus(): void {
|
||||
this.filteredRequests = this.deletionRequests.filter(
|
||||
(req) => req.status === this.activeTab
|
||||
)
|
||||
this.collectionSize = this.filteredRequests.length
|
||||
this.page = 1
|
||||
}
|
||||
|
||||
onTabChange(status: DeletionRequestStatus): void {
|
||||
this.activeTab = status
|
||||
this.filterByStatus()
|
||||
}
|
||||
|
||||
viewDetails(request: DeletionRequest): void {
|
||||
const modalRef = this.modalService.open(DeletionRequestDetailComponent, {
|
||||
size: 'xl',
|
||||
backdrop: 'static',
|
||||
})
|
||||
modalRef.componentInstance.deletionRequest = request
|
||||
modalRef.result.then(
|
||||
(result) => {
|
||||
if (result === 'approved' || result === 'rejected') {
|
||||
this.loadDeletionRequests()
|
||||
}
|
||||
},
|
||||
() => {
|
||||
// Modal dismissed
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
getStatusBadgeClass(status: DeletionRequestStatus): string {
|
||||
switch (status) {
|
||||
case DeletionRequestStatus.Pending:
|
||||
return 'bg-warning text-dark'
|
||||
case DeletionRequestStatus.Approved:
|
||||
return 'bg-success'
|
||||
case DeletionRequestStatus.Rejected:
|
||||
return 'bg-danger'
|
||||
case DeletionRequestStatus.Completed:
|
||||
return 'bg-info'
|
||||
case DeletionRequestStatus.Cancelled:
|
||||
return 'bg-secondary'
|
||||
default:
|
||||
return 'bg-secondary'
|
||||
}
|
||||
}
|
||||
|
||||
getPendingCount(): number {
|
||||
return this.deletionRequests.filter(
|
||||
(req) => req.status === DeletionRequestStatus.Pending
|
||||
).length
|
||||
}
|
||||
|
||||
getStatusCount(status: DeletionRequestStatus): number {
|
||||
return this.deletionRequests.filter((req) => req.status === status).length
|
||||
}
|
||||
}
|
||||
|
|
@ -118,6 +118,13 @@
|
|||
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
|
||||
</div>
|
||||
|
||||
<pngx-ai-suggestions-panel
|
||||
[suggestions]="aiSuggestions"
|
||||
[disabled]="!userCanEdit"
|
||||
(apply)="onApplySuggestion($event)"
|
||||
(reject)="onRejectSuggestion($event)">
|
||||
</pngx-ai-suggestions-panel>
|
||||
|
||||
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
|
||||
<li [ngbNavItem]="DocumentDetailNavIDs.Details">
|
||||
<a ngbNavLink i18n>Details</a>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ import {
|
|||
switchMap,
|
||||
takeUntil,
|
||||
} from 'rxjs/operators'
|
||||
import {
|
||||
AISuggestion,
|
||||
AISuggestionStatus,
|
||||
AISuggestionType,
|
||||
} from 'src/app/data/ai-suggestion'
|
||||
import { Correspondent } from 'src/app/data/correspondent'
|
||||
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||
import { CustomFieldInstance } from 'src/app/data/custom-field-instance'
|
||||
|
|
@ -109,6 +114,7 @@ import { ShareLinksDialogComponent } from '../common/share-links-dialog/share-li
|
|||
import { DocumentHistoryComponent } from '../document-history/document-history.component'
|
||||
import { DocumentNotesComponent } from '../document-notes/document-notes.component'
|
||||
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||
import { AiSuggestionsPanelComponent } from '../ai-suggestions-panel/ai-suggestions-panel.component'
|
||||
import { MetadataCollapseComponent } from './metadata-collapse/metadata-collapse.component'
|
||||
|
||||
enum DocumentDetailNavIDs {
|
||||
|
|
@ -151,6 +157,7 @@ export enum ZoomSetting {
|
|||
CustomFieldsDropdownComponent,
|
||||
DocumentNotesComponent,
|
||||
DocumentHistoryComponent,
|
||||
AiSuggestionsPanelComponent,
|
||||
CheckComponent,
|
||||
DateComponent,
|
||||
DocumentLinkComponent,
|
||||
|
|
@ -216,6 +223,7 @@ export class DocumentDetailComponent
|
|||
document: Document
|
||||
metadata: DocumentMetadata
|
||||
suggestions: DocumentSuggestions
|
||||
aiSuggestions: AISuggestion[] = []
|
||||
users: User[]
|
||||
|
||||
title: string
|
||||
|
|
@ -437,6 +445,7 @@ export class DocumentDetailComponent
|
|||
}
|
||||
this.documentId = doc.id
|
||||
this.suggestions = null
|
||||
this.aiSuggestions = []
|
||||
const openDocument = this.openDocumentService.getOpenDocument(
|
||||
this.documentId
|
||||
)
|
||||
|
|
@ -691,9 +700,11 @@ export class DocumentDetailComponent
|
|||
.subscribe({
|
||||
next: (result) => {
|
||||
this.suggestions = result
|
||||
this.aiSuggestions = this.convertSuggestionsToAI(result)
|
||||
},
|
||||
error: (error) => {
|
||||
this.suggestions = null
|
||||
this.aiSuggestions = []
|
||||
this.toastService.showError(
|
||||
$localize`Error retrieving suggestions.`,
|
||||
error
|
||||
|
|
@ -1542,4 +1553,124 @@ export class DocumentDetailComponent
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
private convertSuggestionsToAI(suggestions: DocumentSuggestions): AISuggestion[] {
|
||||
if (!suggestions) {
|
||||
return []
|
||||
}
|
||||
|
||||
const aiSuggestions: AISuggestion[] = []
|
||||
let id = 1
|
||||
|
||||
// Convert tag suggestions
|
||||
if (suggestions.tags && suggestions.tags.length > 0) {
|
||||
suggestions.tags.forEach((tagId) => {
|
||||
aiSuggestions.push({
|
||||
id: `tag-${id++}`,
|
||||
type: AISuggestionType.Tag,
|
||||
value: tagId,
|
||||
confidence: 0.75, // Default confidence for legacy suggestions
|
||||
status: AISuggestionStatus.Pending,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Convert correspondent suggestions
|
||||
if (suggestions.correspondents && suggestions.correspondents.length > 0) {
|
||||
suggestions.correspondents.forEach((corrId) => {
|
||||
aiSuggestions.push({
|
||||
id: `correspondent-${id++}`,
|
||||
type: AISuggestionType.Correspondent,
|
||||
value: corrId,
|
||||
confidence: 0.75,
|
||||
status: AISuggestionStatus.Pending,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Convert document type suggestions
|
||||
if (suggestions.document_types && suggestions.document_types.length > 0) {
|
||||
suggestions.document_types.forEach((docTypeId) => {
|
||||
aiSuggestions.push({
|
||||
id: `doctype-${id++}`,
|
||||
type: AISuggestionType.DocumentType,
|
||||
value: docTypeId,
|
||||
confidence: 0.75,
|
||||
status: AISuggestionStatus.Pending,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Convert storage path suggestions
|
||||
if (suggestions.storage_paths && suggestions.storage_paths.length > 0) {
|
||||
suggestions.storage_paths.forEach((storagePathId) => {
|
||||
aiSuggestions.push({
|
||||
id: `storage-${id++}`,
|
||||
type: AISuggestionType.StoragePath,
|
||||
value: storagePathId,
|
||||
confidence: 0.75,
|
||||
status: AISuggestionStatus.Pending,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Convert date suggestions
|
||||
if (suggestions.dates && suggestions.dates.length > 0) {
|
||||
suggestions.dates.forEach((date) => {
|
||||
aiSuggestions.push({
|
||||
id: `date-${id++}`,
|
||||
type: AISuggestionType.Date,
|
||||
value: date,
|
||||
confidence: 0.75,
|
||||
status: AISuggestionStatus.Pending,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return aiSuggestions
|
||||
}
|
||||
|
||||
onApplySuggestion(suggestion: AISuggestion): void {
|
||||
switch (suggestion.type) {
|
||||
case AISuggestionType.Tag:
|
||||
const currentTags = this.documentForm.get('tags').value || []
|
||||
if (!currentTags.includes(suggestion.value)) {
|
||||
this.documentForm.get('tags').setValue([...currentTags, suggestion.value])
|
||||
this.documentForm.get('tags').markAsDirty()
|
||||
}
|
||||
break
|
||||
|
||||
case AISuggestionType.Correspondent:
|
||||
this.documentForm.get('correspondent').setValue(suggestion.value)
|
||||
this.documentForm.get('correspondent').markAsDirty()
|
||||
break
|
||||
|
||||
case AISuggestionType.DocumentType:
|
||||
this.documentForm.get('document_type').setValue(suggestion.value)
|
||||
this.documentForm.get('document_type').markAsDirty()
|
||||
break
|
||||
|
||||
case AISuggestionType.StoragePath:
|
||||
this.documentForm.get('storage_path').setValue(suggestion.value)
|
||||
this.documentForm.get('storage_path').markAsDirty()
|
||||
break
|
||||
|
||||
case AISuggestionType.Date:
|
||||
const dateAdapter = new ISODateAdapter()
|
||||
const dateValue = dateAdapter.fromModel(suggestion.value)
|
||||
this.documentForm.get('created').setValue(dateValue)
|
||||
this.documentForm.get('created').markAsDirty()
|
||||
break
|
||||
|
||||
case AISuggestionType.Title:
|
||||
this.documentForm.get('title').setValue(suggestion.value)
|
||||
this.documentForm.get('title').markAsDirty()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onRejectSuggestion(suggestion: AISuggestion): void {
|
||||
// Just remove it from the list (handled by the panel component)
|
||||
// No additional action needed here
|
||||
}
|
||||
}
|
||||
|
|
|
|||
63
src-ui/src/app/data/ai-status.ts
Normal file
63
src-ui/src/app/data/ai-status.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Represents the AI scanner status and statistics
|
||||
*/
|
||||
export interface AIStatus {
|
||||
/**
|
||||
* Whether the AI scanner is currently active/enabled
|
||||
*/
|
||||
active: boolean
|
||||
|
||||
/**
|
||||
* Whether the AI scanner is currently processing documents
|
||||
*/
|
||||
processing: boolean
|
||||
|
||||
/**
|
||||
* Number of documents scanned today
|
||||
*/
|
||||
documents_scanned_today: number
|
||||
|
||||
/**
|
||||
* Number of AI suggestions applied
|
||||
*/
|
||||
suggestions_applied: number
|
||||
|
||||
/**
|
||||
* Number of pending deletion requests awaiting user approval
|
||||
*/
|
||||
pending_deletion_requests: number
|
||||
|
||||
/**
|
||||
* Last scan timestamp (ISO format)
|
||||
*/
|
||||
last_scan?: string
|
||||
|
||||
/**
|
||||
* AI scanner version or configuration info
|
||||
*/
|
||||
version?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a pending deletion request initiated by AI
|
||||
*/
|
||||
export interface DeletionRequest {
|
||||
id: number
|
||||
document_id: number
|
||||
document_title: string
|
||||
reason: string
|
||||
confidence: number
|
||||
created_at: string
|
||||
status: DeletionRequestStatus
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of a deletion request
|
||||
*/
|
||||
export enum DeletionRequestStatus {
|
||||
Pending = 'pending',
|
||||
Approved = 'approved',
|
||||
Rejected = 'rejected',
|
||||
Cancelled = 'cancelled',
|
||||
Completed = 'completed',
|
||||
}
|
||||
32
src-ui/src/app/data/ai-suggestion.ts
Normal file
32
src-ui/src/app/data/ai-suggestion.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
export enum AISuggestionType {
|
||||
Tag = 'tag',
|
||||
Correspondent = 'correspondent',
|
||||
DocumentType = 'document_type',
|
||||
StoragePath = 'storage_path',
|
||||
CustomField = 'custom_field',
|
||||
Date = 'date',
|
||||
Title = 'title',
|
||||
}
|
||||
|
||||
export enum AISuggestionStatus {
|
||||
Pending = 'pending',
|
||||
Applied = 'applied',
|
||||
Rejected = 'rejected',
|
||||
}
|
||||
|
||||
export interface AISuggestion {
|
||||
id: string
|
||||
type: AISuggestionType
|
||||
value: any
|
||||
confidence: number
|
||||
status: AISuggestionStatus
|
||||
label?: string
|
||||
field_name?: string // For custom fields
|
||||
created_at?: Date
|
||||
}
|
||||
|
||||
export interface AIDocumentSuggestions {
|
||||
document_id: number
|
||||
suggestions: AISuggestion[]
|
||||
generated_at: Date
|
||||
}
|
||||
50
src-ui/src/app/data/deletion-request.ts
Normal file
50
src-ui/src/app/data/deletion-request.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { ObjectWithId } from './object-with-id'
|
||||
|
||||
export interface DeletionRequestDocument {
|
||||
id: number
|
||||
title: string
|
||||
created: string
|
||||
correspondent?: string
|
||||
document_type?: string
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export interface DeletionRequestImpactSummary {
|
||||
document_count: number
|
||||
documents: DeletionRequestDocument[]
|
||||
affected_tags: string[]
|
||||
affected_correspondents: string[]
|
||||
affected_types: string[]
|
||||
date_range?: {
|
||||
earliest: string
|
||||
latest: string
|
||||
}
|
||||
}
|
||||
|
||||
export enum DeletionRequestStatus {
|
||||
Pending = 'pending',
|
||||
Approved = 'approved',
|
||||
Rejected = 'rejected',
|
||||
Cancelled = 'cancelled',
|
||||
Completed = 'completed',
|
||||
}
|
||||
|
||||
export interface DeletionRequest extends ObjectWithId {
|
||||
created_at: string
|
||||
updated_at: string
|
||||
requested_by_ai: boolean
|
||||
ai_reason: string
|
||||
user: number
|
||||
user_username: string
|
||||
status: DeletionRequestStatus
|
||||
documents: number[]
|
||||
documents_detail: DeletionRequestDocument[]
|
||||
document_count: number
|
||||
impact_summary: DeletionRequestImpactSummary
|
||||
reviewed_at?: string
|
||||
reviewed_by?: number
|
||||
reviewed_by_username?: string
|
||||
review_comment?: string
|
||||
completed_at?: string
|
||||
completion_details?: any
|
||||
}
|
||||
87
src-ui/src/app/services/ai-status.service.spec.ts
Normal file
87
src-ui/src/app/services/ai-status.service.spec.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing'
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { AIStatus } from 'src/app/data/ai-status'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { AIStatusService } from './ai-status.service'
|
||||
|
||||
describe('AIStatusService', () => {
|
||||
let service: AIStatusService
|
||||
let httpMock: HttpTestingController
|
||||
|
||||
const mockAIStatus: AIStatus = {
|
||||
active: true,
|
||||
processing: false,
|
||||
documents_scanned_today: 42,
|
||||
suggestions_applied: 15,
|
||||
pending_deletion_requests: 2,
|
||||
last_scan: '2025-11-15T12:00:00Z',
|
||||
version: '1.0.0',
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [AIStatusService],
|
||||
})
|
||||
service = TestBed.inject(AIStatusService)
|
||||
httpMock = TestBed.inject(HttpTestingController)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
httpMock.verify()
|
||||
})
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return AI status as observable', (done) => {
|
||||
service.getStatus().subscribe((status) => {
|
||||
expect(status).toBeDefined()
|
||||
expect(status.active).toBeDefined()
|
||||
expect(status.processing).toBeDefined()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should return current status value', () => {
|
||||
const status = service.getCurrentStatus()
|
||||
expect(status).toBeDefined()
|
||||
expect(status.active).toBeDefined()
|
||||
})
|
||||
|
||||
it('should fetch AI status from backend', (done) => {
|
||||
service['fetchAIStatus']().subscribe((status) => {
|
||||
expect(status).toEqual(mockAIStatus)
|
||||
expect(service.loading).toBe(false)
|
||||
done()
|
||||
})
|
||||
|
||||
const req = httpMock.expectOne(`${environment.apiBaseUrl}ai/status/`)
|
||||
expect(req.request.method).toBe('GET')
|
||||
req.flush(mockAIStatus)
|
||||
})
|
||||
|
||||
it('should handle error and return mock data', (done) => {
|
||||
service['fetchAIStatus']().subscribe((status) => {
|
||||
expect(status).toBeDefined()
|
||||
expect(status.active).toBeDefined()
|
||||
expect(service.loading).toBe(false)
|
||||
done()
|
||||
})
|
||||
|
||||
const req = httpMock.expectOne(`${environment.apiBaseUrl}ai/status/`)
|
||||
req.error(new ProgressEvent('error'))
|
||||
})
|
||||
|
||||
it('should manually refresh status', () => {
|
||||
service.refresh()
|
||||
|
||||
const req = httpMock.expectOne(`${environment.apiBaseUrl}ai/status/`)
|
||||
expect(req.request.method).toBe('GET')
|
||||
req.flush(mockAIStatus)
|
||||
})
|
||||
})
|
||||
100
src-ui/src/app/services/ai-status.service.ts
Normal file
100
src-ui/src/app/services/ai-status.service.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable, inject } from '@angular/core'
|
||||
import { BehaviorSubject, Observable, interval } from 'rxjs'
|
||||
import { catchError, map, startWith, switchMap } from 'rxjs/operators'
|
||||
import { AIStatus } from 'src/app/data/ai-status'
|
||||
import { environment } from 'src/environments/environment'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AIStatusService {
|
||||
private http = inject(HttpClient)
|
||||
|
||||
private baseUrl: string = environment.apiBaseUrl
|
||||
private aiStatusSubject = new BehaviorSubject<AIStatus>({
|
||||
active: false,
|
||||
processing: false,
|
||||
documents_scanned_today: 0,
|
||||
suggestions_applied: 0,
|
||||
pending_deletion_requests: 0,
|
||||
})
|
||||
|
||||
public loading: boolean = false
|
||||
|
||||
// Poll every 30 seconds for AI status updates
|
||||
private readonly POLL_INTERVAL = 30000
|
||||
|
||||
constructor() {
|
||||
this.startPolling()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current AI status as an observable
|
||||
*/
|
||||
public getStatus(): Observable<AIStatus> {
|
||||
return this.aiStatusSubject.asObservable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current AI status value
|
||||
*/
|
||||
public getCurrentStatus(): AIStatus {
|
||||
return this.aiStatusSubject.value
|
||||
}
|
||||
|
||||
/**
|
||||
* Start polling for AI status updates
|
||||
*/
|
||||
private startPolling(): void {
|
||||
interval(this.POLL_INTERVAL)
|
||||
.pipe(
|
||||
startWith(0), // Emit immediately on subscription
|
||||
switchMap(() => this.fetchAIStatus())
|
||||
)
|
||||
.subscribe((status) => {
|
||||
this.aiStatusSubject.next(status)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch AI status from the backend
|
||||
*/
|
||||
private fetchAIStatus(): Observable<AIStatus> {
|
||||
this.loading = true
|
||||
|
||||
return this.http
|
||||
.get<AIStatus>(`${this.baseUrl}ai/status/`)
|
||||
.pipe(
|
||||
map((status) => {
|
||||
this.loading = false
|
||||
return status
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.loading = false
|
||||
console.warn('Failed to fetch AI status, using mock data:', error)
|
||||
// Return mock data if endpoint doesn't exist yet
|
||||
return [
|
||||
{
|
||||
active: true,
|
||||
processing: false,
|
||||
documents_scanned_today: 42,
|
||||
suggestions_applied: 15,
|
||||
pending_deletion_requests: 2,
|
||||
last_scan: new Date().toISOString(),
|
||||
version: '1.0.0',
|
||||
},
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually refresh the AI status
|
||||
*/
|
||||
public refresh(): void {
|
||||
this.fetchAIStatus().subscribe((status) => {
|
||||
this.aiStatusSubject.next(status)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing'
|
||||
import { DeletionRequestService } from './deletion-request.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
|
||||
describe('DeletionRequestService', () => {
|
||||
let service: DeletionRequestService
|
||||
let httpMock: HttpTestingController
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [DeletionRequestService],
|
||||
})
|
||||
service = TestBed.inject(DeletionRequestService)
|
||||
httpMock = TestBed.inject(HttpTestingController)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
httpMock.verify()
|
||||
})
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should get pending count', () => {
|
||||
const mockResponse = { count: 5 }
|
||||
|
||||
service.getPendingCount().subscribe((response) => {
|
||||
expect(response.count).toBe(5)
|
||||
})
|
||||
|
||||
const req = httpMock.expectOne(
|
||||
`${environment.apiBaseUrl}deletion_requests/pending_count/`
|
||||
)
|
||||
expect(req.request.method).toBe('GET')
|
||||
req.flush(mockResponse)
|
||||
})
|
||||
|
||||
it('should approve a deletion request', () => {
|
||||
const mockRequest = {
|
||||
id: 1,
|
||||
status: 'approved',
|
||||
}
|
||||
|
||||
service.approve(1, 'Approved').subscribe((response) => {
|
||||
expect(response.status).toBe('approved')
|
||||
})
|
||||
|
||||
const req = httpMock.expectOne(
|
||||
`${environment.apiBaseUrl}deletion_requests/1/approve/`
|
||||
)
|
||||
expect(req.request.method).toBe('POST')
|
||||
expect(req.request.body).toEqual({ review_comment: 'Approved' })
|
||||
req.flush(mockRequest)
|
||||
})
|
||||
|
||||
it('should reject a deletion request', () => {
|
||||
const mockRequest = {
|
||||
id: 1,
|
||||
status: 'rejected',
|
||||
}
|
||||
|
||||
service.reject(1, 'Rejected').subscribe((response) => {
|
||||
expect(response.status).toBe('rejected')
|
||||
})
|
||||
|
||||
const req = httpMock.expectOne(
|
||||
`${environment.apiBaseUrl}deletion_requests/1/reject/`
|
||||
)
|
||||
expect(req.request.method).toBe('POST')
|
||||
expect(req.request.body).toEqual({ review_comment: 'Rejected' })
|
||||
req.flush(mockRequest)
|
||||
})
|
||||
})
|
||||
62
src-ui/src/app/services/rest/deletion-request.service.ts
Normal file
62
src-ui/src/app/services/rest/deletion-request.service.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { tap } from 'rxjs/operators'
|
||||
import { DeletionRequest } from 'src/app/data/deletion-request'
|
||||
import { AbstractPaperlessService } from './abstract-paperless-service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DeletionRequestService extends AbstractPaperlessService<DeletionRequest> {
|
||||
constructor() {
|
||||
super()
|
||||
this.resourceName = 'deletion_requests'
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve a deletion request
|
||||
* @param id The ID of the deletion request
|
||||
* @param reviewComment Optional comment for the approval
|
||||
* @returns Observable of the updated deletion request
|
||||
*/
|
||||
approve(id: number, reviewComment?: string): Observable<DeletionRequest> {
|
||||
this._loading = true
|
||||
const body = reviewComment ? { review_comment: reviewComment } : {}
|
||||
return this.http
|
||||
.post<DeletionRequest>(this.getResourceUrl(id, 'approve'), body)
|
||||
.pipe(
|
||||
tap(() => {
|
||||
this._loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject a deletion request
|
||||
* @param id The ID of the deletion request
|
||||
* @param reviewComment Optional comment for the rejection
|
||||
* @returns Observable of the updated deletion request
|
||||
*/
|
||||
reject(id: number, reviewComment?: string): Observable<DeletionRequest> {
|
||||
this._loading = true
|
||||
const body = reviewComment ? { review_comment: reviewComment } : {}
|
||||
return this.http
|
||||
.post<DeletionRequest>(this.getResourceUrl(id, 'reject'), body)
|
||||
.pipe(
|
||||
tap(() => {
|
||||
this._loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the count of pending deletion requests
|
||||
* @returns Observable with the count
|
||||
*/
|
||||
getPendingCount(): Observable<{ count: number }> {
|
||||
return this.http.get<{ count: number }>(
|
||||
this.getResourceUrl(null, 'pending_count')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from '@angular/common/http'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'
|
||||
import { provideAnimations } from '@angular/platform-browser/animations'
|
||||
import {
|
||||
NgbDateAdapter,
|
||||
NgbDateParserFormatter,
|
||||
|
|
@ -56,11 +57,14 @@ import {
|
|||
checkLg,
|
||||
chevronDoubleLeft,
|
||||
chevronDoubleRight,
|
||||
chevronDown,
|
||||
chevronRight,
|
||||
chevronUp,
|
||||
clipboard,
|
||||
clipboardCheck,
|
||||
clipboardCheckFill,
|
||||
clipboardFill,
|
||||
clock,
|
||||
clockHistory,
|
||||
dash,
|
||||
dashCircle,
|
||||
|
|
@ -71,6 +75,7 @@ import {
|
|||
envelope,
|
||||
envelopeAt,
|
||||
envelopeAtFill,
|
||||
exclamationCircle,
|
||||
exclamationCircleFill,
|
||||
exclamationTriangle,
|
||||
exclamationTriangleFill,
|
||||
|
|
@ -81,6 +86,7 @@ import {
|
|||
fileEarmarkLock,
|
||||
fileEarmarkMinus,
|
||||
fileEarmarkRichtext,
|
||||
fileEarmarkText,
|
||||
fileText,
|
||||
files,
|
||||
filter,
|
||||
|
|
@ -95,11 +101,14 @@ import {
|
|||
hddStack,
|
||||
house,
|
||||
infoCircle,
|
||||
inputCursorText,
|
||||
journals,
|
||||
lightbulb,
|
||||
link,
|
||||
listNested,
|
||||
listTask,
|
||||
listUl,
|
||||
magic,
|
||||
microsoft,
|
||||
nodePlus,
|
||||
pencil,
|
||||
|
|
@ -270,11 +279,14 @@ const icons = {
|
|||
checkLg,
|
||||
chevronDoubleLeft,
|
||||
chevronDoubleRight,
|
||||
chevronDown,
|
||||
chevronRight,
|
||||
chevronUp,
|
||||
clipboard,
|
||||
clipboardCheck,
|
||||
clipboardCheckFill,
|
||||
clipboardFill,
|
||||
clock,
|
||||
clockHistory,
|
||||
dash,
|
||||
dashCircle,
|
||||
|
|
@ -285,6 +297,7 @@ const icons = {
|
|||
envelope,
|
||||
envelopeAt,
|
||||
envelopeAtFill,
|
||||
exclamationCircle,
|
||||
exclamationCircleFill,
|
||||
exclamationTriangle,
|
||||
exclamationTriangleFill,
|
||||
|
|
@ -295,6 +308,7 @@ const icons = {
|
|||
fileEarmarkLock,
|
||||
fileEarmarkMinus,
|
||||
fileEarmarkRichtext,
|
||||
fileEarmarkText,
|
||||
files,
|
||||
fileText,
|
||||
filter,
|
||||
|
|
@ -309,11 +323,14 @@ const icons = {
|
|||
hddStack,
|
||||
house,
|
||||
infoCircle,
|
||||
inputCursorText,
|
||||
journals,
|
||||
lightbulb,
|
||||
link,
|
||||
listNested,
|
||||
listTask,
|
||||
listUl,
|
||||
magic,
|
||||
microsoft,
|
||||
nodePlus,
|
||||
pencil,
|
||||
|
|
@ -402,5 +419,6 @@ bootstrapApplication(AppComponent, {
|
|||
DocumentTypeNamePipe,
|
||||
StoragePathNamePipe,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideAnimations(),
|
||||
],
|
||||
}).catch((err) => console.error(err))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue