Compare commits
2 Commits
master
...
release/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a97697ba46 | ||
|
|
38ac7acd86 |
5
.dev/export_summary.csv
Normal file
5
.dev/export_summary.csv
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
PMRA Document Number,GAP Active Ingredient,Regulatory Utility,Document Comment,SIC Number,Protocol Number,Protocol Number,Data Evaluation Record,Audience,Original Retirement Date,Regulatory Released,Regulatory Authority,Regulatory Authority (legacy to remove),Source Owner,Legacy Primary OECD Code,Legacy Source Organisation,Regulator Comments,Contains Registered Composition?,File Name,FTP Source Location,Submission Format Needed,Legacy Report Number,Sales Unit,Address,ANVISA Process Number,Source Number,Legacy Migration Path,Review Completion Date,Registration Number,Agency Dossier Registration Type,Submission Version,Media Comments,Literature Citation,Document Language,Notes,Study Location,Materials,Approver,Subtype,Test Facility,Is Latest Version,Consumer,Classification,Guideline,TRP Type,Legacy System Date,Function,Artist,Document Title,PI Number,Legacy Version Description,PI Number,Incoming from,Annotations (Unresolved),Work To Date,Copyright Clearance Obtained?,Summary Type,Annotations (Claim Links),Latest Source Minor Version,Regulatory Category,SYN Letter Number,Work From Date,Product,Version Creation Date,Governance Committee,Test Facility (legacy to remove),Regulatory Finalization Date,Imported file?,Status,Information Tracking Submission Package,Approval Type,Media Description,Duration,Legacy System,Archive Date,Exporting country,Regulatory Tracking Number,Annotations (Anchors),Information Tracking,Document Number,GAP Usage Information,Report Number,Legacy Reg Document Number,Legal Representative 2,Registering Company,File Created By,Legacy EPA Decision Code,Meeting Minutes,Product Safety Finalization Date,Report Type,Archived Date,Legacy Other PMRA Data Codes,Template Document Type,Color Space,Legal Representative 1,Document Author,CrossLink,Annotations (Notes),Suggested Links,Assessment Type,Legacy Species Commodity,TK Number,Legacy Test Facilities,Planned Completion Date,PRF Number,Registration Item Country,Legacy Target Species,Legacy Owning Organisation,Archive Number,Protocol Type,Submission Output Format,Submission Date,Coordinator,Regulatory Reporting Date,Annotations (Lines),Field Trial Number,Batch Number,Legacy Version Number,External ID,Author Names,Sponsor Organisation (legacy to remove),Literature Type,Sponsor Organisation,Legacy Crop Host,Document Edition,Agreement Expiry Date,TRP Usage,Legacy EPA Submission Type,Security Classification,Source Document Name,Syngenta Address,Outgoing to (legacy to remove),Link Status,Legacy System Document ID,Copyright,PMRA Registration Number,Annotations (All),SU,Source Vault Name,Latest Source Major Version,Legacy Other OECD Codes,Version Created By,Subarea,Reviewer,Owning Organisation (legacy to remove),Project,Viewer,Regulatory Territory,Lifecycle,Claims,Bound Source Minor Version,Global ID,Legacy Regulatory Authorities,Outgoing to,Additional Information,Year,Organism,File Last Modified By,Requires Attachment of a Signature Page?,Source Binding Rule,Destination country,Cross-Reference Submission Number,Name,Issue Topic,Pages,Type,Reason for Retiring,Rule,Type of Review,Original Finalization Date,Trade Name,Transmittal Document,Pages OCR'ed (%),Legacy Object ID(s),Protocol Number,Last Modified By,Annotations (Approved Links),Request Type,Binder,Author,Last Modified Date,Non-English Document Title,Media Location,Created By,Editor,Owner,CRO Report Number,Format,Legacy Discipline,Major Version Number,Created From,Active Ingredients,Archive Location,EPA Registration Number,Retirement Date,Annotations (Links),TRP Document,Finalization Date,Legacy Task Number,Legal Details,OCR Requested,Global Version ID,Publicly Published,Document Date,MRID Number,Legacy Reg Topic,Requires Review and Approval?,Annotations (Auto Links),Subareas,Product Safety Reporting Date,Media Title,Minor Version Number,Legacy Primary PMRA Data Code,Requestor,Owning Organisation,Zone,File Last Modified Date,Contact Person,File Created Date,Amendment Justification,CDPR Number,Legacy EPA Submission Date,Supports Agency Dossier,Field Trial Year,Reason for Un-retiring,TRP Topic,Bound Source Major Version,Keywords,Legacy Author Name(s),Export File Name,Size,Source Link,This content is a translation?,Quality Assurance Standard,Legacy Fed Reg Number,Checksum,Created Date,Annotations (Resolved),User Task,Review Start Date,test list,Incoming from (legacy to remove),Rendition Profile,Path,URL
|
||||||
|
,,Country Specific,,,,,No,,,Yes,,,,,Syngenta,,,Lambda-cyhalothrin - EU AIR5 - Document A - Statement of the context in which the dossier is submit.doc,,,,,,,,,,,,,,,English,,,PP321,,Authority Form / Document,,TRUE,"All Internal Users, Syngenta Read Only Restricted, Syngenta Read Only Non Restricted",,,,16/07/2019,,,Lambda-cyhalothrin - EU AIR5 - Document A - Statement of the context in which the dossier is submitted,PI0015818,,,,0,,,,0,,,,,,26/03/2020 14:06 CET,,Syngenta Crop Protection AG (Switzerland),,No,Final,,,,,SmartDoc - EAME,,,,0,IT-624180,VV-731623,,N/A,PP321_12074,,,,,,,,,,,,,B Sochard,No,0,0,,,,,,,,,,,,,,,,0,,,1.0|CURRENT,090100b881afdd91,,Syngenta Crop Protection AG (Switzerland),,Syngenta Crop Protection AG,,Original,,,,Internal Use Only,,,ETL_blank,,090100b881afdd91,,,0,,,,,Berangere Sochard,,,Syngenta Crop Protection AG (Switzerland),,,European Union,General Lifecycle,,,40037_850361,,ETL_blank,,,,,,,,,Lambda-cyhalothrin - EU AIR5 - Document A - Statement of the context in which the dossier is submit,,2,Registration Supporting Documentation,,,,,,No,,090100b881afdd91 (cv),,CP Vault Support,0,,No,,24/02/2021 15:20 CET,,,Vault Migration,"Global Reg Ops, NA Reg Ops, PS Ops, EAME Reg Ops, Syngenta Business Administrator, Berangere Sochard, Simon Baker, Lily Williams, Clive Boxwell, Paul Parsons, Richard MacKenzie, Dan Pickford, Laurence Hand",Vault Migration,,application/msword,Regulatory,2,,PP321,,,,0,No,,PI0015818,,No,40037_850361_1958105,No,01/09/2020,,Regulatory,No,0,,,,0,,,Syngenta Crop Protection AG,,,,,,,,,,,,,,Sochard Berangere,,49152,,No,,,86d75e5da397fdadcd332f92234a4f62,29/11/2019 08:51 CET,0,,,,,,Lambda-cyhalothrin - EU AIR5 - Document A - Statement of the.pdf,
|
||||||
|
,,Country Specific,,,,,No,,,Yes,,,,,Syngenta,,,Lambda-cyhalothrin - EU AIR5 - MCA Section 5 - Toxicology (1).docx,,,,,,,,,,,,,,,English,,,PP321,,Summary / Overview,,TRUE,"All Internal Users, Syngenta Read Only Non Restricted, Syngenta Read Only Restricted",,,,16/07/2019,,,Lambda-cyhalothrin - EU AIR5 - MCA Section 5 - Toxicology,PI0015818,,,,0,,,OECD Tier 2 Summary,0,,,,,,17/02/2021 22:02 CET,,Syngenta Crop Protection AG (Switzerland),,Yes,Draft,,,,,SmartDoc - EAME,,,,0,IT-615930,VV-729845,,N/A,PP321_12086,,,Syngenta,,,,,,,,,,S Lloyd,No,0,0,,,,,,,,,,,,,,,,0,,,1.0|CURRENT,090100b881afde22,,Syngenta Crop Protection AG (Switzerland),,Syngenta Crop Protection AG,,Original,,,,Internal Use Only,,,,,090100b881afde22,,,0,,,,,Sonia Ellis,,,Syngenta Crop Protection AG (Switzerland),,,European Union,General Lifecycle,,,40037_848583,,,,,,Ellis Sonia (ext) GBGU,,,,,Lambda-cyhalothrin - EU AIR5 - MCA Section 5 - Toxicology,,893,Summary / Assessment,,,,,,No,,090100b881afde22 (cv),,CP Vault Support,0,,No,Syngenta,24/02/2021 15:20 CET,,,Vault Migration,"EAME Reg Ops, Syngenta Business Administrator, PS Ops, NA Reg Ops, Global Reg Ops, Berangere Sochard, Simon Baker, Lily Williams, Clive Boxwell, Paul Parsons, Richard MacKenzie, Dan Pickford, Laurence Hand, Claire McCombie, Elaine Buss",Vault Migration,,application/vnd.openxmlformats-officedocument.wordprocessingml.document,Toxicology,3,,PP321,,,,0,No,,PI0015818,,No,40037_848583_2033983,No,01/09/2020,,Tox,No,0,,,,1,,,Syngenta Crop Protection AG,,17/02/2021 21:35 CET,,04/09/2020 14:34 CEST,,,,,,,,,VV-729845,Lloyd Sara,,5089990,,No,,,3b13f57d1244e77844eba73ce0ae9813,29/11/2019 08:47 CET,0,,,,,,Lambda-cyhalothrin - EU AIR5 - MCA Section 5 - Toxicology.pdf,
|
||||||
|
,,Country Specific,,,,,No,,,Yes,,,,,Syngenta,,,Lambda-cyhalothrin - EU AIR5 - LCA Section 5 Reference List (1).doc,,,,,,,,,,,,,,,English,,,PP321,,Summary / Overview,,TRUE,"All Internal Users, Syngenta Read Only Restricted, Syngenta Read Only Non Restricted",,,,16/07/2019,,,Lambda-cyhalothrin - EU AIR5 - LCA Section 5 Reference List,PI0015818,,,,0,,,OECD Tier 1 Summary,0,,,,,,12/09/2020 15:48 CEST,,Syngenta Crop Protection AG (Switzerland),,Yes,Final,,,,,SmartDoc - EAME,,,,0,IT-615436,VV-729844,,N/A,PP321_12084,,,,,,,,,,,,,S Ellis,No,0,0,,,,,,,,,,,,,,,,0,,,1.0|CURRENT,090100b881afde20,,Syngenta Crop Protection AG (Switzerland),,Syngenta Crop Protection AG,,Original,,,,Internal Use Only,,,,,090100b881afde20,,,0,,,,,Sonia Ellis,,,Syngenta Crop Protection AG (Switzerland),,,European Union,General Lifecycle,,,40037_848582,,,,,,,,,,,Lambda-cyhalothrin - EU AIR5 - LCA Section 5 Reference List,,147,Summary / Assessment,,,,,,No,,090100b881afde20 (cv),,CP Vault Support,0,,No,,24/02/2021 15:20 CET,,,Vault Migration,"NA Reg Ops, Global Reg Ops, EAME Reg Ops, PS Ops, Syngenta Business Administrator, Berangere Sochard, Paul Parsons",Vault Migration,,application/msword,Toxicology,2,,PP321,,,,0,No,,PI0015818,,No,40037_848582_2001744,No,01/09/2020,,Tox,No,0,,,,0,,,Syngenta Crop Protection AG,,,,,,,,,,,,,,Ellis Sonia,,651264,,No,,,eb0fdf698b528b67f54635395ec7fb52,29/11/2019 08:47 CET,0,,,,,,Lambda-cyhalothrin - EU AIR5 - LCA Section 5 Reference List.pdf,
|
||||||
|
,,Country Specific,,,,,No,,,Yes,,,,,Syngenta,,,Lambda-cyhalothrin - EU AIR5 - LCA Section 4 Reference List (5).doc,,,,,,,,,,,,,,,English,,,PP321,,Summary / Overview,,TRUE,"All Internal Users, Syngenta Read Only Restricted, Syngenta Read Only Non Restricted",,,,16/07/2019,,,Lambda-cyhalothrin - EU AIR5 - LCA Section 4 Reference List,PI0015818,,,,0,,,OECD Tier 1 Summary,0,,,,,,21/09/2020 15:21 CEST,,Syngenta Crop Protection AG (Switzerland),,Yes,Final,,,,,SmartDoc - EAME,,,,0,IT-618210,VV-729843,,N/A,PP321_12083,,,,,,,,,,,,,S Ellis,No,0,0,,,,,,,,,,,,,,,,0,,,1.0|CURRENT,090100b881afde1f,,Syngenta Crop Protection AG (Switzerland),,Syngenta Crop Protection AG,,Original,,,,Internal Use Only,,,,,090100b881afde1f,,,0,,,,,Sonia Ellis,,,Syngenta Crop Protection AG (Switzerland),,,European Union,General Lifecycle,,,40037_848581,,,,,,,,,,,Lambda-cyhalothrin - EU AIR5 - LCA Section 4 Reference List,,151,Summary / Assessment,,,,,,No,,090100b881afde1f (cv),,CP Vault Support,0,,No,,24/02/2021 15:20 CET,,,Vault Migration,"EAME Reg Ops, Global Reg Ops, PS Ops, Syngenta Business Administrator, NA Reg Ops, Berangere Sochard, Melanie Bottoms, Dan Pickford, Lynda Farrelly",Vault Migration,,application/msword,Chemistry - Regulatory,2,,PP321,,,,0,No,,PI0015818,,No,40037_848581_2003177,No,01/09/2020,,Chem - Analytical Methods,No,0,,,,0,,,Syngenta Crop Protection AG,,,,,,,,,,,,,,Ellis Sonia,,699392,,No,,,2c476f477f6e2b55ecb537683f5d1cee,29/11/2019 08:47 CET,0,,,,,,Lambda-cyhalothrin - EU AIR5 - LCA Section 4 Reference List.pdf,
|
||||||
|
@ -1,14 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
.idea/
|
.idea/
|
||||||
dist/
|
dist/
|
||||||
.angular
|
|
||||||
.husky
|
|
||||||
.editorconfig
|
|
||||||
.dockerignore
|
|
||||||
.eslintignore
|
|
||||||
.eslintrc.json
|
|
||||||
.gitignore
|
|
||||||
.prettierignore
|
|
||||||
.prettierrc
|
|
||||||
renovate.json
|
|
||||||
Running
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ ij_typescript_use_double_quotes = false
|
|||||||
ij_typescript_enforce_trailing_comma = keep
|
ij_typescript_enforce_trailing_comma = keep
|
||||||
ij_typescript_spaces_within_imports = true
|
ij_typescript_spaces_within_imports = true
|
||||||
|
|
||||||
[{*.json,.prettierrc,.eslintrc}]
|
[{*.json, .prettierrc, .eslintrc}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
tab_width = 2
|
tab_width = 2
|
||||||
ij_json_array_wrapping = off
|
ij_json_array_wrapping = off
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
.angular
|
|
||||||
.dev
|
|
||||||
.husky
|
|
||||||
dist
|
|
||||||
coverage
|
|
||||||
node_modules
|
|
||||||
bamboo-specs
|
|
||||||
docker
|
|
||||||
paligo-styles
|
|
||||||
paligo-theme
|
|
||||||
151
.eslintrc.json
151
.eslintrc.json
@ -1,22 +1,56 @@
|
|||||||
{
|
{
|
||||||
"root": true,
|
"root": true,
|
||||||
"ignorePatterns": ["**/*"],
|
"ignorePatterns": ["**/*"],
|
||||||
|
"plugins": ["@nrwl/nx"],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"@nrwl/nx/enforce-module-boundaries": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
"allow": [
|
||||||
|
"@services/**",
|
||||||
|
"@components/**",
|
||||||
|
"@guards/**",
|
||||||
|
"@users/**",
|
||||||
|
"@i18n/**",
|
||||||
|
"@utils/**",
|
||||||
|
"@models/**",
|
||||||
|
"@environments/**",
|
||||||
|
"@shared/**",
|
||||||
|
"@upload-download/**",
|
||||||
|
"@translations/**"
|
||||||
|
],
|
||||||
|
"depConstraints": [
|
||||||
|
{
|
||||||
|
"sourceTag": "*",
|
||||||
|
"onlyDependOnLibsWithTags": ["*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts"],
|
||||||
|
"extends": ["plugin:@nrwl/nx/typescript"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"files": ["*.ts"],
|
"files": ["*.ts"],
|
||||||
"extends": [
|
"extends": [
|
||||||
|
"plugin:@nrwl/nx/angular",
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@angular-eslint/recommended",
|
"plugin:@angular-eslint/recommended",
|
||||||
|
"plugin:@angular-eslint/recommended--extra",
|
||||||
"plugin:@angular-eslint/template/process-inline-templates",
|
"plugin:@angular-eslint/template/process-inline-templates",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended"
|
||||||
"plugin:rxjs/recommended"
|
|
||||||
],
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"rxjs/no-ignored-subscription": "warn",
|
|
||||||
"@angular-eslint/prefer-standalone": "off",
|
|
||||||
"@angular-eslint/no-conflicting-lifecycle": "error",
|
"@angular-eslint/no-conflicting-lifecycle": "error",
|
||||||
"@angular-eslint/no-host-metadata-property": "error",
|
"@angular-eslint/no-host-metadata-property": "error",
|
||||||
"@angular-eslint/no-input-rename": "error",
|
"@angular-eslint/no-input-rename": "error",
|
||||||
@ -38,13 +72,13 @@
|
|||||||
"@typescript-eslint/indent": "off",
|
"@typescript-eslint/indent": "off",
|
||||||
"@typescript-eslint/lines-between-class-members": "off",
|
"@typescript-eslint/lines-between-class-members": "off",
|
||||||
"@typescript-eslint/ban-types": "off",
|
"@typescript-eslint/ban-types": "off",
|
||||||
"@typescript-eslint/no-unsafe-argument": "off",
|
|
||||||
"@typescript-eslint/explicit-member-accessibility": [
|
"@typescript-eslint/explicit-member-accessibility": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"accessibility": "no-public"
|
"accessibility": "no-public"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"@typescript-eslint/member-ordering": "error",
|
||||||
"@typescript-eslint/naming-convention": [
|
"@typescript-eslint/naming-convention": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
@ -66,7 +100,7 @@
|
|||||||
"selector": "memberLike",
|
"selector": "memberLike",
|
||||||
"modifiers": ["protected"],
|
"modifiers": ["protected"],
|
||||||
"format": ["camelCase"],
|
"format": ["camelCase"],
|
||||||
"leadingUnderscore": "allow"
|
"leadingUnderscore": "require"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"selector": "memberLike",
|
"selector": "memberLike",
|
||||||
@ -98,108 +132,6 @@
|
|||||||
"@typescript-eslint/no-floating-promises": "off",
|
"@typescript-eslint/no-floating-promises": "off",
|
||||||
"@typescript-eslint/unbound-method": "off",
|
"@typescript-eslint/unbound-method": "off",
|
||||||
"@typescript-eslint/restrict-template-expressions": "off",
|
"@typescript-eslint/restrict-template-expressions": "off",
|
||||||
"@typescript-eslint/member-ordering": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"default": [
|
|
||||||
"signature",
|
|
||||||
"call-signature",
|
|
||||||
"#private-static-field",
|
|
||||||
"private-static-field",
|
|
||||||
"protected-static-field",
|
|
||||||
"public-static-field",
|
|
||||||
"#private-instance-field",
|
|
||||||
"private-instance-field",
|
|
||||||
"protected-instance-field",
|
|
||||||
"public-instance-field",
|
|
||||||
"private-decorated-field",
|
|
||||||
"protected-decorated-field",
|
|
||||||
"public-decorated-field",
|
|
||||||
"protected-abstract-field",
|
|
||||||
"public-abstract-field",
|
|
||||||
"#private-field",
|
|
||||||
"private-field",
|
|
||||||
"protected-field",
|
|
||||||
"public-field",
|
|
||||||
"static-field",
|
|
||||||
"instance-field",
|
|
||||||
"abstract-field",
|
|
||||||
"decorated-field",
|
|
||||||
"field",
|
|
||||||
"static-initialization",
|
|
||||||
"public-constructor",
|
|
||||||
"protected-constructor",
|
|
||||||
"private-constructor",
|
|
||||||
"constructor",
|
|
||||||
"public-static-get",
|
|
||||||
"protected-static-get",
|
|
||||||
"private-static-get",
|
|
||||||
"#private-static-get",
|
|
||||||
"public-decorated-get",
|
|
||||||
"protected-decorated-get",
|
|
||||||
"private-decorated-get",
|
|
||||||
"public-instance-get",
|
|
||||||
"protected-instance-get",
|
|
||||||
"private-instance-get",
|
|
||||||
"#private-instance-get",
|
|
||||||
"public-abstract-get",
|
|
||||||
"protected-abstract-get",
|
|
||||||
"public-get",
|
|
||||||
"protected-get",
|
|
||||||
"private-get",
|
|
||||||
"#private-get",
|
|
||||||
"static-get",
|
|
||||||
"instance-get",
|
|
||||||
"abstract-get",
|
|
||||||
"decorated-get",
|
|
||||||
"get",
|
|
||||||
"public-static-set",
|
|
||||||
"protected-static-set",
|
|
||||||
"private-static-set",
|
|
||||||
"#private-static-set",
|
|
||||||
"public-decorated-set",
|
|
||||||
"protected-decorated-set",
|
|
||||||
"private-decorated-set",
|
|
||||||
"public-instance-set",
|
|
||||||
"protected-instance-set",
|
|
||||||
"private-instance-set",
|
|
||||||
"#private-instance-set",
|
|
||||||
"public-abstract-set",
|
|
||||||
"protected-abstract-set",
|
|
||||||
"public-set",
|
|
||||||
"protected-set",
|
|
||||||
"private-set",
|
|
||||||
"#private-set",
|
|
||||||
"static-set",
|
|
||||||
"instance-set",
|
|
||||||
"abstract-set",
|
|
||||||
"decorated-set",
|
|
||||||
"set",
|
|
||||||
"public-static-method",
|
|
||||||
"protected-static-method",
|
|
||||||
"private-static-method",
|
|
||||||
"#private-static-method",
|
|
||||||
"public-decorated-method",
|
|
||||||
"protected-decorated-method",
|
|
||||||
"private-decorated-method",
|
|
||||||
"public-instance-method",
|
|
||||||
"protected-instance-method",
|
|
||||||
"private-instance-method",
|
|
||||||
"#private-instance-method",
|
|
||||||
"public-abstract-method",
|
|
||||||
"protected-abstract-method",
|
|
||||||
"public-method",
|
|
||||||
"protected-method",
|
|
||||||
"private-method",
|
|
||||||
"#private-method",
|
|
||||||
"static-method",
|
|
||||||
"instance-method",
|
|
||||||
"abstract-method",
|
|
||||||
"decorated-method",
|
|
||||||
"method"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"arrow-body-style": "error",
|
"arrow-body-style": "error",
|
||||||
"arrow-parens": ["error", "as-needed"],
|
"arrow-parens": ["error", "as-needed"],
|
||||||
"constructor-super": "error",
|
"constructor-super": "error",
|
||||||
@ -249,9 +181,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": ["*.html"],
|
"files": ["*.html"],
|
||||||
"extends": ["plugin:@angular-eslint/template/recommended"]
|
"extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/recommended"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// https://github.com/angular-eslint/angular-eslint#notes-for-eslint-plugin-prettier-users
|
||||||
"files": ["*.html"],
|
"files": ["*.html"],
|
||||||
"excludedFiles": ["*inline-template-*.component.html"],
|
"excludedFiles": ["*inline-template-*.component.html"],
|
||||||
"extends": ["plugin:prettier/recommended"],
|
"extends": ["plugin:prettier/recommended"],
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -18,7 +18,6 @@
|
|||||||
*.launch
|
*.launch
|
||||||
.settings/
|
.settings/
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
redaction.iml
|
|
||||||
|
|
||||||
# IDE - VSCode
|
# IDE - VSCode
|
||||||
.vscode/*
|
.vscode/*
|
||||||
@ -42,9 +41,9 @@ testem.log
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
version.properties
|
||||||
|
|
||||||
paligo-styles/style.css*
|
paligo-styles/style.css*
|
||||||
|
|
||||||
migrations.json
|
migrations.json
|
||||||
*.iml
|
*.iml
|
||||||
/.nx/
|
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
variables:
|
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
|
||||||
GIT_SUBMODULE_FORCE_HTTPS: 'true'
|
|
||||||
PROJECT: red-ui
|
|
||||||
DOCKERFILELOCATION: 'docker/$PROJECT/Dockerfile'
|
|
||||||
|
|
||||||
include:
|
|
||||||
- project: 'gitlab/gitlab'
|
|
||||||
ref: 'main'
|
|
||||||
file: 'ci-templates/docker_build_nexus_v2.yml'
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE != "schedule"
|
|
||||||
|
|
||||||
sonarqube:
|
|
||||||
stage: test
|
|
||||||
image:
|
|
||||||
name: sonarsource/sonar-scanner-cli:11.1
|
|
||||||
entrypoint:
|
|
||||||
- ''
|
|
||||||
variables:
|
|
||||||
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
|
|
||||||
GIT_DEPTH: '0'
|
|
||||||
cache:
|
|
||||||
key: "${CI_JOB_NAME}"
|
|
||||||
paths:
|
|
||||||
- ".sonar/cache"
|
|
||||||
script:
|
|
||||||
- sonar-scanner
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
|
|
||||||
- if: "$CI_COMMIT_BRANCH =~ /^release/"
|
|
||||||
|
|
||||||
localazy update:
|
|
||||||
image: node:20.5
|
|
||||||
cache:
|
|
||||||
- key:
|
|
||||||
files:
|
|
||||||
- yarn.lock
|
|
||||||
paths:
|
|
||||||
- .yarn-cache/
|
|
||||||
script:
|
|
||||||
# - git config user.email "${CI_EMAIL}"
|
|
||||||
# - git config user.name "${CI_USERNAME}"
|
|
||||||
# - git remote add gitlab_origin https://${CI_USERNAME}:${CI_ACCESS_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
|
||||||
- git push https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
|
||||||
- cd tools/localazy
|
|
||||||
- yarn install --cache-folder .yarn-cache
|
|
||||||
- yarn start
|
|
||||||
- cd ../..
|
|
||||||
- git add .
|
|
||||||
- |-
|
|
||||||
CHANGES=$(git status --porcelain | wc -l)
|
|
||||||
if [ "$CHANGES" -gt "0" ]
|
|
||||||
then
|
|
||||||
git status
|
|
||||||
git commit -m "push back localazy update"
|
|
||||||
git push gitlab_origin HEAD:${CI_COMMIT_REF_NAME}
|
|
||||||
# git push https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
|
|
||||||
# git push
|
|
||||||
fi
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
|
||||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,3 +1,3 @@
|
|||||||
[submodule "libs/common-ui"]
|
[submodule "libs/common-ui"]
|
||||||
path = libs/common-ui
|
path = libs/common-ui
|
||||||
url = ../../fforesight/shared-ui-libraries/common-ui.git
|
url = ssh://git@git.iqser.com:2222/sl/common-ui.git
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
# Add files here to ignore them from prettier formatting
|
# Add files here to ignore them from prettier formatting
|
||||||
/.angular
|
|
||||||
/.dev
|
|
||||||
/.husky
|
|
||||||
/dist
|
/dist
|
||||||
/coverage
|
/coverage
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|||||||
47
README.md
47
README.md
@ -1,31 +1,39 @@
|
|||||||
# Redaction
|
# Redaction
|
||||||
|
|
||||||
### To Create a new Stack in rancher check [this Wiki page](https://wiki.iqser.com/pages/viewpage.action?spaceKey=RED&title=Work+with+kubectl)
|
## To Create a new Stack in rancher
|
||||||
|
|
||||||
# Dependencies update guide
|
Goto rancher.iqser.com: Select Cluster `Development`, go to apps, click launch and select `Redaction` from the `dev`
|
||||||
* When updating @pdftron/webviewer, make sure to change the version also in the angular.json and everywhere where the path to /assets/wv-recources is used
|
section. Add a new name and a new namespace. Select `answers-development.yaml` and add it to answers `Edit as yaml`.
|
||||||
* Make sure the keycloak.js version is the same with the keycloak version from helm chart
|
|
||||||
|
|
||||||
## Code style
|
For HTTPS / Cloudflare domain go to `workloads` -> `Loadbalancing` -> `select your stack`
|
||||||
* Don't use `setInterval` without calling `clearInterval` in `ngOnDestroy` or in `destroyRef.onDestroy(() => clearInterval(intervalId))`
|
Add cloudflare certificate and specify a hostname to use `timo-redaction-dev.iqser.cloud`
|
||||||
* Never call getters in HTML templates
|
|
||||||
|
|
||||||
## Keycloak Staging Config
|
## Keycloak Staging Config
|
||||||
|
|
||||||
- keycloak:
|
- keycloak:
|
||||||
- authServerUrl: 'https://redkc-staging.iqser.cloud/auth'
|
- authServerUrl: 'https://redkc-staging.iqser.cloud/auth'
|
||||||
- client:
|
- client:
|
||||||
- secret: 'a4e8aa56-03b0-4e6b-b822-8ac1f41280c4'
|
- secret: 'a4e8aa56-03b0-4e6b-b822-8ac1f41280c4'
|
||||||
|
|
||||||
## Default Testing URLs
|
## Default Testing URL
|
||||||
|
|
||||||
* `https://dev-04.iqser.cloud/`
|
`https://dev-04.iqser.cloud/`
|
||||||
* `https://dev-08.iqser.cloud/`
|
|
||||||
|
## Known errors
|
||||||
|
|
||||||
|
- In case of CORS or redirect_uri errors follow these steps:
|
||||||
|
- Go to `<HOST>.iqser.cloud/auth/admin/master/console`
|
||||||
|
- Login with `admin` and `admin1234`
|
||||||
|
- In the left menu go to `Clients`
|
||||||
|
- In the table click `redaction`
|
||||||
|
- Find `Valid Redirect URIs` input
|
||||||
|
- Under `/ui/*` add new value `http://localhost:4200/*`
|
||||||
|
- **Save**
|
||||||
|
|
||||||
## Test Users
|
## Test Users
|
||||||
|
|
||||||
| username | role | comment |
|
| username | role | comment |
|
||||||
|--------------|--------------------------------|----------------------------|
|
| ------------ | ------------------------------ | -------------------------- |
|
||||||
| guest | | cannot use the application |
|
| guest | | cannot use the application |
|
||||||
| user | RED_USER | |
|
| user | RED_USER | |
|
||||||
| red_manager | RED_MANAGER | |
|
| red_manager | RED_MANAGER | |
|
||||||
@ -36,11 +44,12 @@ Password for all users is `OsloImWinter!23`
|
|||||||
|
|
||||||
### Running the app locally
|
### Running the app locally
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
* node 16 or newer installed ( https://nodejs.org/en/download/ )
|
* node 16 or newer installed ( https://nodejs.org/en/download/ )
|
||||||
* yarn ( execute `npm install -g yarn` in any terminal after installing node )
|
* yarn ( execute `npm install -g yarn` in any terminal after installing node )
|
||||||
|
|
||||||
In the root folder simply execute `yarn install` followed by `yarn start`.
|
In the root folder simply execute `yarn install` followed by `yarn start`.
|
||||||
The file `apps/red-ui/src/assets/config.config.json` contains the configuration for local development. All properties
|
The file `apps/red-ui/src/assets/config.config.json` contains the configuration for local development. All properties defined here are later available via env variables.
|
||||||
defined here are later available via env variables.
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
59
angular.json
59
angular.json
@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"cli": {
|
|
||||||
"packageManager": "yarn",
|
|
||||||
"schematicCollections": ["@angular-eslint/schematics"],
|
|
||||||
"analytics": "2bccdff1-3aff-4f10-b233-211065aa25d9"
|
|
||||||
},
|
|
||||||
"newProjectRoot": "projects",
|
|
||||||
"projects": {
|
"projects": {
|
||||||
|
"red-domain": {
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"projectType": "library",
|
||||||
|
"root": "libs/red-domain",
|
||||||
|
"sourceRoot": "libs/red-domain/src",
|
||||||
|
"prefix": "red"
|
||||||
|
},
|
||||||
"red-ui": {
|
"red-ui": {
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
"schematics": {
|
"schematics": {
|
||||||
"@schematics/angular:component": {
|
"@schematics/angular:component": {
|
||||||
@ -31,26 +32,25 @@
|
|||||||
"skipTests": true
|
"skipTests": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "",
|
"root": "apps/red-ui",
|
||||||
"sourceRoot": "apps/red-ui/src",
|
"sourceRoot": "apps/red-ui/src",
|
||||||
"prefix": "redaction",
|
"prefix": "redaction",
|
||||||
"targets": {
|
"architect": {
|
||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular/build:application",
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": {
|
"outputPath": "dist/apps/red-ui",
|
||||||
"base": "dist/apps/red-ui"
|
|
||||||
},
|
|
||||||
"index": "apps/red-ui/src/index.html",
|
"index": "apps/red-ui/src/index.html",
|
||||||
"polyfills": ["apps/red-ui/src/polyfills.ts"],
|
"main": "apps/red-ui/src/main.ts",
|
||||||
"tsConfig": "tsconfig.json",
|
"polyfills": "apps/red-ui/src/polyfills.ts",
|
||||||
|
"tsConfig": "apps/red-ui/tsconfig.json",
|
||||||
"baseHref": "/ui/",
|
"baseHref": "/ui/",
|
||||||
"assets": [
|
"assets": [
|
||||||
"apps/red-ui/src/favicon.ico",
|
"apps/red-ui/src/favicon.ico",
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "node_modules/@pdftron/webviewer/public/",
|
"input": "node_modules/@pdftron/webviewer/public/",
|
||||||
"output": "/assets/wv-resources/11.1.0/"
|
"output": "/assets/wv-resources/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
@ -73,12 +73,13 @@
|
|||||||
"stylePreprocessorOptions": {
|
"stylePreprocessorOptions": {
|
||||||
"includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
|
"includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
|
||||||
},
|
},
|
||||||
"scripts": ["node_modules/chart.js/auto/auto.cjs"],
|
"scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"],
|
||||||
|
"vendorChunk": true,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
|
"buildOptimizer": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
"namedChunks": true,
|
"namedChunks": true
|
||||||
"browser": "apps/red-ui/src/main.ts"
|
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
@ -100,6 +101,8 @@
|
|||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"namedChunks": false,
|
"namedChunks": false,
|
||||||
"extractLicenses": true,
|
"extractLicenses": true,
|
||||||
|
"vendorChunk": false,
|
||||||
|
"buildOptimizer": true,
|
||||||
"budgets": [
|
"budgets": [
|
||||||
{
|
{
|
||||||
"type": "initial",
|
"type": "initial",
|
||||||
@ -112,20 +115,28 @@
|
|||||||
"maximumError": "20kb"
|
"maximumError": "20kb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"serviceWorker": "ngsw-config.json"
|
"serviceWorker": true,
|
||||||
|
"ngswConfigPath": "apps/red-ui/ngsw-config.json"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"outputs": ["{options.outputPath}"]
|
||||||
},
|
},
|
||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular/build:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "red-ui:build"
|
"browserTarget": "red-ui:build"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"buildTarget": "red-ui:build:production"
|
"browserTarget": "red-ui:build:production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "red-ui:build"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
apps/red-ui/.eslintrc.json
Normal file
22
apps/red-ui/.eslintrc.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"extends": ["../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.ts"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-argument": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/unbound-method": "off",
|
||||||
|
"@typescript-eslint/no-misused-promises": "off",
|
||||||
|
"@typescript-eslint/no-floating-promises": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
"$schema": "../../node_modules/@angular/service-worker/config/schema.json",
|
||||||
"index": "/index.html",
|
"index": "/index.html",
|
||||||
"assetGroups": [
|
"assetGroups": [
|
||||||
{
|
{
|
||||||
@ -17,15 +17,5 @@
|
|||||||
"files": ["/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"]
|
"files": ["/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"navigationUrls": [
|
|
||||||
"/**",
|
|
||||||
"!/**/*.*",
|
|
||||||
"!/**/*__*",
|
|
||||||
"!/**/*__*/**",
|
|
||||||
"!/ui/assets/wv-resources/**/webviewer-core.min.js",
|
|
||||||
"!/ui/assets/wv-resources/**/webviewer-ui.min.js",
|
|
||||||
"!/assets/wv-resources/**/webviewer-core.min.js",
|
|
||||||
"!/assets/wv-resources/**/webviewer-ui.min.js"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,36 +1,38 @@
|
|||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { RouteReuseStrategy, RouterModule } from '@angular/router';
|
|
||||||
import { ifNotLoggedIn } from '@common-ui/tenants/guards/if-not-logged-in.guard';
|
|
||||||
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
|
||||||
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
|
||||||
import { DashboardGuard } from '@guards/dashboard-guard.service';
|
|
||||||
import { DossierFilesGuard } from '@guards/dossier-files-guard';
|
|
||||||
import { templateExistsWhenEnteringDossierList } from '@guards/dossier-template-exists.guard';
|
|
||||||
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
|
||||||
import { loadActiveDossiersGuard, loadAllDossiersGuard, loadArchivedDossiersGuard } from '@guards/dossiers.guard';
|
|
||||||
import { isNotEditingFileAttributeGuard } from '@guards/file-attribute.guard';
|
|
||||||
import { FeaturesGuard } from '@guards/features-guard.service';
|
|
||||||
import { ifLoggedIn } from '@guards/if-logged-in.guard';
|
|
||||||
import { TrashGuard } from '@guards/trash.guard';
|
|
||||||
import { CompositeRouteGuard, DEFAULT_REDIRECT_KEY, IqserPermissionsGuard, IqserRoutes, orderedAsyncGuards } from '@iqser/common-ui';
|
|
||||||
import { TenantSelectComponent } from '@iqser/common-ui/lib/tenants';
|
|
||||||
import { doesNotHaveAnyRole, hasAnyRole, IqserAuthGuard } from '@iqser/common-ui/lib/users';
|
|
||||||
import { CustomRouteReuseStrategy } from '@iqser/common-ui/lib/utils';
|
|
||||||
import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ROUTE, FILE_ID } from '@red/domain';
|
|
||||||
import { RedRoleGuard } from '@users/red-role.guard';
|
|
||||||
import { Roles } from '@users/roles';
|
|
||||||
import { mainGuard } from '@utils/main.guard';
|
|
||||||
import { webViewerLoadedGuard } from './modules/pdf-viewer/services/webviewer-loaded.guard';
|
|
||||||
import { ACTIVE_DOSSIERS_SERVICE } from './tokens';
|
|
||||||
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
||||||
|
import {
|
||||||
|
CompositeRouteGuard,
|
||||||
|
CustomRouteReuseStrategy,
|
||||||
|
DEFAULT_REDIRECT_KEY,
|
||||||
|
IqserAuthGuard,
|
||||||
|
IqserPermissionsGuard,
|
||||||
|
IqserRoutes,
|
||||||
|
} from '@iqser/common-ui';
|
||||||
|
import { RedRoleGuard } from '@users/red-role.guard';
|
||||||
|
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
||||||
|
import { RouteReuseStrategy, RouterModule } from '@angular/router';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
||||||
|
import { DossiersGuard } from '@guards/dossiers.guard';
|
||||||
|
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
|
||||||
|
import { FeaturesGuard } from '@guards/features-guard.service';
|
||||||
|
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||||
|
import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard';
|
||||||
|
import { DashboardGuard } from '@guards/dashboard-guard.service';
|
||||||
|
import { TrashGuard } from '@guards/trash.guard';
|
||||||
|
import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE, FILE_ID } from '@red/domain';
|
||||||
|
import { DossierFilesGuard } from '@guards/dossier-files-guard';
|
||||||
|
import { WebViewerLoadedGuard } from './modules/pdf-viewer/services/webviewer-loaded.guard';
|
||||||
|
import { ROLES } from '@users/roles';
|
||||||
|
|
||||||
const dossierTemplateIdRoutes: IqserRoutes = [
|
const dossierTemplateIdRoutes: IqserRoutes = [
|
||||||
{
|
{
|
||||||
path: `${DOSSIERS_ROUTE}`,
|
path: `${DOSSIERS_ROUTE}`,
|
||||||
canActivate: [loadActiveDossiersGuard(), IqserPermissionsGuard],
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
|
routeGuards: [DossiersGuard],
|
||||||
|
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: [Roles.files.readStatus],
|
allow: [ROLES.files.readStatus],
|
||||||
redirectTo: '/auth-error',
|
redirectTo: '/auth-error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -38,37 +40,36 @@ const dossierTemplateIdRoutes: IqserRoutes = [
|
|||||||
{
|
{
|
||||||
path: `:${DOSSIER_ID}`,
|
path: `:${DOSSIER_ID}`,
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
canDeactivate: [isNotEditingFileAttributeGuard],
|
|
||||||
data: {
|
data: {
|
||||||
routeGuards: [DossierFilesGuard],
|
routeGuards: [DossierFilesGuard],
|
||||||
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier],
|
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier],
|
||||||
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: [Roles.dossierAttributes.read, Roles.dossierAttributes.readConfig],
|
allow: [ROLES.dossierAttributes.read, ROLES.dossierAttributes.readConfig],
|
||||||
redirectTo: '/auth-error',
|
redirectTo: '/auth-error',
|
||||||
},
|
},
|
||||||
skeleton: 'dossier',
|
skeleton: 'dossier',
|
||||||
},
|
},
|
||||||
loadChildren: () => import('./modules/dossier-overview/dossier-overview.routes'),
|
loadChildren: () => import('./modules/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `:${DOSSIER_ID}/file/:${FILE_ID}`,
|
path: `:${DOSSIER_ID}/file/:${FILE_ID}`,
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, webViewerLoadedGuard()],
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
routeGuards: [DossierFilesGuard],
|
routeGuards: [DossierFilesGuard, WebViewerLoadedGuard],
|
||||||
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
|
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
|
||||||
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: [Roles.readRedactionLog, Roles.files.downloadOriginal, Roles.highlights.read],
|
allow: [ROLES.readRedactionLog, ROLES.files.downloadOriginal, ROLES.highlights.read],
|
||||||
redirectTo: '/auth-error',
|
redirectTo: '/auth-error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
loadChildren: () => import('./modules/file-preview/file-preview.routes'),
|
loadChildren: () => import('./modules/file-preview/file-preview.module').then(m => m.FilePreviewModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
loadChildren: () => import('./modules/dossiers-listing/dossiers-listing.routes'),
|
loadChildren: () => import('./modules/dossiers-listing/dossiers-listing.module').then(m => m.DossiersListingModule),
|
||||||
data: {
|
data: {
|
||||||
breadcrumbs: [BreadcrumbTypes.dossierTemplate],
|
breadcrumbs: [BreadcrumbTypes.dossierTemplate],
|
||||||
},
|
},
|
||||||
@ -77,10 +78,12 @@ const dossierTemplateIdRoutes: IqserRoutes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${ARCHIVE_ROUTE}`,
|
path: `${ARCHIVE_ROUTE}`,
|
||||||
loadChildren: () => import('./modules/archive/archive.routes'),
|
loadChildren: () => import('./modules/archive/archive.module').then(m => m.ArchiveModule),
|
||||||
canActivate: [CompositeRouteGuard, loadArchivedDossiersGuard()],
|
canActivate: [CompositeRouteGuard, WebViewerLoadedGuard],
|
||||||
data: {
|
data: {
|
||||||
routeGuards: [FeaturesGuard],
|
routeGuards: [FeaturesGuard, DossiersGuard],
|
||||||
|
dossiersService: ARCHIVED_DOSSIERS_SERVICE,
|
||||||
|
features: [DOSSIERS_ARCHIVE],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -90,129 +93,127 @@ const dossierTemplateIdRoutes: IqserRoutes = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const mainRoutes: IqserRoutes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
redirectTo: 'dashboard',
|
|
||||||
pathMatch: 'full',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'account',
|
|
||||||
loadChildren: () => import('./modules/account/account.routes'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'admin',
|
|
||||||
loadChildren: () => import('./modules/admin/admin.routes'),
|
|
||||||
canActivate: [RedRoleGuard],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dashboard',
|
|
||||||
loadChildren: () => import('./modules/dashboard/dashboard.routes'),
|
|
||||||
canActivate: [CompositeRouteGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard, IqserPermissionsGuard, DossierTemplatesGuard, DashboardGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: [
|
|
||||||
Roles.any,
|
|
||||||
Roles.templates.read,
|
|
||||||
Roles.fileAttributes.readConfig,
|
|
||||||
Roles.dictionaryTypes.read,
|
|
||||||
Roles.colors.read,
|
|
||||||
Roles.states.read,
|
|
||||||
Roles.notifications.read,
|
|
||||||
'RED_USER',
|
|
||||||
],
|
|
||||||
redirectTo: {
|
|
||||||
RED_USER: '/main/admin',
|
|
||||||
[Roles.templates.read]: '/main/admin',
|
|
||||||
[DEFAULT_REDIRECT_KEY]: '/auth-error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
skeleton: 'dashboard',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'downloads',
|
|
||||||
// TODO: transform into a lazy loaded module
|
|
||||||
component: DownloadsListScreenComponent,
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: Roles.readDownloadStatus,
|
|
||||||
redirectTo: '/auth-error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'search',
|
|
||||||
loadComponent: () => import('./modules/search/search-screen/search-screen.component'),
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, loadAllDossiersGuard()],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.search],
|
|
||||||
redirectTo: '/auth-error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'trash',
|
|
||||||
loadChildren: () => import('./modules/trash/trash.routes'),
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard, TrashGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.dossiers.read, Roles.files.readStatus],
|
|
||||||
redirectTo: '/auth-error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `:${DOSSIER_TEMPLATE_ID}`,
|
|
||||||
children: dossierTemplateIdRoutes,
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, templateExistsWhenEnteringDossierList()],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: [
|
|
||||||
Roles.any,
|
|
||||||
Roles.templates.read,
|
|
||||||
Roles.fileAttributes.readConfig,
|
|
||||||
Roles.dictionaryTypes.read,
|
|
||||||
Roles.colors.read,
|
|
||||||
Roles.states.read,
|
|
||||||
Roles.notifications.read,
|
|
||||||
Roles.dossiers.read,
|
|
||||||
'RED_USER',
|
|
||||||
],
|
|
||||||
redirectTo: {
|
|
||||||
[Roles.any]: '/auth-error',
|
|
||||||
RED_USER: '/main/admin',
|
|
||||||
[DEFAULT_REDIRECT_KEY]: '/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const routes: IqserRoutes = [
|
const routes: IqserRoutes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
redirectTo: 'main',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
canActivate: [ifNotLoggedIn()],
|
|
||||||
component: TenantSelectComponent,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'main',
|
path: 'main',
|
||||||
canActivate: [orderedAsyncGuards([ifLoggedIn(), hasAnyRole(), mainGuard()])],
|
|
||||||
component: BaseScreenComponent,
|
component: BaseScreenComponent,
|
||||||
children: mainRoutes,
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
redirectTo: 'dashboard',
|
||||||
|
pathMatch: 'full',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'account',
|
||||||
|
loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'admin',
|
||||||
|
loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
|
||||||
|
canActivate: [RedRoleGuard],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dashboard',
|
||||||
|
loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule),
|
||||||
|
canActivate: [CompositeRouteGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard, IqserPermissionsGuard, DossierTemplatesGuard, DashboardGuard],
|
||||||
|
permissions: {
|
||||||
|
allow: [
|
||||||
|
ROLES.any,
|
||||||
|
ROLES.templates.read,
|
||||||
|
ROLES.fileAttributes.readConfig,
|
||||||
|
ROLES.watermarks.read,
|
||||||
|
ROLES.dictionaryTypes.read,
|
||||||
|
ROLES.colors.read,
|
||||||
|
ROLES.states.read,
|
||||||
|
ROLES.notifications.read,
|
||||||
|
'RED_USER',
|
||||||
|
],
|
||||||
|
redirectTo: {
|
||||||
|
RED_USER: '/main/admin',
|
||||||
|
[ROLES.templates.read]: '/main/admin',
|
||||||
|
[DEFAULT_REDIRECT_KEY]: '/auth-error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
skeleton: 'dashboard',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'downloads',
|
||||||
|
component: DownloadsListScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
permissions: {
|
||||||
|
allow: ROLES.readDownloadStatus,
|
||||||
|
redirectTo: '/auth-error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'search',
|
||||||
|
loadChildren: () => import('./modules/search/search.module').then(m => m.SearchModule),
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.search],
|
||||||
|
redirectTo: '/auth-error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'trash',
|
||||||
|
loadChildren: () => import('./modules/trash/trash.module').then(m => m.TrashModule),
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard, DossiersGuard, TrashGuard],
|
||||||
|
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.dossiers.read, ROLES.files.readStatus],
|
||||||
|
redirectTo: '/auth-error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `:${DOSSIER_TEMPLATE_ID}`,
|
||||||
|
children: dossierTemplateIdRoutes,
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard, DossierTemplateExistsGuard],
|
||||||
|
permissions: {
|
||||||
|
allow: [
|
||||||
|
ROLES.any,
|
||||||
|
ROLES.templates.read,
|
||||||
|
ROLES.fileAttributes.readConfig,
|
||||||
|
ROLES.watermarks.read,
|
||||||
|
ROLES.dictionaryTypes.read,
|
||||||
|
ROLES.colors.read,
|
||||||
|
ROLES.states.read,
|
||||||
|
ROLES.notifications.read,
|
||||||
|
ROLES.dossiers.read,
|
||||||
|
'RED_USER',
|
||||||
|
],
|
||||||
|
redirectTo: {
|
||||||
|
[ROLES.any]: '/auth-error',
|
||||||
|
RED_USER: '/main/admin',
|
||||||
|
[DEFAULT_REDIRECT_KEY]: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'auth-error',
|
path: 'auth-error',
|
||||||
component: AuthErrorComponent,
|
component: AuthErrorComponent,
|
||||||
canActivate: [doesNotHaveAnyRole()],
|
canActivate: [IqserAuthGuard],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
@ -222,14 +223,8 @@ const routes: IqserRoutes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' })],
|
||||||
RouterModule.forRoot(routes, {
|
providers: [{ provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy }],
|
||||||
scrollPositionRestoration: 'enabled',
|
|
||||||
bindToComponentInputs: true,
|
|
||||||
paramsInheritanceStrategy: 'always',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
providers: [{ provide: RouteReuseStrategy, useExisting: CustomRouteReuseStrategy }],
|
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
||||||
|
<redaction-pdf-viewer [style.visibility]="(documentViewer.loaded$ | async) ? 'visible' : 'hidden'"></redaction-pdf-viewer>
|
||||||
|
|
||||||
<iqser-full-page-loading-indicator></iqser-full-page-loading-indicator>
|
<iqser-full-page-loading-indicator></iqser-full-page-loading-indicator>
|
||||||
<iqser-connection-status></iqser-connection-status>
|
<iqser-connection-status></iqser-connection-status>
|
||||||
<iqser-full-page-error></iqser-full-page-error>
|
<iqser-full-page-error></iqser-full-page-error>
|
||||||
|
|||||||
@ -1,30 +1,31 @@
|
|||||||
import { Component, Renderer2, ViewContainerRef } from '@angular/core';
|
import { Component, Inject, Renderer2, ViewContainerRef } from '@angular/core';
|
||||||
import { RouterHistoryService } from '@services/router-history.service';
|
import { RouterHistoryService } from '@services/router-history.service';
|
||||||
|
import { REDDocumentViewer } from './modules/pdf-viewer/services/document-viewer.service';
|
||||||
|
import { DossiersChangesService } from '@services/dossiers/dossier-changes.service';
|
||||||
|
import { DOCUMENT } from '@angular/common';
|
||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
import { UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { getConfig } from '@iqser/common-ui';
|
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
|
||||||
|
import { ROLES } from '@users/roles';
|
||||||
import { AppConfig } from '@red/domain';
|
import { AppConfig } from '@red/domain';
|
||||||
import { NavigationEnd, Router } from '@angular/router';
|
|
||||||
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
|
|
||||||
import { APP_TYPE_PATHS } from '@common-ui/utils/constants';
|
|
||||||
import { MatIconRegistry } from '@angular/material/icon';
|
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
|
||||||
import { TenantsService } from '@common-ui/tenants';
|
|
||||||
|
|
||||||
export function loadCustomTheme(cssFileName: string) {
|
function loadCustomTheme() {
|
||||||
const head = document.getElementsByTagName('head')[0];
|
const cssFileName = getConfig<AppConfig>().THEME;
|
||||||
const link = document.createElement('link');
|
|
||||||
link.id = cssFileName;
|
if (cssFileName) {
|
||||||
link.rel = 'stylesheet';
|
const head = document.getElementsByTagName('head')[0];
|
||||||
link.type = 'text/css';
|
const link = document.createElement('link');
|
||||||
link.href = 'assets/styles/themes/' + cssFileName + '.css';
|
link.id = cssFileName;
|
||||||
link.media = 'all';
|
link.rel = 'stylesheet';
|
||||||
head.appendChild(link);
|
link.type = 'text/css';
|
||||||
|
link.href = 'assets/styles/themes/' + cssFileName + '.css';
|
||||||
|
link.media = 'all';
|
||||||
|
head.appendChild(link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-root',
|
selector: 'redaction-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
constructor(
|
constructor(
|
||||||
@ -32,54 +33,18 @@ export class AppComponent {
|
|||||||
readonly viewContainerRef: ViewContainerRef,
|
readonly viewContainerRef: ViewContainerRef,
|
||||||
/** RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load */
|
/** RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load */
|
||||||
private readonly _routerHistoryService: RouterHistoryService,
|
private readonly _routerHistoryService: RouterHistoryService,
|
||||||
userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
renderer: Renderer2,
|
readonly documentViewer: REDDocumentViewer,
|
||||||
private readonly _router: Router,
|
private readonly _dossierChangesService: DossiersChangesService,
|
||||||
private readonly _iconRegistry: MatIconRegistry,
|
@Inject(DOCUMENT) private readonly _document: Document,
|
||||||
private readonly _sanitizer: DomSanitizer,
|
private readonly _renderer: Renderer2,
|
||||||
private readonly _tenantsService: TenantsService,
|
private readonly _permissionsService: IqserPermissionsService,
|
||||||
) {
|
) {
|
||||||
const config = getConfig<AppConfig>();
|
this._renderer.addClass(this._document.body, _userPreferenceService.getTheme());
|
||||||
renderer.addClass(document.body, userPreferenceService.getTheme());
|
loadCustomTheme();
|
||||||
|
// TODO: Find a better place to initialize dossiers refresh
|
||||||
const removeQueryParams = _router.events.pipe(
|
if (_permissionsService.has(ROLES.dossiers.read)) {
|
||||||
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
|
_dossierChangesService.initializeRefresh();
|
||||||
map(event => event.urlAfterRedirects),
|
}
|
||||||
filter(url => url.includes('code=') || url.includes('state=') || url.includes('session_state=')),
|
|
||||||
switchMap(() => this.#removeKeycloakQueryParams()),
|
|
||||||
take(1),
|
|
||||||
);
|
|
||||||
removeQueryParams.subscribe();
|
|
||||||
|
|
||||||
this._tenantsService
|
|
||||||
.waitForSettingTenant()
|
|
||||||
.pipe(
|
|
||||||
tap(() => {
|
|
||||||
const isDocumine = this._tenantsService.activeTenant.documine;
|
|
||||||
const logo = isDocumine ? 'documine' : 'redaction';
|
|
||||||
_iconRegistry.addSvgIconInNamespace(
|
|
||||||
'iqser',
|
|
||||||
'logo',
|
|
||||||
_sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/general/${logo}-logo.svg`),
|
|
||||||
);
|
|
||||||
if (isDocumine) {
|
|
||||||
document.getElementById('favicon').setAttribute('href', 'assets/icons/documine-logo.ico');
|
|
||||||
}
|
|
||||||
loadCustomTheme(isDocumine ? APP_TYPE_PATHS.SCM : APP_TYPE_PATHS.REDACT);
|
|
||||||
}),
|
|
||||||
take(1),
|
|
||||||
)
|
|
||||||
.subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
#removeKeycloakQueryParams() {
|
|
||||||
return this._router.navigate([], {
|
|
||||||
queryParams: {
|
|
||||||
state: null,
|
|
||||||
session_state: null,
|
|
||||||
code: null,
|
|
||||||
},
|
|
||||||
queryParamsHandling: 'merge',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,101 +1,94 @@
|
|||||||
import { APP_BASE_HREF, DatePipe as BaseDatePipe } from '@angular/common';
|
|
||||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
||||||
import { ErrorHandler, inject, NgModule, provideEnvironmentInitializer } from '@angular/core';
|
|
||||||
import { MatDividerModule } from '@angular/material/divider';
|
|
||||||
import { MatIcon } from '@angular/material/icon';
|
|
||||||
import { MatMenu, MatMenuContent, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
|
||||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
|
||||||
import { MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltip } from '@angular/material/tooltip';
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import { ChevronButtonComponent } from '@common-ui/buttons/chevron-button';
|
|
||||||
import { EmptyStateComponent } from '@common-ui/empty-state';
|
|
||||||
import { HelpModeKey } from '@common-ui/help-mode/types';
|
|
||||||
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
|
|
||||||
import { RoundCheckboxComponent } from '@common-ui/inputs/round-checkbox/round-checkbox.component';
|
|
||||||
import { GET_TENANT_FROM_PATH_FN, UI_ROOT } from '@common-ui/utils';
|
|
||||||
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
|
||||||
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
||||||
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
|
import { MissingTranslationHandler } from '@ngx-translate/core';
|
||||||
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
|
||||||
import { NotificationsComponent } from '@components/notifications/notifications.component';
|
|
||||||
import { DashboardSkeletonComponent } from '@components/skeleton/dashboard-skeleton/dashboard-skeleton.component';
|
|
||||||
import { DossierSkeletonComponent } from '@components/skeleton/dossier-skeleton/dossier-skeleton.component';
|
|
||||||
import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skeleton-stats.component';
|
|
||||||
import { SkeletonTopBarComponent } from '@components/skeleton/skeleton-top-bar/skeleton-top-bar.component';
|
|
||||||
import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight-search.component';
|
|
||||||
import { TenantsMenuComponent } from '@components/tenants-menu/tenants-menu.component';
|
|
||||||
import { UserMenuComponent } from '@components/user-menu/user-menu.component';
|
|
||||||
import { environment } from '@environments/environment';
|
|
||||||
import {
|
import {
|
||||||
|
BASE_HREF,
|
||||||
CachingModule,
|
CachingModule,
|
||||||
CircleButtonComponent,
|
CommonUiModule,
|
||||||
HelpButtonComponent,
|
IqserHelpModeModule,
|
||||||
HelpModeComponent,
|
|
||||||
HiddenActionDirective,
|
|
||||||
IconButtonComponent,
|
|
||||||
IqserAllowDirective,
|
|
||||||
IqserDenyDirective,
|
|
||||||
IqserListingModule,
|
|
||||||
IqserLoadingModule,
|
IqserLoadingModule,
|
||||||
|
IqserPermissionsModule,
|
||||||
|
IqserPermissionsService,
|
||||||
|
IqserSharedModule,
|
||||||
IqserTranslateModule,
|
IqserTranslateModule,
|
||||||
|
IqserUsersModule,
|
||||||
LanguageService,
|
LanguageService,
|
||||||
MAX_RETRIES_ON_SERVER_ERROR,
|
MAX_RETRIES_ON_SERVER_ERROR,
|
||||||
SERVER_ERROR_SKIP_PATHS,
|
SERVER_ERROR_SKIP_PATHS,
|
||||||
ServerErrorInterceptor,
|
ServerErrorInterceptor,
|
||||||
StopPropagationDirective,
|
ToastComponent,
|
||||||
} from '@iqser/common-ui';
|
} from '@iqser/common-ui';
|
||||||
import { CommonUiModule } from '@iqser/common-ui/lib/common-ui.module';
|
import { ToastrModule } from 'ngx-toastr';
|
||||||
import { provideHelpMode } from '@iqser/common-ui/lib/help-mode/utils/help-mode.provider';
|
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||||
import { LogoComponent, SkeletonComponent, ToastComponent } from '@iqser/common-ui/lib/shared';
|
import { environment } from '@environments/environment';
|
||||||
import { TenantsModule } from '@iqser/common-ui/lib/tenants';
|
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
||||||
import { InitialsAvatarComponent, IqserUsersModule } from '@iqser/common-ui/lib/users';
|
import { NotificationsComponent } from '@components/notifications/notifications.component';
|
||||||
|
import { DashboardSkeletonComponent } from '@components/skeleton/dashboard-skeleton/dashboard-skeleton.component';
|
||||||
|
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
|
||||||
|
import { DatePipe as BaseDatePipe } from '@angular/common';
|
||||||
|
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
|
||||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { GlobalErrorHandler } from '@utils/global-error-handler.service';
|
||||||
import { AppConfig, ILoggerConfig } from '@red/domain';
|
import { REDMissingTranslationHandler } from '@utils/missing-translations-handler';
|
||||||
|
import { configurationInitializer } from '@utils/configuration.initializer';
|
||||||
import { ConfigService } from '@services/config.service';
|
import { ConfigService } from '@services/config.service';
|
||||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight-search.component';
|
||||||
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
|
||||||
import { GlobalErrorHandler } from '@services/global-error-handler.service';
|
|
||||||
import { LoggerRulesService } from '@services/logger-rules.service';
|
|
||||||
import { provideCustomDateFormatter } from '@shared/custom-date-formatting.provider';
|
|
||||||
import { NavigateLastDossiersScreenDirective } from '@shared/directives/navigate-last-dossiers-screen.directive';
|
|
||||||
import { DatePipe } from '@shared/pipes/date.pipe';
|
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||||
import { RedRoleGuard } from '@users/red-role.guard';
|
import * as links from '../assets/help-mode/links.json';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||||
|
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
|
||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
import { UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { UserService } from '@users/user.service';
|
import { UserService } from '@users/user.service';
|
||||||
import { UI_CACHES } from '@utils/constants';
|
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||||
import { ColorPickerService } from 'ngx-color-picker';
|
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
||||||
import { LoggerModule, NGXLogger, NgxLoggerLevel, TOKEN_LOGGER_CONFIG, TOKEN_LOGGER_RULES_SERVICE } from 'ngx-logger';
|
import { FeaturesService } from '@services/features.service';
|
||||||
import { ToastrModule } from 'ngx-toastr';
|
import { MAT_LEGACY_TOOLTIP_DEFAULT_OPTIONS as MAT_TOOLTIP_DEFAULT_OPTIONS } from '@angular/material/legacy-tooltip';
|
||||||
import helpModeKeys from '../assets/help-mode/help-mode-keys.json';
|
import { LoggerModule, NgxLoggerLevel, TOKEN_LOGGER_CONFIG, TOKEN_LOGGER_RULES_SERVICE } from 'ngx-logger';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { LoggerRulesService } from '@services/logger-rules.service';
|
||||||
import { AppComponent } from './app.component';
|
import { AppConfig, ILoggerConfig } from '@red/domain';
|
||||||
import { IconsModule } from './modules/icons/icons.module';
|
import { SystemPreferencesService } from '@services/system-preferences.service';
|
||||||
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
|
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
|
||||||
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
|
import { LicenseService } from '@services/license.service';
|
||||||
|
import { TenantIdInterceptor } from '@utils/tenant-id-interceptor';
|
||||||
|
import { UI_CACHES } from '@utils/constants';
|
||||||
|
import { RedRoleGuard } from '@users/red-role.guard';
|
||||||
|
import { SkeletonTopBarComponent } from '@components/skeleton/skeleton-top-bar/skeleton-top-bar.component';
|
||||||
|
import { DossierSkeletonComponent } from '@components/skeleton/dossier-skeleton/dossier-skeleton.component';
|
||||||
|
import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skeleton-stats.component';
|
||||||
|
|
||||||
|
const screens = [BaseScreenComponent, DownloadsListScreenComponent];
|
||||||
|
|
||||||
|
const components = [
|
||||||
|
AppComponent,
|
||||||
|
AuthErrorComponent,
|
||||||
|
NotificationsComponent,
|
||||||
|
SpotlightSearchComponent,
|
||||||
|
BreadcrumbsComponent,
|
||||||
|
DashboardSkeletonComponent,
|
||||||
|
DossierSkeletonComponent,
|
||||||
|
SkeletonTopBarComponent,
|
||||||
|
SkeletonStatsComponent,
|
||||||
|
|
||||||
|
...screens,
|
||||||
|
];
|
||||||
|
|
||||||
export const appModuleFactory = (config: AppConfig) => {
|
export const appModuleFactory = (config: AppConfig) => {
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [...components],
|
||||||
AppComponent,
|
|
||||||
AuthErrorComponent,
|
|
||||||
NotificationsComponent,
|
|
||||||
SpotlightSearchComponent,
|
|
||||||
BreadcrumbsComponent,
|
|
||||||
DashboardSkeletonComponent,
|
|
||||||
DossierSkeletonComponent,
|
|
||||||
SkeletonTopBarComponent,
|
|
||||||
SkeletonStatsComponent,
|
|
||||||
BaseScreenComponent,
|
|
||||||
UserMenuComponent,
|
|
||||||
TenantsMenuComponent,
|
|
||||||
DownloadsListScreenComponent,
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
|
SharedModule,
|
||||||
|
FileUploadDownloadModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
MonacoEditorModule,
|
MonacoEditorModule,
|
||||||
CommonUiModule.forRoot({
|
CommonUiModule.forRoot({
|
||||||
@ -107,7 +100,9 @@ export const appModuleFactory = (config: AppConfig) => {
|
|||||||
existingUserService: UserService,
|
existingUserService: UserService,
|
||||||
existingRoleGuard: RedRoleGuard,
|
existingRoleGuard: RedRoleGuard,
|
||||||
}),
|
}),
|
||||||
|
IqserSharedModule,
|
||||||
CachingModule.forRoot(UI_CACHES),
|
CachingModule.forRoot(UI_CACHES),
|
||||||
|
IqserHelpModeModule.forRoot(links),
|
||||||
PdfViewerModule,
|
PdfViewerModule,
|
||||||
ToastrModule.forRoot({
|
ToastrModule.forRoot({
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
@ -116,9 +111,9 @@ export const appModuleFactory = (config: AppConfig) => {
|
|||||||
preventDuplicates: true,
|
preventDuplicates: true,
|
||||||
resetTimeoutOnDuplicate: true,
|
resetTimeoutOnDuplicate: true,
|
||||||
}),
|
}),
|
||||||
TenantsModule.forRoot(),
|
IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY || '/assets/i18n/redact/' }),
|
||||||
IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY }),
|
|
||||||
IqserLoadingModule.forRoot(),
|
IqserLoadingModule.forRoot(),
|
||||||
|
IqserPermissionsModule.forRoot(),
|
||||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||||
LoggerModule.forRoot(undefined, {
|
LoggerModule.forRoot(undefined, {
|
||||||
ruleProvider: {
|
ruleProvider: {
|
||||||
@ -129,7 +124,7 @@ export const appModuleFactory = (config: AppConfig) => {
|
|||||||
provide: TOKEN_LOGGER_CONFIG,
|
provide: TOKEN_LOGGER_CONFIG,
|
||||||
useValue: {
|
useValue: {
|
||||||
level: environment.production ? NgxLoggerLevel.ERROR : NgxLoggerLevel.DEBUG,
|
level: environment.production ? NgxLoggerLevel.ERROR : NgxLoggerLevel.DEBUG,
|
||||||
enableSourceMaps: false,
|
enableSourceMaps: true,
|
||||||
timestampFormat: 'mm:ss:SSS',
|
timestampFormat: 'mm:ss:SSS',
|
||||||
disableFileDetails: true,
|
disableFileDetails: true,
|
||||||
features: {
|
features: {
|
||||||
@ -141,14 +136,8 @@ export const appModuleFactory = (config: AppConfig) => {
|
|||||||
FILTERS: {
|
FILTERS: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
TENANTS: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
ROUTES: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
PDF: {
|
PDF: {
|
||||||
enabled: false,
|
enabled: true,
|
||||||
},
|
},
|
||||||
FILE: {
|
FILE: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@ -159,71 +148,12 @@ export const appModuleFactory = (config: AppConfig) => {
|
|||||||
STATS: {
|
STATS: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
REDACTION_LOG: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
VIEWED_PAGES: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
PAGES: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
DOSSIERS_CHANGES: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
GUARDS: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as ILoggerConfig,
|
} as ILoggerConfig,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
CircleButtonComponent,
|
|
||||||
EmptyStateComponent,
|
|
||||||
SkeletonComponent,
|
|
||||||
LogoComponent,
|
|
||||||
HiddenActionDirective,
|
|
||||||
StopPropagationDirective,
|
|
||||||
InputWithActionComponent,
|
|
||||||
RoundCheckboxComponent,
|
|
||||||
IqserAllowDirective,
|
|
||||||
IqserDenyDirective,
|
|
||||||
IqserListingModule,
|
|
||||||
IconButtonComponent,
|
|
||||||
MatDividerModule,
|
|
||||||
ChevronButtonComponent,
|
|
||||||
InitialsAvatarComponent,
|
|
||||||
HelpModeComponent,
|
|
||||||
HelpButtonComponent,
|
|
||||||
MatMenuTrigger,
|
|
||||||
MatMenuItem,
|
|
||||||
MatIcon,
|
|
||||||
MatMenu,
|
|
||||||
MatMenuContent,
|
|
||||||
MatTooltip,
|
|
||||||
MatProgressSpinner,
|
|
||||||
IconsModule,
|
|
||||||
NavigateLastDossiersScreenDirective,
|
|
||||||
DatePipe,
|
|
||||||
TranslateModule,
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
|
||||||
provide: UI_ROOT,
|
|
||||||
useValue: '/ui',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: APP_BASE_HREF,
|
|
||||||
useFactory: () => {
|
|
||||||
const uiRoot = inject(UI_ROOT);
|
|
||||||
const tenant = inject(GET_TENANT_FROM_PATH_FN)();
|
|
||||||
console.log(tenant);
|
|
||||||
const appBaseHref = uiRoot + '/' + tenant;
|
|
||||||
|
|
||||||
inject(NGXLogger).info('Provide APP_BASE_HREF:', appBaseHref);
|
|
||||||
return appBaseHref;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
multi: true,
|
multi: true,
|
||||||
@ -233,13 +163,37 @@ export const appModuleFactory = (config: AppConfig) => {
|
|||||||
provide: ErrorHandler,
|
provide: ErrorHandler,
|
||||||
useClass: GlobalErrorHandler,
|
useClass: GlobalErrorHandler,
|
||||||
},
|
},
|
||||||
provideEnvironmentInitializer(async () => {
|
{
|
||||||
const languageService = inject(LanguageService);
|
provide: HTTP_INTERCEPTORS,
|
||||||
return languageService.setInitialLanguage();
|
multi: true,
|
||||||
}),
|
useClass: TenantIdInterceptor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
multi: true,
|
||||||
|
useFactory: configurationInitializer,
|
||||||
|
deps: [
|
||||||
|
BASE_HREF,
|
||||||
|
KeycloakService,
|
||||||
|
ConfigService,
|
||||||
|
SystemPreferencesService,
|
||||||
|
FeaturesService,
|
||||||
|
GeneralSettingsService,
|
||||||
|
LanguageService,
|
||||||
|
UserService,
|
||||||
|
UserPreferenceService,
|
||||||
|
LicenseService,
|
||||||
|
IqserPermissionsService,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MissingTranslationHandler,
|
||||||
|
useClass: REDMissingTranslationHandler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: MAX_RETRIES_ON_SERVER_ERROR,
|
provide: MAX_RETRIES_ON_SERVER_ERROR,
|
||||||
useFactory: () => config.MAX_RETRIES_ON_SERVER_ERROR,
|
useFactory: (configService: ConfigService) => configService.values.MAX_RETRIES_ON_SERVER_ERROR,
|
||||||
|
deps: [ConfigService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SERVER_ERROR_SKIP_PATHS,
|
provide: SERVER_ERROR_SKIP_PATHS,
|
||||||
@ -257,18 +211,33 @@ export const appModuleFactory = (config: AppConfig) => {
|
|||||||
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
|
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
|
||||||
useValue: {
|
useValue: {
|
||||||
disableTooltipInteractivity: true,
|
disableTooltipInteractivity: true,
|
||||||
showDelay: 1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BaseDatePipe,
|
|
||||||
DatePipe,
|
DatePipe,
|
||||||
...provideCustomDateFormatter(),
|
BaseDatePipe,
|
||||||
...provideHelpMode(helpModeKeys as HelpModeKey[]),
|
|
||||||
ColorPickerService,
|
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
class AppModule {}
|
class AppModule {
|
||||||
|
constructor(private readonly _router: Router, private readonly _route: ActivatedRoute) {
|
||||||
|
this._configureKeyCloakRouteHandling();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _configureKeyCloakRouteHandling() {
|
||||||
|
this._route.queryParamMap.subscribe(queryParams => {
|
||||||
|
if (queryParams.has('code') || queryParams.has('state') || queryParams.has('session_state')) {
|
||||||
|
this._router.navigate([], {
|
||||||
|
queryParams: {
|
||||||
|
state: null,
|
||||||
|
session_state: null,
|
||||||
|
code: null,
|
||||||
|
},
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return AppModule;
|
return AppModule;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { UserService } from '@users/user.service';
|
import { UserService } from '@users/user.service';
|
||||||
import { getConfig } from '@iqser/common-ui';
|
import { ConfigService } from '@services/config.service';
|
||||||
import { AppConfig } from '@red/domain';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-auth-error',
|
selector: 'redaction-auth-error',
|
||||||
templateUrl: './auth-error.component.html',
|
templateUrl: './auth-error.component.html',
|
||||||
styleUrls: ['./auth-error.component.scss'],
|
styleUrls: ['./auth-error.component.scss'],
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class AuthErrorComponent {
|
export class AuthErrorComponent {
|
||||||
readonly #config = getConfig<AppConfig>();
|
adminName = this._configService.values.ADMIN_CONTACT_NAME;
|
||||||
readonly adminName = this.#config.ADMIN_CONTACT_NAME;
|
adminUrl = this._configService.values.ADMIN_CONTACT_URL;
|
||||||
readonly adminUrl = this.#config.ADMIN_CONTACT_URL;
|
|
||||||
|
|
||||||
constructor(readonly userService: UserService) {}
|
constructor(readonly userService: UserService, private readonly _configService: ConfigService) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<iqser-help-mode></iqser-help-mode>
|
<iqser-help-mode *deny="roles.getRss"></iqser-help-mode>
|
||||||
|
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<ng-template #menuPlaceholder>
|
<ng-template #menuPlaceholder>
|
||||||
@ -9,9 +9,11 @@
|
|||||||
<redaction-breadcrumbs></redaction-breadcrumbs>
|
<redaction-breadcrumbs></redaction-breadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/main']" class="logo">
|
<a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/']" class="logo">
|
||||||
<div [attr.help-mode-key]="'home'" class="actions">
|
<div [iqserHelpMode]="'home'" class="actions">
|
||||||
<iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo>
|
<iqser-hidden-action (action)="userPreferenceService.toggleDevFeatures()">
|
||||||
|
<iqser-logo icon="red:logo"></iqser-logo>
|
||||||
|
</iqser-hidden-action>
|
||||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -21,30 +23,34 @@
|
|||||||
<redaction-spotlight-search
|
<redaction-spotlight-search
|
||||||
*allow="roles.search; if: (isSearchScreen$ | async) === false && (currentUser.isUser || currentUser.isManager)"
|
*allow="roles.search; if: (isSearchScreen$ | async) === false && (currentUser.isUser || currentUser.isManager)"
|
||||||
[actions]="searchActions"
|
[actions]="searchActions"
|
||||||
[attr.help-mode-key]="'search_entire_application'"
|
[iqserHelpMode]="'search_in_entire_application'"
|
||||||
[placeholder]="'search.placeholder' | translate"
|
[placeholder]="'search.placeholder' | translate"
|
||||||
></redaction-spotlight-search>
|
></redaction-spotlight-search>
|
||||||
|
|
||||||
<iqser-help-button [dialogButton]="false"></iqser-help-button>
|
<iqser-help-button *deny="roles.getRss" [iqserHelpMode]="'help_mode'"></iqser-help-button>
|
||||||
|
|
||||||
<redaction-notifications
|
<redaction-notifications [iqserHelpMode]="'open_notifications'"></redaction-notifications>
|
||||||
*ngIf="currentUser.isUser || currentUser.isManager"
|
|
||||||
[attr.help-mode-key]="'open_notifications'"
|
|
||||||
></redaction-notifications>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<iqser-user-button [attr.help-mode-key]="'open_usermenu'" [matMenuTriggerFor]="userMenu" id="userMenu"></iqser-user-button>
|
<iqser-user-button [iqserHelpMode]="'open_usermenu'" [matMenuTriggerFor]="userMenu" id="userMenu"></iqser-user-button>
|
||||||
|
|
||||||
<mat-menu #userMenu="matMenu" xPosition="before">
|
<mat-menu #userMenu="matMenu" xPosition="before">
|
||||||
<ng-template matMenuContent>
|
<div id="user-menu-items">
|
||||||
<app-user-menu></app-user-menu>
|
<ng-container *ngFor="let item of userMenuItems; trackBy: trackByName">
|
||||||
</ng-template>
|
<a (click)="item.action?.()" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink" mat-menu-item>
|
||||||
|
{{ item.name | translate }}
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<button (click)="userService.logout()" id="logout" mat-menu-item>
|
||||||
|
<mat-icon svgIcon="iqser:logout"></mat-icon>
|
||||||
|
<span translate="top-bar.navigation-items.my-account.children.logout"> </span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="userPreferenceService.isIqserDevMode" class="dev-mode" translate="dev-mode"></div>
|
<div *ngIf="userPreferenceService.areDevFeaturesEnabled" class="dev-mode" translate="dev-mode"></div>
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
||||||
<redaction-pdf-viewer [style.visibility]="documentViewer.loaded() ? 'visible' : 'hidden'"></redaction-pdf-viewer>
|
|
||||||
|
|||||||
@ -1,73 +1,102 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { UserService } from '@users/user.service';
|
import { UserService } from '@users/user.service';
|
||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
import { UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { ActivatedRoute, NavigationStart, ParamMap, Router } from '@angular/router';
|
import { NavigationStart, Router } from '@angular/router';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { filter, map, startWith } from 'rxjs/operators';
|
import { filter, map, startWith } from 'rxjs/operators';
|
||||||
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
|
import { IqserPermissionsService, shareDistinctLast } from '@iqser/common-ui';
|
||||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||||
import { ARCHIVE_ROUTE, DOSSIERS_ROUTE } from '@red/domain';
|
import { FeaturesService } from '@services/features.service';
|
||||||
import { Roles } from '@users/roles';
|
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
|
||||||
import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service';
|
import { ROLES } from '@users/roles';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
||||||
import { List, shareDistinctLast } from '@iqser/common-ui/lib/utils';
|
|
||||||
|
|
||||||
const isNavigationStart = (event: unknown): event is NavigationStart => event instanceof NavigationStart;
|
interface MenuItem {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly routerLink?: string;
|
||||||
|
readonly show: boolean;
|
||||||
|
readonly action?: () => void;
|
||||||
|
readonly showDot?: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNavigationStart = event => event instanceof NavigationStart;
|
||||||
const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
|
const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './base-screen.component.html',
|
templateUrl: './base-screen.component.html',
|
||||||
styleUrls: ['./base-screen.component.scss'],
|
styleUrls: ['./base-screen.component.scss'],
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class BaseScreenComponent {
|
export class BaseScreenComponent {
|
||||||
readonly roles = Roles;
|
readonly roles = ROLES;
|
||||||
readonly documentViewer = inject(REDDocumentViewer);
|
|
||||||
readonly currentUser = this.userService.currentUser;
|
readonly currentUser = this.userService.currentUser;
|
||||||
readonly searchActions: List<SpotlightSearchAction> = [
|
readonly userMenuItems: readonly MenuItem[] = [
|
||||||
|
{
|
||||||
|
id: 'account',
|
||||||
|
name: _('top-bar.navigation-items.my-account.children.account'),
|
||||||
|
routerLink: '/main/account',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'admin',
|
||||||
|
name: _('top-bar.navigation-items.my-account.children.admin'),
|
||||||
|
routerLink: '/main/admin',
|
||||||
|
show: (this.currentUser.isManager || this.currentUser.isUserAdmin) && this.permissionsService.has([ROLES.templates.read]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'downloads',
|
||||||
|
name: _('top-bar.navigation-items.my-account.children.downloads'),
|
||||||
|
routerLink: '/main/downloads',
|
||||||
|
show: this.currentUser.isUser && this.permissionsService.has(ROLES.readDownloadStatus),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trash',
|
||||||
|
name: _('top-bar.navigation-items.my-account.children.trash'),
|
||||||
|
routerLink: '/main/trash',
|
||||||
|
show: this.currentUser.isUser && this.permissionsService.has([ROLES.dossiers.read, ROLES.files.readStatus]),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
readonly searchActions: readonly SpotlightSearchAction[] = [
|
||||||
{
|
{
|
||||||
text: this._translateService.instant('search.this-dossier'),
|
text: this._translateService.instant('search.this-dossier'),
|
||||||
icon: 'red:enter',
|
icon: 'red:enter',
|
||||||
hide: (): boolean => this.#hideSearchThisDossier,
|
hide: (): boolean => this._hideSearchThisDossier,
|
||||||
action: (query): void => this.#searchThisDossier(query),
|
action: (query): void => this._searchThisDossier(query),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: this._translateService.instant('search.active-dossiers'),
|
text: this._translateService.instant('search.active-dossiers'),
|
||||||
icon: 'red:enter',
|
icon: 'red:enter',
|
||||||
action: (query): void => this.#search(query, [], true),
|
hide: () => !this._featuresService.isEnabled(DOSSIERS_ARCHIVE),
|
||||||
|
action: (query): void => this._search(query, [], true),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: this._translateService.instant('search.all-dossiers'),
|
text: this._translateService.instant('search.all-dossiers'),
|
||||||
icon: 'red:enter',
|
icon: 'red:enter',
|
||||||
action: (query): void => this.#search(query, []),
|
action: (query): void => this._search(query, []),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
readonly config = getConfig();
|
private readonly _navigationStart$ = this._router.events.pipe(
|
||||||
readonly #navigationStart$ = this._router.events.pipe(
|
|
||||||
filter(isNavigationStart),
|
filter(isNavigationStart),
|
||||||
map(event => event.url),
|
map((event: NavigationStart) => event.url),
|
||||||
startWith(this._router.url),
|
startWith(this._router.url),
|
||||||
shareDistinctLast(),
|
shareDistinctLast(),
|
||||||
);
|
);
|
||||||
readonly isSearchScreen$ = this.#navigationStart$.pipe(map(isSearchScreen));
|
readonly isSearchScreen$ = this._navigationStart$.pipe(map(isSearchScreen));
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _router: Router,
|
private readonly _router: Router,
|
||||||
activatedRoute: ActivatedRoute,
|
|
||||||
private readonly _translateService: TranslateService,
|
private readonly _translateService: TranslateService,
|
||||||
|
private readonly _featuresService: FeaturesService,
|
||||||
readonly permissionsService: IqserPermissionsService,
|
readonly permissionsService: IqserPermissionsService,
|
||||||
readonly userService: UserService,
|
readonly userService: UserService,
|
||||||
readonly userPreferenceService: UserPreferenceService,
|
readonly userPreferenceService: UserPreferenceService,
|
||||||
readonly titleService: Title,
|
readonly titleService: Title,
|
||||||
readonly breadcrumbsService: BreadcrumbsService,
|
readonly breadcrumbsService: BreadcrumbsService,
|
||||||
) {
|
) {}
|
||||||
// eslint-disable-next-line rxjs/no-ignored-subscription
|
|
||||||
activatedRoute.queryParamMap.pipe(takeUntilDestroyed()).subscribe(queryParams => this.#navigate(queryParams));
|
|
||||||
}
|
|
||||||
|
|
||||||
get #hideSearchThisDossier() {
|
private get _hideSearchThisDossier() {
|
||||||
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
|
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
|
||||||
if (!routerLink) {
|
if (!routerLink) {
|
||||||
return true;
|
return true;
|
||||||
@ -77,28 +106,21 @@ export class BaseScreenComponent {
|
|||||||
return !isDossierOverview;
|
return !isDossierOverview;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navigate(queryParams: ParamMap) {
|
trackByName(_index: number, item: MenuItem) {
|
||||||
if (queryParams.has('username')) {
|
return item.name;
|
||||||
return this._router.navigate([], {
|
|
||||||
queryParams: {
|
|
||||||
username: null,
|
|
||||||
},
|
|
||||||
queryParamsHandling: 'merge',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#search(query: string, dossierIds: string[], onlyActive = false) {
|
private _search(query: string, dossierIds: string[], onlyActive = false) {
|
||||||
const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive };
|
const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive };
|
||||||
this._router.navigate(['/main/search'], { queryParams }).then();
|
this._router.navigate(['main/search'], { queryParams }).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
#searchThisDossier(query: string) {
|
private _searchThisDossier(query: string) {
|
||||||
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
|
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
|
||||||
if (!routerLink) {
|
if (!routerLink) {
|
||||||
return this.#search(query, []);
|
return this._search(query, []);
|
||||||
}
|
}
|
||||||
const dossierId = routerLink[2];
|
const dossierId = routerLink[2];
|
||||||
return this.#search(query, [dossierId]);
|
return this._search(query, [dossierId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<div *ngIf="breadcrumbsService.breadcrumbs$ | async as breadcrumbs" [attr.help-mode-key]="'navigate_breadcrumbs'" class="breadcrumbs">
|
<div *ngIf="breadcrumbsService.breadcrumbs$ | async as breadcrumbs" [iqserHelpMode]="'navigate_in_breadcrumbs'" class="breadcrumbs">
|
||||||
<a *ngIf="breadcrumbs.length === 0; else items" class="breadcrumb back" redactionNavigateLastDossiersScreen>
|
<a *ngIf="breadcrumbs.length === 0; else items" class="breadcrumb back" redactionNavigateLastDossiersScreen>
|
||||||
<mat-icon svgIcon="iqser:expand"></mat-icon>
|
<mat-icon svgIcon="iqser:expand"></mat-icon>
|
||||||
{{ 'top-bar.navigation-items.back' | translate }}
|
{{ 'top-bar.navigation-items.back' | translate }}
|
||||||
@ -22,24 +22,21 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ng-container *ngIf="is(breadcrumb, 'dropdown')">
|
<ng-container *ngIf="is(breadcrumb, 'dropdown')">
|
||||||
<iqser-chevron-button
|
<button [matMenuTriggerFor]="dropdownMenu" class="dropdown-breadcrumb" mat-button>
|
||||||
[label]="breadcrumb.name$ | async"
|
<span>{{ breadcrumb.name$ | async }}</span>
|
||||||
[matMenuTriggerFor]="dropdownMenu"
|
<mat-icon svgIcon="iqser:arrow-down"></mat-icon>
|
||||||
class="dropdown-breadcrumb"
|
</button>
|
||||||
></iqser-chevron-button>
|
|
||||||
|
|
||||||
<mat-menu #dropdownMenu="matMenu" class="padding-bottom-8">
|
<mat-menu #dropdownMenu="matMenu" class="padding-bottom-8">
|
||||||
<div id="breadcrumbs-menu-items">
|
<a
|
||||||
<a
|
*ngFor="let option of breadcrumb.options.options"
|
||||||
*ngFor="let option of breadcrumb.options.options"
|
[routerLink]="option.options.routerLink"
|
||||||
[routerLink]="option.options.routerLink"
|
mat-menu-item
|
||||||
mat-menu-item
|
routerLinkActive="active"
|
||||||
routerLinkActive="active"
|
>
|
||||||
>
|
{{ option.name$ | async }}
|
||||||
{{ option.name$ | async }}
|
<mat-icon class="checkmark" svgIcon="iqser:check"></mat-icon>
|
||||||
<mat-icon class="checkmark" svgIcon="iqser:check"></mat-icon>
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { Breadcrumb, BreadcrumbDisplayType, BreadcrumbsService } from '@services
|
|||||||
selector: 'redaction-breadcrumbs',
|
selector: 'redaction-breadcrumbs',
|
||||||
templateUrl: './breadcrumbs.component.html',
|
templateUrl: './breadcrumbs.component.html',
|
||||||
styleUrls: ['./breadcrumbs.component.scss'],
|
styleUrls: ['./breadcrumbs.component.scss'],
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class BreadcrumbsComponent {
|
export class BreadcrumbsComponent {
|
||||||
constructor(readonly breadcrumbsService: BreadcrumbsService) {}
|
constructor(readonly breadcrumbsService: BreadcrumbsService) {}
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<iqser-table
|
<iqser-table
|
||||||
[bulkActions]="bulkActions"
|
[bulkActions]="bulkActions"
|
||||||
[headerHelpModeKey]="'my_downloads'"
|
|
||||||
[itemSize]="80"
|
[itemSize]="80"
|
||||||
[noDataText]="'downloads-list.no-data.title' | translate"
|
[noDataText]="'downloads-list.no-data.title' | translate"
|
||||||
[selectionEnabled]="true"
|
[selectionEnabled]="true"
|
||||||
[tableColumnConfigs]="tableColumnConfigs"
|
[tableColumnConfigs]="tableColumnConfigs"
|
||||||
|
[headerHelpModeKey]="'my_downloads'"
|
||||||
noDataIcon="iqser:download"
|
noDataIcon="iqser:download"
|
||||||
></iqser-table>
|
></iqser-table>
|
||||||
</div>
|
</div>
|
||||||
@ -21,11 +21,11 @@
|
|||||||
(action)="deleteItems()"
|
(action)="deleteItems()"
|
||||||
*ngIf="listingService.areSomeSelected$ | async"
|
*ngIf="listingService.areSomeSelected$ | async"
|
||||||
[tooltip]="'downloads-list.bulk.delete' | translate"
|
[tooltip]="'downloads-list.bulk.delete' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
icon="iqser:trash"
|
icon="iqser:trash"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<!--TODO: move to a separate component-->
|
|
||||||
<ng-template #tableItemTemplate let-entity="entity">
|
<ng-template #tableItemTemplate let-entity="entity">
|
||||||
<div *ngIf="cast(entity) as download">
|
<div *ngIf="cast(entity) as download">
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<div class="small-label">
|
<div class="small-label">
|
||||||
{{ download.creationDate | date : 'd MMM yyyy, hh:mm a' }}
|
{{ download.creationDate | date: 'd MMM yyyy, hh:mm a' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -56,12 +56,14 @@
|
|||||||
(action)="downloadItem(download)"
|
(action)="downloadItem(download)"
|
||||||
*ngIf="download.status === 'READY' && !download.inProgress"
|
*ngIf="download.status === 'READY' && !download.inProgress"
|
||||||
[tooltip]="'downloads-list.actions.download' | translate"
|
[tooltip]="'downloads-list.actions.download' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
icon="iqser:download"
|
icon="iqser:download"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="deleteItems([download])"
|
(action)="deleteItems([download])"
|
||||||
[tooltip]="'downloads-list.actions.delete' | translate"
|
[tooltip]="'downloads-list.actions.delete' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
icon="iqser:trash"
|
icon="iqser:trash"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
import { Component, OnDestroy } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { ListingComponent, listingProvidersFactory, LoadingService, TableColumnConfig } from '@iqser/common-ui';
|
|
||||||
import { DownloadStatus } from '@red/domain';
|
|
||||||
import { RouterHistoryService } from '@services/router-history.service';
|
|
||||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||||
|
import { DownloadStatus } from '@red/domain';
|
||||||
|
import { CircleButtonTypes, ListingComponent, listingProvidersFactory, LoadingService, TableColumnConfig } from '@iqser/common-ui';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { RouterHistoryService } from '@services/router-history.service';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
selector: 'redaction-downloads-list-screen',
|
||||||
templateUrl: './downloads-list-screen.component.html',
|
templateUrl: './downloads-list-screen.component.html',
|
||||||
styleUrls: ['./downloads-list-screen.component.scss'],
|
styleUrls: ['./downloads-list-screen.component.scss'],
|
||||||
providers: listingProvidersFactory<DownloadStatus>({
|
providers: listingProvidersFactory<DownloadStatus>({
|
||||||
entitiesService: FileDownloadService,
|
entitiesService: FileDownloadService,
|
||||||
component: DownloadsListScreenComponent,
|
component: DownloadsListScreenComponent,
|
||||||
}),
|
}),
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class DownloadsListScreenComponent extends ListingComponent<DownloadStatus> implements OnDestroy {
|
export class DownloadsListScreenComponent extends ListingComponent<DownloadStatus> {
|
||||||
readonly #interval: number;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly tableHeaderLabel = _('downloads-list.table-header.title');
|
readonly tableHeaderLabel = _('downloads-list.table-header.title');
|
||||||
readonly tableColumnConfigs: TableColumnConfig<DownloadStatus>[] = [
|
readonly tableColumnConfigs: TableColumnConfig<DownloadStatus>[] = [
|
||||||
{ label: _('downloads-list.table-col-names.name'), width: '2fr' },
|
{ label: _('downloads-list.table-col-names.name'), width: '2fr' },
|
||||||
@ -32,11 +32,7 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._loadingService.loadWhile(this._loadData());
|
this._loadingService.loadWhile(this._loadData());
|
||||||
this.#interval = window.setInterval(() => this._loadData(), 5000);
|
setInterval(() => this._loadData(), 5000);
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
window.clearInterval(this.#interval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadItem(download: DownloadStatus) {
|
downloadItem(download: DownloadStatus) {
|
||||||
|
|||||||
@ -1,11 +1,4 @@
|
|||||||
<iqser-circle-button
|
<iqser-circle-button [matMenuTriggerFor]="menu" [showDot]="hasUnreadNotifications$ | async" icon="red:notification"></iqser-circle-button>
|
||||||
[matMenuTriggerFor]="menu"
|
|
||||||
[showDot]="hasUnreadNotifications$ | async"
|
|
||||||
[tooltipPosition]="'below'"
|
|
||||||
[tooltip]="'notifications.button-text' | translate"
|
|
||||||
buttonId="notification-button"
|
|
||||||
icon="red:notification"
|
|
||||||
></iqser-circle-button>
|
|
||||||
|
|
||||||
<mat-menu #menu="matMenu" backdropClass="notifications-backdrop" class="notifications-menu" xPosition="before">
|
<mat-menu #menu="matMenu" backdropClass="notifications-backdrop" class="notifications-menu" xPosition="before">
|
||||||
<ng-template matMenuContent>
|
<ng-template matMenuContent>
|
||||||
@ -20,22 +13,13 @@
|
|||||||
<div *ngFor="let group of groups; let first = first">
|
<div *ngFor="let group of groups; let first = first">
|
||||||
<div class="all-caps-label flex-align-items-center">
|
<div class="all-caps-label flex-align-items-center">
|
||||||
<div>{{ group.date }}</div>
|
<div>{{ group.date }}</div>
|
||||||
<div
|
<div (click)="markRead($event)" *ngIf="(hasUnreadNotifications$ | async) && first" class="view-all">View all</div>
|
||||||
(click)="markRead()"
|
|
||||||
*ngIf="(hasUnreadNotifications$ | async) && first"
|
|
||||||
class="view-all"
|
|
||||||
id="notifications-mark-all-as-read-btn"
|
|
||||||
iqserStopPropagation
|
|
||||||
>
|
|
||||||
{{ 'notifications.mark-all-as-read' | translate }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
(click)="handleMarkReadEvent($event, [notification], true)"
|
(click)="markRead($event, [notification], true)"
|
||||||
*ngFor="let notification of group.notifications; trackBy: trackBy"
|
*ngFor="let notification of group.notifications | sortBy: 'desc':'creationDate'"
|
||||||
[class.unread]="!notification.readDate"
|
[class.unread]="!notification.readDate"
|
||||||
[id]="'notifications-mark-as-read-' + notification.id + '-btn'"
|
|
||||||
class="notification"
|
class="notification"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
>
|
>
|
||||||
@ -43,14 +27,11 @@
|
|||||||
|
|
||||||
<div class="notification-content">
|
<div class="notification-content">
|
||||||
<div [innerHTML]="notification.message"></div>
|
<div [innerHTML]="notification.message"></div>
|
||||||
<div class="small-label mt-2">{{ notification.creationDate | date: 'exactDate' }}</div>
|
<div class="small-label mt-2">{{ notification.time }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
(click)="markRead([notification], !notification.readDate)"
|
(click)="markRead($event, [notification], !notification.readDate)"
|
||||||
[id]="'notifications-mark-' + notification.id"
|
|
||||||
class="dot"
|
class="dot"
|
||||||
iqserStopPropagation
|
|
||||||
matTooltip="{{ 'notifications.mark-as' | translate: { type: notification.readDate ? 'unread' : 'read' } }}"
|
matTooltip="{{ 'notifications.mark-as' | translate: { type: notification.readDate ? 'unread' : 'read' } }}"
|
||||||
matTooltipPosition="before"
|
matTooltipPosition="before"
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@use 'variables';
|
||||||
|
|
||||||
::ng-deep .notifications-backdrop + .cdk-overlay-connected-position-bounding-box {
|
::ng-deep .notifications-backdrop + .cdk-overlay-connected-position-bounding-box {
|
||||||
right: 0 !important;
|
right: 0 !important;
|
||||||
|
|
||||||
@ -21,8 +23,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-menu-item.notification {
|
.mat-menu-item.notification {
|
||||||
padding: 8px 26px 10px 8px !important;
|
padding: 8px 26px 10px 8px;
|
||||||
margin: 2px 0 0 0;
|
margin: 2px 0 0 0;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -65,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.unread {
|
&.unread {
|
||||||
background-color: rgba(var(--iqser-primary-rgb), 0.1);
|
background-color: rgba(variables.$primary, 0.1);
|
||||||
|
|
||||||
.dot {
|
.dot {
|
||||||
background-color: var(--iqser-primary);
|
background-color: var(--iqser-primary);
|
||||||
|
|||||||
@ -1,47 +1,37 @@
|
|||||||
import { Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { DatePipe } from '@shared/pipes/date.pipe';
|
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||||
import { NotificationsService } from '@services/notifications.service';
|
import { NotificationsService } from '@services/notifications.service';
|
||||||
import { Notification } from '@red/domain';
|
import { Notification } from '@red/domain';
|
||||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
import { Observable } from 'rxjs';
|
import { firstValueFrom, Observable } from 'rxjs';
|
||||||
import { isToday, shareLast, trackByFactory } from '@iqser/common-ui/lib/utils';
|
import { shareLast } from '@iqser/common-ui';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
interface NotificationsGroup {
|
interface NotificationsGroup {
|
||||||
date: string;
|
date: string;
|
||||||
notifications: Notification[];
|
notifications: Notification[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isOlderThan(date: string | Date | Dayjs, years: number) {
|
|
||||||
return dayjs(date).year() <= dayjs(Date.now()).subtract(years, 'year').year();
|
|
||||||
}
|
|
||||||
|
|
||||||
function chronologically(first: string, second: string) {
|
|
||||||
return dayjs(first).isAfter(second) ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-notifications',
|
selector: 'redaction-notifications',
|
||||||
templateUrl: './notifications.component.html',
|
templateUrl: './notifications.component.html',
|
||||||
styleUrls: ['./notifications.component.scss'],
|
styleUrls: ['./notifications.component.scss'],
|
||||||
standalone: false,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class NotificationsComponent {
|
export class NotificationsComponent {
|
||||||
readonly hasUnreadNotifications$: Observable<boolean>;
|
readonly hasUnreadNotifications$: Observable<boolean>;
|
||||||
readonly groupedNotifications$: Observable<NotificationsGroup[]>;
|
readonly groupedNotifications$: Observable<NotificationsGroup[]>;
|
||||||
readonly trackBy = trackByFactory();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _notificationsService: NotificationsService,
|
private readonly _notificationsService: NotificationsService,
|
||||||
private readonly _datePipe: DatePipe,
|
private readonly _datePipe: DatePipe,
|
||||||
private readonly _translateService: TranslateService,
|
private readonly _dossiersCacheService: DossiersCacheService,
|
||||||
) {
|
) {
|
||||||
this.groupedNotifications$ = this._notificationsService.all$.pipe(map(notifications => this.#groupNotifications(notifications)));
|
this.groupedNotifications$ = this._notificationsService.all$.pipe(map(notifications => this._groupNotifications(notifications)));
|
||||||
this.hasUnreadNotifications$ = this.#hasUnreadNotifications$;
|
this.hasUnreadNotifications$ = this._hasUnreadNotifications$;
|
||||||
}
|
}
|
||||||
|
|
||||||
get #hasUnreadNotifications$(): Observable<boolean> {
|
private get _hasUnreadNotifications$(): Observable<boolean> {
|
||||||
return this._notificationsService.all$.pipe(
|
return this._notificationsService.all$.pipe(
|
||||||
map(notifications => notifications.filter(n => !n.readDate).length > 0),
|
map(notifications => notifications.filter(n => !n.readDate).length > 0),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
@ -49,41 +39,31 @@ export class NotificationsComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async markRead(notifications: Notification[] = this._notificationsService.all, isRead = true): Promise<void> {
|
async markRead($event, notifications: Notification[] = this._notificationsService.all, isRead = true): Promise<void> {
|
||||||
|
$event.stopPropagation();
|
||||||
if (!notifications.find(notification => !!notification.readDate !== isRead)) {
|
if (!notifications.find(notification => !!notification.readDate !== isRead)) {
|
||||||
// If no notification changes status after the request, abort
|
// If no notification changes status after the request, abort
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await firstValueFrom(
|
||||||
const notificationsIds = notifications.map(n => n.id);
|
this._notificationsService.toggleNotificationRead(
|
||||||
await this._notificationsService.toggleNotificationRead(notificationsIds, isRead);
|
notifications.map(n => n.id),
|
||||||
|
isRead,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleMarkReadEvent(event: MouseEvent, notifications: Notification[] = this._notificationsService.all, isRead = true) {
|
private _groupNotifications(notifications: Notification[]): NotificationsGroup[] {
|
||||||
if (!(event.target as HTMLBaseElement).href) {
|
const res = {};
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
for (const notification of notifications) {
|
||||||
|
const date = this._datePipe.transform(notification.creationDate, 'sophisticatedDate');
|
||||||
|
if (!res[date]) {
|
||||||
|
res[date] = [];
|
||||||
|
}
|
||||||
|
res[date].push(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.markRead(notifications, isRead);
|
return Object.keys(res).map(key => ({ date: key, notifications: res[key] }));
|
||||||
}
|
|
||||||
|
|
||||||
#groupNotifications(notifications: Notification[]): NotificationsGroup[] {
|
|
||||||
const todayTranslation = this._translateService.instant('today');
|
|
||||||
const groupedMap = notifications.groupBy(n => {
|
|
||||||
if (isOlderThan(n.creationDate, 2)) {
|
|
||||||
return dayjs(n.creationDate).year().toString();
|
|
||||||
} else if (isOlderThan(n.creationDate, 1)) {
|
|
||||||
return dayjs(n.creationDate).format('YYYY-MM');
|
|
||||||
}
|
|
||||||
|
|
||||||
return n.creationDate.split('T')[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortedGroups = [...groupedMap.entries()].sort(([aDate], [bDate]) => chronologically(aDate, bDate));
|
|
||||||
return sortedGroups.map(([date, _notifications]) => ({
|
|
||||||
date: isToday(date) ? todayTranslation : this._datePipe.transform(date, 'sophisticatedDate'),
|
|
||||||
notifications: [..._notifications].sort((a, b) => chronologically(a.creationDate, b.creationDate)),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
width: 1000px;
|
width: 900px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|||||||
templateUrl: './dashboard-skeleton.component.html',
|
templateUrl: './dashboard-skeleton.component.html',
|
||||||
styleUrls: ['./dashboard-skeleton.component.scss'],
|
styleUrls: ['./dashboard-skeleton.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class DashboardSkeletonComponent {}
|
export class DashboardSkeletonComponent {}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } fr
|
|||||||
templateUrl: './dossier-skeleton.component.html',
|
templateUrl: './dossier-skeleton.component.html',
|
||||||
styleUrls: ['./dossier-skeleton.component.scss'],
|
styleUrls: ['./dossier-skeleton.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class DossierSkeletonComponent implements OnInit {
|
export class DossierSkeletonComponent implements OnInit {
|
||||||
@ViewChild('workload1', { static: true }) workload1: TemplateRef<unknown>;
|
@ViewChild('workload1', { static: true }) workload1: TemplateRef<unknown>;
|
||||||
|
|||||||
@ -5,6 +5,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|||||||
templateUrl: './skeleton-stats.component.html',
|
templateUrl: './skeleton-stats.component.html',
|
||||||
styleUrls: ['./skeleton-stats.component.scss'],
|
styleUrls: ['./skeleton-stats.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class SkeletonStatsComponent {}
|
export class SkeletonStatsComponent {}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<a class="logo">
|
<a class="logo">
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<iqser-logo icon="iqser:logo"></iqser-logo>
|
<iqser-logo icon="red:logo"></iqser-logo>
|
||||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { Title } from '@angular/platform-browser';
|
|||||||
templateUrl: './skeleton-top-bar.component.html',
|
templateUrl: './skeleton-top-bar.component.html',
|
||||||
styleUrls: ['./skeleton-top-bar.component.scss'],
|
styleUrls: ['./skeleton-top-bar.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class SkeletonTopBarComponent {
|
export class SkeletonTopBarComponent {
|
||||||
constructor(readonly titleService: Title) {}
|
constructor(readonly titleService: Title) {}
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep.search-menu .mat-mdc-menu-content {
|
::ng-deep.search-menu .mat-menu-content {
|
||||||
padding: 8px !important;
|
padding: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,13 @@ import { ChangeDetectionStrategy, Component, HostListener, Input, ViewChild } fr
|
|||||||
import { debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||||
import { MatMenuTrigger } from '@angular/material/menu';
|
import { MatLegacyMenuTrigger as MatMenuTrigger } from '@angular/material/legacy-menu';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-spotlight-search [actions]',
|
selector: 'redaction-spotlight-search [actions]',
|
||||||
templateUrl: './spotlight-search.component.html',
|
templateUrl: './spotlight-search.component.html',
|
||||||
styleUrls: ['./spotlight-search.component.scss'],
|
styleUrls: ['./spotlight-search.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class SpotlightSearchComponent {
|
export class SpotlightSearchComponent {
|
||||||
@Input() actions: readonly SpotlightSearchAction[];
|
@Input() actions: readonly SpotlightSearchAction[];
|
||||||
@ -43,20 +42,20 @@ export class SpotlightSearchComponent {
|
|||||||
this.shownActions[this._currentActionIdx].action(this.valueChanges$.getValue());
|
this.shownActions[this._currentActionIdx].action(this.valueChanges$.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keydown.arrowDown', ['$event'])
|
@HostListener('document:keydown.arrowDown', ['$event'])
|
||||||
@HostListener('keydown.arrowUp', ['$event'])
|
@HostListener('document:keydown.arrowUp', ['$event'])
|
||||||
handleKeyDown(event: KeyboardEvent): void {
|
handleKeyDown(event: KeyboardEvent): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keyup.arrowDown', ['$event'])
|
@HostListener('document:keyup.arrowDown', ['$event'])
|
||||||
handleKeyUpArrowDown(event: KeyboardEvent): void {
|
handleKeyUpArrowDown(event: KeyboardEvent): void {
|
||||||
this.handleKeyDown(event);
|
this.handleKeyDown(event);
|
||||||
const index = this._currentActionIdx + 1;
|
const index = this._currentActionIdx + 1;
|
||||||
this._currentActionIdx$.next(index >= this.shownActions.length ? 0 : index);
|
this._currentActionIdx$.next(index >= this.shownActions.length ? 0 : index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('keyup.arrowUp', ['$event'])
|
@HostListener('document:keyup.arrowUp', ['$event'])
|
||||||
handleKeyUpArrowUp(event: KeyboardEvent): void {
|
handleKeyUpArrowUp(event: KeyboardEvent): void {
|
||||||
this.handleKeyDown(event);
|
this.handleKeyDown(event);
|
||||||
const index = this._currentActionIdx - 1;
|
const index = this._currentActionIdx - 1;
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
<div id="tenants-menu-items">
|
|
||||||
<a
|
|
||||||
(click)="!stored.isCurrent && switchTenant(stored.tenantId)"
|
|
||||||
*ngFor="let stored of storedTenants; trackBy: trackBy"
|
|
||||||
[class.disabled]="stored.isCurrent"
|
|
||||||
[class.reverse-icon]="stored.isCurrent"
|
|
||||||
[id]="stored.tenantId"
|
|
||||||
[iqserStopPropagation]="stored.isCurrent"
|
|
||||||
class="little-padding"
|
|
||||||
mat-menu-item
|
|
||||||
>
|
|
||||||
{{ stored.tenantId }}
|
|
||||||
<mat-icon *ngIf="stored.isCurrent" [svgIcon]="'iqser:check'"></mat-icon>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<mat-divider class="pb-3 pt-3"></mat-divider>
|
|
||||||
|
|
||||||
<a (click)="switchTenant()" mat-menu-item>
|
|
||||||
<span translate="top-bar.navigation-items.my-account.children.join-another-tenant"> </span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-family: var(--iqser-font-family);
|
|
||||||
font-size: var(--iqser-font-size);
|
|
||||||
background-color: var(--iqser-background);
|
|
||||||
color: var(--iqser-disabled);
|
|
||||||
padding-left: 17px;
|
|
||||||
padding-top: 5px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled {
|
|
||||||
background-color: var(--iqser-grey-6);
|
|
||||||
color: var(--iqser-grey-3);
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.little-padding {
|
|
||||||
padding: 0 8px 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reverse-icon {
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { IStoredTenantId, KeycloakStatusService, TenantsService } from '@iqser/common-ui/lib/tenants';
|
|
||||||
|
|
||||||
interface ITenant extends IStoredTenantId {
|
|
||||||
readonly isCurrent: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-tenants-menu',
|
|
||||||
templateUrl: './tenants-menu.component.html',
|
|
||||||
styleUrls: ['./tenants-menu.component.scss'],
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class TenantsMenuComponent {
|
|
||||||
readonly tenantsService = inject(TenantsService);
|
|
||||||
readonly storedTenants: ITenant[];
|
|
||||||
readonly #keycloakStatusService = inject(KeycloakStatusService);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.storedTenants = this.#getStoredTenants();
|
|
||||||
}
|
|
||||||
|
|
||||||
trackBy(_index: number, item: ITenant) {
|
|
||||||
return item.tenantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
async switchTenant(tenantId?: string) {
|
|
||||||
await this.#keycloakStatusService.switchTenant(tenantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
#getStoredTenants(): ITenant[] {
|
|
||||||
const storedTenants = this.tenantsService.getStoredTenants();
|
|
||||||
const sorted = storedTenants.sort((a, b) => a.tenantId.localeCompare(b.tenantId));
|
|
||||||
return sorted.map(tenant => ({ ...tenant, isCurrent: tenant.tenantId === this.tenantsService.activeTenantId }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
<div id="user-menu-items">
|
|
||||||
<ng-container *ngFor="let item of userMenuItems; trackBy: trackBy">
|
|
||||||
<a (click)="item.action?.()" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink" mat-menu-item>
|
|
||||||
{{ item.name | translate }}
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<a [id]="'switch-accounts'" [matMenuTriggerFor]="tenantsMenu" mat-menu-item>
|
|
||||||
{{ 'top-bar.navigation-items.my-account.children.select-tenant' | translate }}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<button (click)="userService.logout()" id="logout" mat-menu-item>
|
|
||||||
<mat-icon svgIcon="iqser:logout"></mat-icon>
|
|
||||||
<span translate="top-bar.navigation-items.my-account.children.logout"> </span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-menu #tenantsMenu="matMenu" xPosition="before">
|
|
||||||
<ng-template matMenuContent>
|
|
||||||
<app-tenants-menu></app-tenants-menu>
|
|
||||||
</ng-template>
|
|
||||||
</mat-menu>
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { Roles } from '@users/roles';
|
|
||||||
import { IqserPermissionsService } from '@iqser/common-ui';
|
|
||||||
import { User } from '@red/domain';
|
|
||||||
import { UserService } from '@users/user.service';
|
|
||||||
import { getCurrentUser } from '@iqser/common-ui/lib/users';
|
|
||||||
import { List } from '@iqser/common-ui/lib/utils';
|
|
||||||
|
|
||||||
interface MenuItem {
|
|
||||||
readonly id: string;
|
|
||||||
readonly name: string;
|
|
||||||
readonly routerLink?: string;
|
|
||||||
readonly show: boolean;
|
|
||||||
readonly action?: () => void;
|
|
||||||
readonly showDot?: () => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-user-menu',
|
|
||||||
templateUrl: './user-menu.component.html',
|
|
||||||
styleUrls: ['./user-menu.component.scss'],
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class UserMenuComponent {
|
|
||||||
readonly currentUser = getCurrentUser<User>();
|
|
||||||
readonly userService = inject(UserService);
|
|
||||||
readonly #permissionsService = inject(IqserPermissionsService);
|
|
||||||
readonly userMenuItems: List<MenuItem> = [
|
|
||||||
{
|
|
||||||
id: 'account',
|
|
||||||
name: _('top-bar.navigation-items.my-account.children.account'),
|
|
||||||
routerLink: '/main/account',
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'admin',
|
|
||||||
name: _('top-bar.navigation-items.my-account.children.admin'),
|
|
||||||
routerLink: '/main/admin',
|
|
||||||
show: (this.currentUser.isManager || this.currentUser.isUserAdmin) && this.#permissionsService.has([Roles.templates.read]),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'downloads',
|
|
||||||
name: _('top-bar.navigation-items.my-account.children.downloads'),
|
|
||||||
routerLink: '/main/downloads',
|
|
||||||
show: this.currentUser.isUser && this.#permissionsService.has(Roles.readDownloadStatus),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'trash',
|
|
||||||
name: _('top-bar.navigation-items.my-account.children.trash'),
|
|
||||||
routerLink: '/main/trash',
|
|
||||||
show: this.currentUser.isUser && this.#permissionsService.has([Roles.dossiers.read, Roles.files.readStatus]),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
trackBy(_index: number, item: MenuItem) {
|
|
||||||
return item.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { CanDeactivate } from '@angular/router';
|
import { CanDeactivate } from '@angular/router';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { map } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
import { ConfirmationDialogService, ConfirmOptions } from '@iqser/common-ui';
|
import { ConfirmationDialogService, ConfirmOptions } from '@iqser/common-ui';
|
||||||
|
|
||||||
export interface ComponentCanDeactivate {
|
export interface ComponentCanDeactivate {
|
||||||
@ -15,24 +15,23 @@ export interface ComponentCanDeactivate {
|
|||||||
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
|
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
|
||||||
constructor(private _dialogService: ConfirmationDialogService) {}
|
constructor(private _dialogService: ConfirmationDialogService) {}
|
||||||
|
|
||||||
canDeactivate(component: ComponentCanDeactivate) {
|
canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
|
||||||
if (!component.changed) {
|
if (component.changed) {
|
||||||
return true;
|
component.isLeavingPage = true;
|
||||||
|
|
||||||
|
const dialogRef = this._dialogService.openDialog({ disableConfirm: component.valid === false });
|
||||||
|
return dialogRef.afterClosed().pipe(
|
||||||
|
map(result => {
|
||||||
|
if (result === ConfirmOptions.CONFIRM) {
|
||||||
|
component.save().then();
|
||||||
|
} else {
|
||||||
|
component.discard?.().then();
|
||||||
|
}
|
||||||
|
component.isLeavingPage = false;
|
||||||
|
return !!result;
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
component.isLeavingPage = true;
|
|
||||||
|
|
||||||
const dialogRef = this._dialogService.open({ disableConfirm: component.valid === false });
|
|
||||||
return dialogRef.afterClosed().pipe(
|
|
||||||
map(result => {
|
|
||||||
if (result === ConfirmOptions.CONFIRM) {
|
|
||||||
component.save().then();
|
|
||||||
} else {
|
|
||||||
component.discard?.().then();
|
|
||||||
}
|
|
||||||
component.isLeavingPage = false;
|
|
||||||
return !!result;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CanActivate } from '@angular/router';
|
import { CanActivate } from '@angular/router';
|
||||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||||
|
|
||||||
// TODO: Remove this and use a CanActivateFn instead
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class DashboardGuard implements CanActivate {
|
export class DashboardGuard implements CanActivate {
|
||||||
constructor(private readonly _dashboardStatsService: DashboardStatsService) {}
|
constructor(private readonly _dashboardStatsService: DashboardStatsService) {}
|
||||||
|
|||||||
@ -1,24 +1,19 @@
|
|||||||
import { Injectable, Injector, ProviderToken } from '@angular/core';
|
import { Injectable, Injector, ProviderToken } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||||
import { getConfig } from '@iqser/common-ui';
|
|
||||||
import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
|
||||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
|
||||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
|
||||||
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
|
|
||||||
import { FilesMapService } from '@services/files/files-map.service';
|
import { FilesMapService } from '@services/files/files-map.service';
|
||||||
import { FilesService } from '@services/files/files.service';
|
import { FilesService } from '@services/files/files.service';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||||
|
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||||
|
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class DossierFilesGuard implements CanActivate {
|
export class DossierFilesGuard implements CanActivate {
|
||||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _injector: Injector,
|
private readonly _injector: Injector,
|
||||||
private readonly _filesMapService: FilesMapService,
|
private readonly _filesMapService: FilesMapService,
|
||||||
private readonly _filesService: FilesService,
|
private readonly _filesService: FilesService,
|
||||||
private readonly _dictionaryService: DictionaryService,
|
private readonly _dictionaryService: DictionaryService,
|
||||||
private readonly _dictionaryMapService: DossierDictionariesMapService,
|
|
||||||
private readonly _router: Router,
|
private readonly _router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -46,10 +41,8 @@ export class DossierFilesGuard implements CanActivate {
|
|||||||
async loadDossierData(dossierId: string, dossierTemplateId: string) {
|
async loadDossierData(dossierId: string, dossierTemplateId: string) {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
if (!this._dictionaryMapService.has(dossierId) && !this.isDocumine) {
|
const dictionary$ = this._dictionaryService.loadDossierDictionary(dossierTemplateId, dossierId);
|
||||||
const dictionaryPromise = firstValueFrom(this._dictionaryService.loadDictionaryDataForDossier(dossierTemplateId, dossierId));
|
promises.push(firstValueFrom(dictionary$));
|
||||||
promises.push(dictionaryPromise);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._filesMapService.has(dossierId)) {
|
if (!this._filesMapService.has(dossierId)) {
|
||||||
promises.push(firstValueFrom(this._filesService.loadAll(dossierId)));
|
promises.push(firstValueFrom(this._filesService.loadAll(dossierId)));
|
||||||
|
|||||||
@ -1,82 +1,35 @@
|
|||||||
import { inject } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||||
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
|
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||||
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
|
|
||||||
import { NGXLogger } from 'ngx-logger';
|
|
||||||
import { firstValueFrom } from 'rxjs';
|
|
||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
|
||||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
|
||||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
|
||||||
import { WatermarkService } from '@services/entity-services/watermark.service';
|
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
|
||||||
import { getConfig, Toaster } from '@iqser/common-ui';
|
|
||||||
import { RulesService } from '../modules/admin/services/rules.service';
|
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
|
|
||||||
export function templateExistsWhenEnteringAdmin(): CanActivateFn {
|
@Injectable({ providedIn: 'root' })
|
||||||
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
|
export class DossierTemplateExistsGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private readonly _dashboardStatsService: DashboardStatsService,
|
||||||
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
|
private readonly _router: Router,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||||
const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||||
const fileAttributesService = inject(FileAttributesService);
|
const dossiersListView = !route.pathFromRoot.find(r => r.routeConfig?.path === 'admin');
|
||||||
const dictionaryService = inject(DictionaryService);
|
|
||||||
const defaultColorsService = inject(DefaultColorsService);
|
|
||||||
const watermarksService = inject(WatermarkService);
|
|
||||||
const router = inject(Router);
|
|
||||||
const rulesService = inject(RulesService);
|
|
||||||
const isDocumine = getConfig().IS_DOCUMINE;
|
|
||||||
|
|
||||||
const dossierTemplate = inject(DossierTemplateStatsService).get(dossierTemplateId);
|
if (dossiersListView) {
|
||||||
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
|
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
|
||||||
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
|
if (!dossierTemplateStats || dossierTemplateStats.isEmpty) {
|
||||||
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
|
await this._router.navigate(['']);
|
||||||
await firstValueFrom(rulesService.getFor(dossierTemplateId));
|
return false;
|
||||||
if (!isDocumine) {
|
}
|
||||||
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
|
} else {
|
||||||
}
|
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
||||||
if (!dossierTemplate) {
|
if (!dossierTemplate) {
|
||||||
await router.navigate(['main', 'admin', 'dossier-templates']);
|
await this._router.navigate(['main', 'admin', 'dossier-templates']);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export function templateExistsWhenEnteringDossierList(): CanActivateFn {
|
|
||||||
return async function (route: ActivatedRouteSnapshot) {
|
|
||||||
const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
|
||||||
const dashboardStatsService = inject(DashboardStatsService);
|
|
||||||
const dossierTemplatesService = inject(DossierTemplatesService);
|
|
||||||
const logger = inject(NGXLogger);
|
|
||||||
const router = inject(Router);
|
|
||||||
const userPreferencesService = inject(UserPreferenceService);
|
|
||||||
const fileAttributesService = inject(FileAttributesService);
|
|
||||||
const dictionaryService = inject(DictionaryService);
|
|
||||||
const defaultColorsService = inject(DefaultColorsService);
|
|
||||||
const watermarksService = inject(WatermarkService);
|
|
||||||
const rulesService = inject(RulesService);
|
|
||||||
const toaster = inject(Toaster);
|
|
||||||
const isDocumine = getConfig().IS_DOCUMINE;
|
|
||||||
|
|
||||||
await firstValueFrom(dashboardStatsService.loadForTemplate(dossierTemplateId));
|
|
||||||
await firstValueFrom(dossierTemplatesService.loadDossierTemplate(dossierTemplateId));
|
|
||||||
const dossierTemplateStats = dashboardStatsService.find(dossierTemplateId);
|
|
||||||
if (!dossierTemplateStats || dossierTemplateStats.isEmpty) {
|
|
||||||
logger.warn(`[ROUTES] Dossier template ${dossierTemplateId} not found, redirecting to main`);
|
|
||||||
await userPreferencesService.saveLastDossierTemplate(null);
|
|
||||||
await router.navigate(['main']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
|
|
||||||
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
|
|
||||||
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
|
|
||||||
const rules = await firstValueFrom(rulesService.getFor(dossierTemplateId));
|
|
||||||
if (rules.timeoutDetected) {
|
|
||||||
toaster.error(_('dossier-listing.rules.timeoutError'));
|
|
||||||
}
|
|
||||||
if (!isDocumine) {
|
|
||||||
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,57 +1,43 @@
|
|||||||
import { inject } from '@angular/core';
|
import { Injectable, Injector, ProviderToken } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom, forkJoin } from 'rxjs';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||||
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
||||||
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
|
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||||
|
import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||||
import { TenantsService } from '@iqser/common-ui/lib/tenants';
|
|
||||||
import { NGXLogger } from 'ngx-logger';
|
|
||||||
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from '../tokens';
|
|
||||||
|
|
||||||
export function loadAllDossiersGuard(): CanActivateFn {
|
@Injectable({ providedIn: 'root' })
|
||||||
return async () => {
|
export class DossiersGuard implements CanActivate {
|
||||||
const logger = inject(NGXLogger);
|
constructor(
|
||||||
logger.info('[GUARDS] loadAllDossiersGuard start');
|
private readonly _injector: Injector,
|
||||||
|
private readonly _router: Router,
|
||||||
|
private readonly _dashboardStatsService: DashboardStatsService,
|
||||||
|
private readonly _activeDossiersService: ActiveDossiersService,
|
||||||
|
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||||
|
) {}
|
||||||
|
|
||||||
const services = [inject(ArchivedDossiersService), inject(ActiveDossiersService)];
|
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||||
const requests = services.map(service => firstValueFrom(service.loadAll()));
|
const token: ProviderToken<DossiersService> = route.data.dossiersService;
|
||||||
await Promise.all(requests);
|
if (!token) {
|
||||||
|
const services = [this._archivedDossiersService, this._activeDossiersService];
|
||||||
|
const loading$ = forkJoin(services.map(service => service.loadAll().pipe(take(1))));
|
||||||
|
await firstValueFrom(loading$);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('[GUARDS] loadAllDossiersGuard end');
|
const dossiersService: DossiersService = this._injector.get<DossiersService>(token);
|
||||||
return true;
|
const isArchive = dossiersService.routerPath === ARCHIVE_ROUTE;
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadActiveDossiersGuard(): CanActivateFn {
|
|
||||||
return async () => {
|
|
||||||
const logger = inject(NGXLogger);
|
|
||||||
logger.info('[GUARDS] loadDossiersGuard start');
|
|
||||||
|
|
||||||
await firstValueFrom(inject<ActiveDossiersService>(ACTIVE_DOSSIERS_SERVICE).loadAll());
|
|
||||||
|
|
||||||
logger.info('[GUARDS] loadDossiersGuard end');
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadArchivedDossiersGuard(): CanActivateFn {
|
|
||||||
return async (route: ActivatedRouteSnapshot) => {
|
|
||||||
const logger = inject(NGXLogger);
|
|
||||||
logger.info('[GUARDS] loadArchivedDossiersGuard start');
|
|
||||||
|
|
||||||
const dossiersService = inject<ArchivedDossiersService>(ARCHIVED_DOSSIERS_SERVICE);
|
|
||||||
const dossierTemplateId = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
const dossierTemplateId = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||||
const dossierTemplateStats = inject(DashboardStatsService).find(dossierTemplateId);
|
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
|
||||||
|
|
||||||
if (dossierTemplateStats?.numberOfArchivedDossiers === 0) {
|
if (isArchive && dossierTemplateStats?.numberOfArchivedDossiers === 0) {
|
||||||
logger.info('[GUARDS] loadArchivedDossiersGuard no archived dossiers, redirect to active dossiers page');
|
await this._router.navigate(['main', dossierTemplateId, 'dossiers']);
|
||||||
await inject(Router).navigate([inject(TenantsService).activeTenantId, 'main', dossierTemplateId, 'dossiers']);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await firstValueFrom(dossiersService.loadAll());
|
await firstValueFrom(dossiersService.loadAll());
|
||||||
logger.info('[GUARDS] loadArchivedDossiersGuard end');
|
|
||||||
return true;
|
return true;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,27 @@
|
|||||||
import { inject } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
||||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||||
import { TenantsService } from '@iqser/common-ui/lib/tenants';
|
|
||||||
|
|
||||||
export function entityExistsGuard(): CanActivateFn {
|
@Injectable({ providedIn: 'root' })
|
||||||
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
|
export class EntityExistsGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private readonly _dictionariesMapService: DictionariesMapService,
|
||||||
|
private readonly _router: Router,
|
||||||
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||||
const dossierTemplateId = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
const dossierTemplateId = route.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||||
const type = route.paramMap.get(ENTITY_TYPE);
|
const type = route.paramMap.get(ENTITY_TYPE);
|
||||||
|
|
||||||
if (!inject(DictionariesMapService).get(dossierTemplateId, type)) {
|
if (!this._dictionariesMapService.get(dossierTemplateId, type)) {
|
||||||
const dossierTemplate = inject(DossierTemplatesService).find(dossierTemplateId);
|
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
||||||
await inject(Router).navigate([`/${inject(TenantsService).activeTenantId}/${dossierTemplate.routerLink}/entities`]);
|
await this._router.navigate([`${dossierTemplate.routerLink}/entities`]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { CanDeactivateFn } from '@angular/router';
|
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
|
||||||
import DossierOverviewScreenComponent from '../modules/dossier-overview/screen/dossier-overview-screen.component';
|
|
||||||
|
|
||||||
export const isNotEditingFileAttributeGuard: CanDeactivateFn<DossierOverviewScreenComponent> = () =>
|
|
||||||
!inject(FileAttributesService).isEditingFileAttribute();
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ActivatedRouteSnapshot, Router } from '@angular/router';
|
|
||||||
import { GET_TENANT_FROM_PATH_FN } from '@common-ui/utils';
|
|
||||||
import { AsyncGuard } from '@iqser/common-ui';
|
|
||||||
import { keycloakInitializer, KeycloakStatusService, TenantsService } from '@iqser/common-ui/lib/tenants';
|
|
||||||
import { LicenseService } from '@services/license.service';
|
|
||||||
import { UserService } from '@users/user.service';
|
|
||||||
import { jwtDecode } from 'jwt-decode';
|
|
||||||
import { KeycloakService } from 'keycloak-angular';
|
|
||||||
import { NGXLogger } from 'ngx-logger';
|
|
||||||
|
|
||||||
export interface JwtToken {
|
|
||||||
auth_time: number;
|
|
||||||
iat: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ifLoggedIn(): AsyncGuard {
|
|
||||||
return async (route: ActivatedRouteSnapshot) => {
|
|
||||||
const logger = inject(NGXLogger);
|
|
||||||
logger.info('[ROUTES] Check if can activate route', route);
|
|
||||||
|
|
||||||
const tenantsService = inject(TenantsService);
|
|
||||||
const keycloakService = inject(KeycloakService);
|
|
||||||
const usersService = inject(UserService);
|
|
||||||
const licenseService = inject(LicenseService);
|
|
||||||
const keycloakStatusService = inject(KeycloakStatusService);
|
|
||||||
|
|
||||||
const tenant = inject(GET_TENANT_FROM_PATH_FN)();
|
|
||||||
const keycloakInstance = keycloakService.getKeycloakInstance();
|
|
||||||
|
|
||||||
const queryParams = new URLSearchParams(window.location.search);
|
|
||||||
const username = queryParams.get('username');
|
|
||||||
const router = inject(Router);
|
|
||||||
|
|
||||||
if (!keycloakInstance) {
|
|
||||||
if (!tenant) {
|
|
||||||
logger.error('[ROUTES] No tenant found, something is wrong...');
|
|
||||||
return router.navigate(['/']);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('[KEYCLOAK] Keycloak init...');
|
|
||||||
await keycloakInitializer(tenant);
|
|
||||||
logger.info('[KEYCLOAK] Keycloak init done for tenant: ', { tenant });
|
|
||||||
await tenantsService.selectTenant(tenant);
|
|
||||||
await usersService.initialize();
|
|
||||||
await licenseService.loadLicenses();
|
|
||||||
|
|
||||||
const token = await keycloakService.getToken();
|
|
||||||
if (token) {
|
|
||||||
const jwtToken = jwtDecode(token) as JwtToken;
|
|
||||||
const authTime = (jwtToken.auth_time || jwtToken.iat).toString();
|
|
||||||
localStorage.setItem('authTime', authTime);
|
|
||||||
localStorage.setItem('token', token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keycloakService.isLoggedIn()) {
|
|
||||||
logger.info('[ROUTES] Is logged in, continuing');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.warn('[ROUTES] Redirect to login');
|
|
||||||
keycloakStatusService.createLoginUrlAndExecute(username);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
|
||||||
import { EntityPermissionsService } from '@services/entity-permissions/entity-permissions.service';
|
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { EntityPermissionsService } from '@services/entity-permissions/entity-permissions.service';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class PermissionsGuard implements CanActivate {
|
export class PermissionsGuard implements CanActivate {
|
||||||
|
|||||||
@ -1,32 +1,14 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CanActivate, CanActivateFn } from '@angular/router';
|
import { CanActivate } from '@angular/router';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { TrashService } from '@services/entity-services/trash.service';
|
import { TrashService } from '@services/entity-services/trash.service';
|
||||||
import { SystemPreferencesService } from '@services/system-preferences.service';
|
|
||||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
|
||||||
import { ACTIVE_DOSSIERS_SERVICE } from '../tokens';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TrashGuard implements CanActivate {
|
export class TrashGuard implements CanActivate {
|
||||||
constructor(
|
constructor(private readonly _trashService: TrashService) {}
|
||||||
private readonly _trashService: TrashService,
|
|
||||||
private readonly _systemPreferences: SystemPreferencesService,
|
|
||||||
private readonly _activeDossierService: ActiveDossiersService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async canActivate(): Promise<boolean> {
|
async canActivate(): Promise<boolean> {
|
||||||
await this._systemPreferences.loadPreferencesIfNeeded();
|
|
||||||
await firstValueFrom(this._activeDossierService.loadAll());
|
|
||||||
await firstValueFrom(this._trashService.loadAll());
|
await firstValueFrom(this._trashService.loadAll());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function trashGuard(): CanActivateFn {
|
|
||||||
return async () => {
|
|
||||||
await inject(SystemPreferencesService).loadPreferencesIfNeeded();
|
|
||||||
await firstValueFrom(inject<ActiveDossiersService>(ACTIVE_DOSSIERS_SERVICE).loadAll());
|
|
||||||
await firstValueFrom(inject(TrashService).loadAll());
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,26 +1,21 @@
|
|||||||
import { inject } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||||
import { WatermarksMapService } from '@services/entity-services/watermarks-map.service';
|
import { WatermarksMapService } from '@services/entity-services/watermarks-map.service';
|
||||||
import { DOSSIER_TEMPLATE_ID, WATERMARK_ID } from '@red/domain';
|
import { DOSSIER_TEMPLATE_ID, WATERMARK_ID } from '@red/domain';
|
||||||
import { TenantsService } from '@iqser/common-ui/lib/tenants';
|
|
||||||
|
|
||||||
export function watermarkExistsGuard(): CanActivateFn {
|
@Injectable({ providedIn: 'root' })
|
||||||
return async function (route: ActivatedRouteSnapshot) {
|
export class WatermarkExistsGuard implements CanActivate {
|
||||||
|
constructor(private readonly _watermarksMapService: WatermarksMapService, private readonly _router: Router) {}
|
||||||
|
|
||||||
|
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||||
const dossierTemplateId = route.parent.paramMap.get(DOSSIER_TEMPLATE_ID);
|
const dossierTemplateId = route.parent.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||||
const watermarkId = Number(route.paramMap.get(WATERMARK_ID));
|
const watermarkId = Number(route.paramMap.get(WATERMARK_ID));
|
||||||
|
|
||||||
if (inject(WatermarksMapService).get(dossierTemplateId, watermarkId)) {
|
if (this._watermarksMapService.get(dossierTemplateId, watermarkId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await inject(Router).navigate([
|
await this._router.navigate(['main', 'admin', 'dossier-templates', dossierTemplateId, 'watermarks']);
|
||||||
inject(TenantsService).activeTenantId,
|
|
||||||
'main',
|
|
||||||
'admin',
|
|
||||||
'dossier-templates',
|
|
||||||
dossierTemplateId,
|
|
||||||
'watermarks',
|
|
||||||
]);
|
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
|
||||||
import { Dictionary, SuperTypes } from '@red/domain';
|
|
||||||
|
|
||||||
export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) => !isApprover && annotation.pending;
|
|
||||||
|
|
||||||
export const canForceHint = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
|
|
||||||
canAddRedaction && annotation.isIgnoredHint && !annotation.pending;
|
|
||||||
|
|
||||||
export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
|
|
||||||
canAddRedaction &&
|
|
||||||
(annotation.isSkipped || (annotation.IMAGE_HINT && annotation.superType === SuperTypes.Hint)) &&
|
|
||||||
!annotation.isFalsePositive &&
|
|
||||||
!annotation.pending;
|
|
||||||
|
|
||||||
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
|
|
||||||
|
|
||||||
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
|
|
||||||
annotation.canBeMarkedAsFalsePositive && !annotation.hasBeenResizedLocally && annotationEntity?.hasDictionary;
|
|
||||||
|
|
||||||
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
|
|
||||||
canAddRedaction &&
|
|
||||||
(autoAnalysisDisabled || !annotation.pending) &&
|
|
||||||
(annotation.isRedacted || (annotation.isHint && !annotation.isImage));
|
|
||||||
|
|
||||||
export const canRemoveFromDictionary = (annotation: AnnotationWrapper, autoAnalysisDisabled: boolean) =>
|
|
||||||
(annotation.isModifyDictionary || annotation.engines.includes('DICTIONARY') || annotation.engines.includes('DOSSIER_DICTIONARY')) &&
|
|
||||||
(annotation.isRedacted || annotation.isSkipped || annotation.isHint || (annotation.isIgnoredHint && !annotation.isRuleBased)) &&
|
|
||||||
(autoAnalysisDisabled || !annotation.pending) &&
|
|
||||||
annotation.isDictBased;
|
|
||||||
|
|
||||||
export const canRemoveRedaction = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) =>
|
|
||||||
(!annotation.isIgnoredHint || !annotation.isRuleBased) &&
|
|
||||||
(permissions.canRemoveOnlyHere || permissions.canRemoveFromDictionary || permissions.canMarkAsFalsePositive);
|
|
||||||
|
|
||||||
export const canChangeLegalBasis = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
|
|
||||||
canAddRedaction && annotation.isRedacted && (autoAnalysisDisabled || !annotation.pending);
|
|
||||||
|
|
||||||
export const canRecategorizeAnnotation = (annotation: AnnotationWrapper, canRecategorize: boolean, autoAnalysisDisabled: boolean) =>
|
|
||||||
canRecategorize &&
|
|
||||||
(annotation.isImage || (annotation.isHint && !annotation.isRuleBased)) &&
|
|
||||||
(autoAnalysisDisabled || !annotation.pending);
|
|
||||||
|
|
||||||
export const canResizeAnnotation = (
|
|
||||||
annotation: AnnotationWrapper,
|
|
||||||
canAddRedaction: boolean,
|
|
||||||
autoAnalysisDisabled: boolean,
|
|
||||||
hasDictionary = false,
|
|
||||||
) =>
|
|
||||||
(canAddRedaction &&
|
|
||||||
!annotation.isSkipped &&
|
|
||||||
!annotation.isIgnoredHint &&
|
|
||||||
(autoAnalysisDisabled || !annotation.pending) &&
|
|
||||||
(annotation.isRedacted ||
|
|
||||||
annotation.isImage ||
|
|
||||||
(annotation.isHint && !annotation.isRuleBased) ||
|
|
||||||
(!annotation.isHint && hasDictionary && annotation.isRuleBased))) ||
|
|
||||||
annotation.isRecommendation;
|
|
||||||
|
|
||||||
export const canResizeInDictionary = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) =>
|
|
||||||
permissions.canResizeAnnotation &&
|
|
||||||
annotation.isModifyDictionary &&
|
|
||||||
!annotation.hasBeenResizedLocally &&
|
|
||||||
!(annotation.hasBeenForcedHint || annotation.hasBeenForcedRedaction);
|
|
||||||
|
|
||||||
export const canEditAnnotation = (annotation: AnnotationWrapper) => (annotation.isRedacted || annotation.isSkipped) && !annotation.isImage;
|
|
||||||
|
|
||||||
export const canEditHint = (annotation: AnnotationWrapper) =>
|
|
||||||
((annotation.isHint && !annotation.isRuleBased) || annotation.isIgnoredHint) && !annotation.isImage;
|
|
||||||
|
|
||||||
export const canEditImage = (annotation: AnnotationWrapper) => annotation.isImage;
|
|
||||||
|
|
||||||
export const canRevertChanges = (annotation: AnnotationWrapper) => annotation.hasRedactionChanges;
|
|
||||||
@ -1,109 +1,85 @@
|
|||||||
import { IqserPermissionsService } from '@iqser/common-ui';
|
|
||||||
import { Dictionary } from '@red/domain';
|
|
||||||
import { Roles } from '@users/roles';
|
|
||||||
import { isArray } from 'lodash-es';
|
|
||||||
import {
|
|
||||||
canAcceptRecommendation,
|
|
||||||
canChangeLegalBasis,
|
|
||||||
canEditAnnotation,
|
|
||||||
canEditHint,
|
|
||||||
canEditImage,
|
|
||||||
canForceHint,
|
|
||||||
canForceRedaction,
|
|
||||||
canMarkAsFalsePositive,
|
|
||||||
canRecategorizeAnnotation,
|
|
||||||
canRemoveFromDictionary,
|
|
||||||
canRemoveOnlyHere,
|
|
||||||
canRemoveRedaction,
|
|
||||||
canResizeAnnotation,
|
|
||||||
canResizeInDictionary,
|
|
||||||
canRevertChanges,
|
|
||||||
canUndo,
|
|
||||||
} from './annotation-permissions.utils';
|
|
||||||
import { AnnotationWrapper } from './annotation.wrapper';
|
import { AnnotationWrapper } from './annotation.wrapper';
|
||||||
|
import { Dictionary } from '@red/domain';
|
||||||
|
import { isArray } from 'lodash-es';
|
||||||
|
import { IqserPermissionsService } from '@iqser/common-ui';
|
||||||
|
import { ROLES } from '@users/roles';
|
||||||
|
|
||||||
export class AnnotationPermissions {
|
export class AnnotationPermissions {
|
||||||
canUndo = true;
|
canUndo = true;
|
||||||
canAcceptRecommendation = true;
|
canAcceptRecommendation = true;
|
||||||
canMarkAsFalsePositive = true;
|
canMarkAsFalsePositive = true;
|
||||||
canRemoveOnlyHere = true;
|
canRemoveOrSuggestToRemoveOnlyHere = true;
|
||||||
canRemoveFromDictionary = true;
|
canRemoveOrSuggestToRemoveFromDictionary = true;
|
||||||
canRemoveRedaction = true;
|
canAcceptSuggestion = true;
|
||||||
|
canRejectSuggestion = true;
|
||||||
canForceRedaction = true;
|
canForceRedaction = true;
|
||||||
canChangeLegalBasis = true;
|
canChangeLegalBasis = true;
|
||||||
canResizeAnnotation = true;
|
canResizeAnnotation = true;
|
||||||
canResizeInDictionary = true;
|
canRecategorizeImage = true;
|
||||||
canRecategorizeAnnotation = true;
|
|
||||||
canForceHint = true;
|
canForceHint = true;
|
||||||
canEditAnnotations = true;
|
|
||||||
canEditHints = true;
|
|
||||||
canEditImages = true;
|
|
||||||
canRevertChanges = true;
|
|
||||||
|
|
||||||
static forUser(
|
static forUser(
|
||||||
isApprover: boolean,
|
isApprover: boolean,
|
||||||
annotations: AnnotationWrapper | AnnotationWrapper[],
|
annotations: AnnotationWrapper | AnnotationWrapper[],
|
||||||
entities: Dictionary[],
|
entities: Dictionary[],
|
||||||
permissionsService: IqserPermissionsService,
|
permissionsService: IqserPermissionsService,
|
||||||
autoAnalysisDisabled: boolean,
|
|
||||||
) {
|
) {
|
||||||
if (!isArray(annotations)) {
|
if (!isArray(annotations)) {
|
||||||
annotations = [annotations];
|
annotations = [annotations];
|
||||||
}
|
}
|
||||||
|
|
||||||
const summedPermissions: AnnotationPermissions = new AnnotationPermissions();
|
const summedPermissions: AnnotationPermissions = new AnnotationPermissions();
|
||||||
const canAddRedaction = permissionsService.has(Roles.redactions.write);
|
const canAddRedaction = permissionsService.has(ROLES.redactions.write);
|
||||||
|
const canRequestRedaction = permissionsService.has(ROLES.redactions.request);
|
||||||
|
const canProcessManualRedaction = permissionsService.has(ROLES.redactions.processManualRequest);
|
||||||
|
const canAddOrRequestRedaction = canAddRedaction || canRequestRedaction;
|
||||||
|
|
||||||
for (const annotation of annotations) {
|
for (const annotation of annotations) {
|
||||||
const permissions: AnnotationPermissions = new AnnotationPermissions();
|
const permissions: AnnotationPermissions = new AnnotationPermissions();
|
||||||
|
|
||||||
|
permissions.canUndo = !isApprover && (annotation.isSuggestion || annotation.pending);
|
||||||
|
permissions.canAcceptSuggestion = isApprover && (annotation.isSuggestion || annotation.isDeclinedSuggestion);
|
||||||
|
if (annotation.isSuggestionAdd || annotation.isSuggestionRemoveDictionary) {
|
||||||
|
permissions.canAcceptSuggestion = permissions.canAcceptSuggestion && canProcessManualRedaction;
|
||||||
|
}
|
||||||
|
permissions.canRejectSuggestion = isApprover && annotation.isSuggestion;
|
||||||
|
if (annotation.isSuggestionAdd || annotation.isSuggestionRemoveDictionary) {
|
||||||
|
permissions.canRejectSuggestion = permissions.canRejectSuggestion && canProcessManualRedaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions.canForceHint = canAddOrRequestRedaction && annotation.isIgnoredHint && !annotation.pending;
|
||||||
|
permissions.canForceRedaction =
|
||||||
|
canAddOrRequestRedaction && annotation.isSkipped && !annotation.isFalsePositive && !annotation.pending;
|
||||||
|
permissions.canAcceptRecommendation = annotation.isRecommendation && !annotation.pending;
|
||||||
|
|
||||||
const annotationEntity = entities.find(entity => entity.type === annotation.type);
|
const annotationEntity = entities.find(entity => entity.type === annotation.type);
|
||||||
permissions.canUndo = canUndo(annotation, isApprover);
|
permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive && annotationEntity.hasDictionary;
|
||||||
permissions.canForceHint = canForceHint(annotation, canAddRedaction);
|
|
||||||
permissions.canForceRedaction = canForceRedaction(annotation, canAddRedaction);
|
permissions.canRemoveOrSuggestToRemoveOnlyHere =
|
||||||
permissions.canAcceptRecommendation = canAcceptRecommendation(annotation);
|
canAddOrRequestRedaction && !annotation.pending && (annotation.isRedacted || (annotation.isHint && !annotation.isImage));
|
||||||
permissions.canMarkAsFalsePositive = canMarkAsFalsePositive(annotation, annotationEntity);
|
permissions.canRemoveOrSuggestToRemoveFromDictionary =
|
||||||
permissions.canRemoveOnlyHere = canRemoveOnlyHere(annotation, canAddRedaction, autoAnalysisDisabled);
|
annotation.isModifyDictionary &&
|
||||||
permissions.canRemoveFromDictionary = canRemoveFromDictionary(annotation, autoAnalysisDisabled);
|
(annotation.isRedacted || annotation.isSkipped || annotation.isHint) &&
|
||||||
permissions.canRemoveRedaction = canRemoveRedaction(annotation, permissions);
|
!annotation.pending &&
|
||||||
permissions.canChangeLegalBasis = canChangeLegalBasis(annotation, canAddRedaction, autoAnalysisDisabled);
|
!annotation.hasBeenResized;
|
||||||
permissions.canRecategorizeAnnotation = canRecategorizeAnnotation(annotation, canAddRedaction, autoAnalysisDisabled);
|
|
||||||
permissions.canResizeAnnotation = canResizeAnnotation(
|
permissions.canChangeLegalBasis = canAddOrRequestRedaction && annotation.isRedacted && !annotation.pending;
|
||||||
annotation,
|
|
||||||
canAddRedaction,
|
permissions.canRecategorizeImage =
|
||||||
autoAnalysisDisabled,
|
((annotation.isImage && !annotation.isSuggestion) || annotation.isSuggestionRecategorizeImage) && !annotation.pending;
|
||||||
annotationEntity?.hasDictionary,
|
|
||||||
);
|
permissions.canResizeAnnotation =
|
||||||
permissions.canResizeInDictionary = canResizeInDictionary(annotation, permissions);
|
canAddOrRequestRedaction &&
|
||||||
permissions.canEditAnnotations = canEditAnnotation(annotation);
|
(((annotation.isRedacted || annotation.isImage) && !annotation.isSuggestion) ||
|
||||||
permissions.canEditHints = canEditHint(annotation);
|
annotation.isSuggestionResize ||
|
||||||
permissions.canEditImages = canEditImage(annotation);
|
annotation.isRecommendation) &&
|
||||||
permissions.canRevertChanges = canRevertChanges(annotation);
|
!annotation.pending;
|
||||||
|
|
||||||
summedPermissions._merge(permissions);
|
summedPermissions._merge(permissions);
|
||||||
}
|
}
|
||||||
return summedPermissions;
|
return summedPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
static reduce(permissions: AnnotationPermissions[]): AnnotationPermissions {
|
|
||||||
const result = new AnnotationPermissions();
|
|
||||||
result.canResizeAnnotation = permissions.length === 1 && permissions[0].canResizeAnnotation;
|
|
||||||
result.canResizeInDictionary = permissions.length === 1 && permissions[0].canResizeInDictionary;
|
|
||||||
result.canChangeLegalBasis = permissions.reduce((acc, next) => acc && next.canChangeLegalBasis, true);
|
|
||||||
result.canRecategorizeAnnotation = permissions.reduce((acc, next) => acc && next.canRecategorizeAnnotation, true);
|
|
||||||
result.canRemoveFromDictionary = permissions.reduce((acc, next) => acc && next.canRemoveFromDictionary, true);
|
|
||||||
result.canAcceptRecommendation = permissions.reduce((acc, next) => acc && next.canAcceptRecommendation, true);
|
|
||||||
result.canMarkAsFalsePositive = permissions.reduce((acc, next) => acc && next.canMarkAsFalsePositive, true);
|
|
||||||
result.canForceRedaction = permissions.reduce((acc, next) => acc && next.canForceRedaction, true);
|
|
||||||
result.canForceHint = permissions.reduce((acc, next) => acc && next.canForceHint, true);
|
|
||||||
result.canRemoveOnlyHere = permissions.reduce((acc, next) => acc && next.canRemoveOnlyHere, true);
|
|
||||||
result.canRemoveRedaction = permissions.reduce((acc, next) => acc && next.canRemoveRedaction, true);
|
|
||||||
result.canUndo = permissions.reduce((acc, next) => acc && next.canUndo, true);
|
|
||||||
result.canEditAnnotations = permissions.reduce((acc, next) => acc && next.canEditAnnotations, true);
|
|
||||||
result.canEditHints = permissions.reduce((acc, next) => acc && next.canEditHints, true);
|
|
||||||
result.canEditImages = permissions.reduce((acc, next) => acc && next.canEditImages, true);
|
|
||||||
result.canRevertChanges = permissions.reduce((acc, next) => acc && next.canRevertChanges, true);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _merge(permissions: AnnotationPermissions) {
|
private _merge(permissions: AnnotationPermissions) {
|
||||||
for (const key of Object.keys(this)) {
|
for (const key of Object.keys(this)) {
|
||||||
if (typeof this[key] === 'boolean') {
|
if (typeof this[key] === 'boolean') {
|
||||||
|
|||||||
@ -1,103 +1,82 @@
|
|||||||
|
import { annotationTypesTranslations, SuggestionAddFalsePositive } from '@translations/annotation-types-translations';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { IListable } from '@iqser/common-ui';
|
|
||||||
import {
|
import {
|
||||||
annotationDefaultColorConfig,
|
annotationDefaultColorConfig,
|
||||||
annotationEntityColorConfig,
|
annotationEntityColorConfig,
|
||||||
AnnotationIconType,
|
AnnotationIconType,
|
||||||
ChangeType,
|
|
||||||
DefaultColors,
|
DefaultColors,
|
||||||
Dictionary,
|
Dictionary,
|
||||||
Earmark,
|
Earmark,
|
||||||
EntityTypes,
|
|
||||||
EntryStates,
|
|
||||||
FalsePositiveSuperTypes,
|
FalsePositiveSuperTypes,
|
||||||
IEntityLogEntry,
|
IComment,
|
||||||
ILegalBasis,
|
IManualChange,
|
||||||
IPoint,
|
IPoint,
|
||||||
IRectangle,
|
IRectangle,
|
||||||
LogEntryEngine,
|
LogEntryStatuses,
|
||||||
LogEntryEngines,
|
|
||||||
LowLevelFilterTypes,
|
LowLevelFilterTypes,
|
||||||
ManualRedactionType,
|
|
||||||
ManualRedactionTypes,
|
ManualRedactionTypes,
|
||||||
|
SuggestionAddSuperTypes,
|
||||||
|
SuggestionRemoveSuperTypes,
|
||||||
|
SuggestionsSuperTypes,
|
||||||
SuperType,
|
SuperType,
|
||||||
SuperTypeMapper,
|
|
||||||
SuperTypes,
|
SuperTypes,
|
||||||
} from '@red/domain';
|
} from '@red/domain';
|
||||||
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
|
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
|
||||||
|
import { IListable, List } from '@iqser/common-ui';
|
||||||
|
|
||||||
interface AnnotationContent {
|
export class AnnotationWrapper implements IListable, Record<string, unknown> {
|
||||||
translation: string;
|
[x: string]: unknown;
|
||||||
params: { [key: string]: string };
|
|
||||||
untranslatedContent: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AnnotationWrapper implements IListable {
|
|
||||||
id: string;
|
|
||||||
superType: SuperType;
|
superType: SuperType;
|
||||||
superTypeLabel: string;
|
typeValue: string;
|
||||||
type: string;
|
recategorizationType: string;
|
||||||
typeLabel?: string;
|
|
||||||
color: string;
|
color: string;
|
||||||
numberOfComments = 0;
|
entity: Dictionary;
|
||||||
firstBottomLeftPoint: IPoint;
|
comments: IComment[] = [];
|
||||||
|
firstTopLeftPoint: IPoint;
|
||||||
|
annotationId: string;
|
||||||
shortContent: string;
|
shortContent: string;
|
||||||
content: AnnotationContent;
|
content: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
typeLabel: string;
|
||||||
pageNumber: number;
|
pageNumber: number;
|
||||||
dictionaryOperation = false;
|
hint: boolean;
|
||||||
positions: IRectangle[] = [];
|
redaction: boolean;
|
||||||
|
status: string;
|
||||||
|
dictionaryOperation: boolean;
|
||||||
|
positions: IRectangle[];
|
||||||
|
recommendationType: string;
|
||||||
legalBasisValue: string;
|
legalBasisValue: string;
|
||||||
// AREA === rectangle
|
legalBasisChangeValue?: string;
|
||||||
AREA = false;
|
resizing = false;
|
||||||
HINT = false;
|
rectangle?: boolean;
|
||||||
IMAGE = false;
|
|
||||||
IMAGE_HINT = false;
|
|
||||||
section?: string;
|
section?: string;
|
||||||
reference: string[] = [];
|
reference: List;
|
||||||
imported = false;
|
imported?: boolean;
|
||||||
manual = false;
|
image?: boolean;
|
||||||
|
manual?: boolean;
|
||||||
pending = false;
|
pending = false;
|
||||||
|
hintDictionary?: boolean;
|
||||||
textAfter?: string;
|
textAfter?: string;
|
||||||
textBefore?: string;
|
textBefore?: string;
|
||||||
isChangeLogEntry = false;
|
isChangeLogEntry?: boolean;
|
||||||
engines: LogEntryEngine[] = [];
|
changeLogType?: 'ADDED' | 'REMOVED' | 'CHANGED';
|
||||||
|
engines?: string[];
|
||||||
hasBeenResized: boolean;
|
hasBeenResized: boolean;
|
||||||
hasBeenResizedLocally: boolean;
|
|
||||||
hasBeenRecategorized: boolean;
|
hasBeenRecategorized: boolean;
|
||||||
hasLegalBasisChanged: boolean;
|
hasLegalBasisChanged: boolean;
|
||||||
hasBeenForcedHint: boolean;
|
hasBeenForcedHint: boolean;
|
||||||
hasBeenForcedRedaction: boolean;
|
hasBeenForcedRedaction: boolean;
|
||||||
hasBeenRemovedByManualOverride: boolean;
|
hasBeenRemovedByManualOverride: boolean;
|
||||||
isRemoved = false;
|
|
||||||
isRemovedLocally = false;
|
|
||||||
hiddenInWorkload = false;
|
|
||||||
lastManualChange: ManualRedactionType;
|
|
||||||
entry: IEntityLogEntry;
|
|
||||||
|
|
||||||
get isRuleBased() {
|
|
||||||
return this.engines.includes(LogEntryEngines.RULE);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isDictBased() {
|
|
||||||
return [LogEntryEngines.DICTIONARY, LogEntryEngines.DOSSIER_DICTIONARY].some(engine => this.engines.includes(engine));
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRedactedImageHint() {
|
|
||||||
return (
|
|
||||||
(this.IMAGE_HINT && this.superType === SuperTypes.Redaction) ||
|
|
||||||
(this.IMAGE_HINT && this.superType === SuperTypes.ManualRedaction)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isSkippedImageHint() {
|
|
||||||
return this.IMAGE_HINT && this.superType === SuperTypes.Hint;
|
|
||||||
}
|
|
||||||
|
|
||||||
get searchKey(): string {
|
get searchKey(): string {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isChangeLogRemoved() {
|
||||||
|
return this.changeLogType === 'REMOVED';
|
||||||
|
}
|
||||||
|
|
||||||
get descriptor() {
|
get descriptor() {
|
||||||
return this.isModifyDictionary ? _('dictionary') : _('type');
|
return this.isModifyDictionary ? _('dictionary') : _('type');
|
||||||
}
|
}
|
||||||
@ -112,10 +91,7 @@ export class AnnotationWrapper implements IListable {
|
|||||||
|
|
||||||
get canBeMarkedAsFalsePositive() {
|
get canBeMarkedAsFalsePositive() {
|
||||||
return (
|
return (
|
||||||
(this.isRecommendation ||
|
(this.isRecommendation || this.superType === SuperTypes.Redaction) &&
|
||||||
this.superType === SuperTypes.Redaction ||
|
|
||||||
(this.isSkipped && this.isDictBased) ||
|
|
||||||
(this.isRemovedLocally && this.isDictBased)) &&
|
|
||||||
!this.isImage &&
|
!this.isImage &&
|
||||||
!this.imported &&
|
!this.imported &&
|
||||||
!this.pending &&
|
!this.pending &&
|
||||||
@ -123,18 +99,26 @@ export class AnnotationWrapper implements IListable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSuperTypeBasedColor() {
|
||||||
|
return this.isSuggestion || this.isDeclinedSuggestion;
|
||||||
|
}
|
||||||
|
|
||||||
get isSkipped() {
|
get isSkipped() {
|
||||||
return this.superType === SuperTypes.Skipped;
|
return this.superType === SuperTypes.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isImage() {
|
get isImage() {
|
||||||
return this.type?.toLowerCase() === 'image' || this.IMAGE || this.IMAGE_HINT;
|
return this.type?.toLowerCase() === 'image' || this.image;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isOCR() {
|
get isOCR() {
|
||||||
return this.type?.toLowerCase() === 'ocr';
|
return this.type?.toLowerCase() === 'ocr';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get type() {
|
||||||
|
return this.recategorizationType || this.typeValue;
|
||||||
|
}
|
||||||
|
|
||||||
get topLevelFilter() {
|
get topLevelFilter() {
|
||||||
return !LowLevelFilterTypes[this.superType];
|
return !LowLevelFilterTypes[this.superType];
|
||||||
}
|
}
|
||||||
@ -151,12 +135,16 @@ export class AnnotationWrapper implements IListable {
|
|||||||
return this.type?.toLowerCase() === 'false_positive' && !!FalsePositiveSuperTypes[this.superType];
|
return this.type?.toLowerCase() === 'false_positive' && !!FalsePositiveSuperTypes[this.superType];
|
||||||
}
|
}
|
||||||
|
|
||||||
get isHint() {
|
get isDeclinedSuggestion() {
|
||||||
return this.superType === SuperTypes.Hint || this.superType === SuperTypes.ManualHint;
|
return this.superType === SuperTypes.DeclinedSuggestion;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isIgnoredHint() {
|
get isApproved() {
|
||||||
return this.superType === SuperTypes.IgnoredHint;
|
return this.status === 'APPROVED';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isHint() {
|
||||||
|
return this.superType === SuperTypes.Hint;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEarmark() {
|
get isEarmark() {
|
||||||
@ -168,17 +156,53 @@ export class AnnotationWrapper implements IListable {
|
|||||||
return 'hexagon';
|
return 'hexagon';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.HINT) {
|
if (this.isHint || this.isIgnoredHint) {
|
||||||
return 'circle';
|
return 'circle';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isSuggestion || this.isDeclinedSuggestion) {
|
||||||
|
return 'rhombus';
|
||||||
|
}
|
||||||
|
|
||||||
return 'square';
|
return 'square';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isIgnoredHint() {
|
||||||
|
return this.superType === SuperTypes.IgnoredHint;
|
||||||
|
}
|
||||||
|
|
||||||
get isRedacted() {
|
get isRedacted() {
|
||||||
return this.superType === SuperTypes.Redaction || this.superType === SuperTypes.ManualRedaction;
|
return this.superType === SuperTypes.Redaction || this.superType === SuperTypes.ManualRedaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSuggestion() {
|
||||||
|
return !!SuggestionsSuperTypes[this.superType];
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSuggestionResize() {
|
||||||
|
return this.superType === SuperTypes.SuggestionResize;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSuggestionRecategorizeImage() {
|
||||||
|
return this.superType === SuperTypes.SuggestionRecategorizeImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSuggestionAdd() {
|
||||||
|
return !!SuggestionAddSuperTypes[this.superType];
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSuggestionRemove() {
|
||||||
|
return !!SuggestionRemoveSuperTypes[this.superType];
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSuggestionRemoveDictionary() {
|
||||||
|
return this.superType === SuperTypes.SuggestionRemoveDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSuggestionLegalBasisChange() {
|
||||||
|
return this.superType === SuperTypes.SuggestionChangeLegalBasis;
|
||||||
|
}
|
||||||
|
|
||||||
get isModifyDictionary() {
|
get isModifyDictionary() {
|
||||||
return this.dictionaryOperation;
|
return this.dictionaryOperation;
|
||||||
}
|
}
|
||||||
@ -198,16 +222,20 @@ export class AnnotationWrapper implements IListable {
|
|||||||
return this.superType === SuperTypes.Recommendation;
|
return this.superType === SuperTypes.Recommendation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this.annotationId;
|
||||||
|
}
|
||||||
|
|
||||||
get x() {
|
get x() {
|
||||||
return this.firstBottomLeftPoint.x;
|
return this.firstTopLeftPoint.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
get y() {
|
get y() {
|
||||||
return this.firstBottomLeftPoint.y;
|
return this.firstTopLeftPoint.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
get legalBasis() {
|
get legalBasis() {
|
||||||
return this.legalBasisValue;
|
return this.legalBasisChangeValue || this.legalBasisValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
get width(): number {
|
get width(): number {
|
||||||
@ -218,167 +246,178 @@ export class AnnotationWrapper implements IListable {
|
|||||||
return Math.floor(this.positions[0].height);
|
return Math.floor(this.positions[0].height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get previewAnnotation() {
|
||||||
|
return (
|
||||||
|
this.isRedacted ||
|
||||||
|
this.isSuggestionAdd ||
|
||||||
|
this.isSuggestionRemove ||
|
||||||
|
this.isSuggestionResize ||
|
||||||
|
this.isSuggestionLegalBasisChange ||
|
||||||
|
this.isSuggestionRecategorizeImage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static fromEarmark(earmark: Earmark) {
|
static fromEarmark(earmark: Earmark) {
|
||||||
const annotationWrapper = new AnnotationWrapper();
|
const annotationWrapper = new AnnotationWrapper();
|
||||||
|
|
||||||
annotationWrapper.id = earmark.id;
|
annotationWrapper.annotationId = earmark.id;
|
||||||
annotationWrapper.pageNumber = earmark.positions[0].page;
|
annotationWrapper.pageNumber = earmark.positions[0].page;
|
||||||
annotationWrapper.superType = SuperTypes.TextHighlight;
|
annotationWrapper.superType = SuperTypes.TextHighlight;
|
||||||
annotationWrapper.type = SuperTypes.TextHighlight;
|
annotationWrapper.typeValue = SuperTypes.TextHighlight;
|
||||||
annotationWrapper.value = 'Imported';
|
annotationWrapper.value = 'Imported';
|
||||||
annotationWrapper.color = earmark.hexColor;
|
annotationWrapper.color = earmark.hexColor;
|
||||||
annotationWrapper.positions = earmark.positions;
|
annotationWrapper.positions = earmark.positions;
|
||||||
annotationWrapper.firstBottomLeftPoint = earmark.positions[0]?.topLeft;
|
annotationWrapper.firstTopLeftPoint = earmark.positions[0]?.topLeft;
|
||||||
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
annotationWrapper.typeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
||||||
|
|
||||||
return annotationWrapper;
|
return annotationWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromData(
|
static fromData(redactionLogEntry: RedactionLogEntry, dictionaries: Dictionary[], defaultColors: DefaultColors) {
|
||||||
logEntry: IEntityLogEntry,
|
|
||||||
dictionary: Dictionary,
|
|
||||||
changeLogType: ChangeType,
|
|
||||||
legalBasisList: ILegalBasis[],
|
|
||||||
isDocumine: boolean,
|
|
||||||
defaultColors: DefaultColors,
|
|
||||||
) {
|
|
||||||
const annotationWrapper = new AnnotationWrapper();
|
const annotationWrapper = new AnnotationWrapper();
|
||||||
|
|
||||||
annotationWrapper.pending = logEntry.state === EntryStates.PENDING;
|
annotationWrapper.annotationId = redactionLogEntry.id;
|
||||||
annotationWrapper.id = logEntry.id + (annotationWrapper.pending ? '-pending' : '');
|
annotationWrapper.isChangeLogEntry = redactionLogEntry.isChangeLogEntry;
|
||||||
annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType;
|
annotationWrapper.changeLogType = redactionLogEntry.changeLogType;
|
||||||
annotationWrapper.type = logEntry.type;
|
annotationWrapper.redaction = redactionLogEntry.redacted;
|
||||||
annotationWrapper.value = logEntry.value;
|
annotationWrapper.hint = redactionLogEntry.hint;
|
||||||
annotationWrapper.firstBottomLeftPoint = { x: logEntry.positions[0].rectangle[0], y: logEntry.positions[0].rectangle[1] };
|
annotationWrapper.typeValue = redactionLogEntry.type;
|
||||||
annotationWrapper.pageNumber = logEntry.positions[0].pageNumber;
|
annotationWrapper.value = redactionLogEntry.value;
|
||||||
annotationWrapper.positions = logEntry.positions.map(p => ({
|
annotationWrapper.firstTopLeftPoint = redactionLogEntry.positions[0]?.topLeft;
|
||||||
page: p.pageNumber,
|
annotationWrapper.pageNumber = redactionLogEntry.positions[0]?.page;
|
||||||
height: p.rectangle[3],
|
annotationWrapper.positions = redactionLogEntry.positions;
|
||||||
width: p.rectangle[2],
|
annotationWrapper.textBefore = redactionLogEntry.textBefore;
|
||||||
topLeft: { x: p.rectangle[0], y: p.rectangle[1] },
|
annotationWrapper.textAfter = redactionLogEntry.textAfter;
|
||||||
}));
|
annotationWrapper.dictionaryOperation = redactionLogEntry.dictionaryEntry;
|
||||||
annotationWrapper.textBefore = logEntry.textBefore;
|
annotationWrapper.image = redactionLogEntry.image;
|
||||||
annotationWrapper.textAfter = logEntry.textAfter;
|
annotationWrapper.imported = redactionLogEntry.imported;
|
||||||
annotationWrapper.dictionaryOperation = logEntry.dictionaryEntry;
|
annotationWrapper.legalBasisValue = redactionLogEntry.legalBasis;
|
||||||
|
annotationWrapper.comments = redactionLogEntry.comments || [];
|
||||||
annotationWrapper.HINT = logEntry.entryType === EntityTypes.HINT;
|
annotationWrapper.manual = redactionLogEntry.manualChanges?.length > 0;
|
||||||
annotationWrapper.IMAGE = logEntry.entryType === EntityTypes.IMAGE;
|
annotationWrapper.engines = redactionLogEntry.engines;
|
||||||
annotationWrapper.AREA = logEntry.entryType === EntityTypes.AREA;
|
annotationWrapper.section = redactionLogEntry.section;
|
||||||
annotationWrapper.IMAGE_HINT = logEntry.entryType === EntityTypes.IMAGE_HINT;
|
annotationWrapper.reference = redactionLogEntry.reference || [];
|
||||||
|
annotationWrapper.rectangle = redactionLogEntry.rectangle;
|
||||||
annotationWrapper.numberOfComments = logEntry.numberOfComments;
|
annotationWrapper.hintDictionary = redactionLogEntry.hintDictionary;
|
||||||
annotationWrapper.imported = logEntry.imported;
|
annotationWrapper.hasBeenResized = !!redactionLogEntry.manualChanges?.find(
|
||||||
annotationWrapper.legalBasisValue = logEntry.legalBasis;
|
c => c.manualRedactionType === ManualRedactionTypes.RESIZE && c.annotationStatus === LogEntryStatuses.APPROVED,
|
||||||
annotationWrapper.manual = logEntry.manualChanges?.length > 0;
|
|
||||||
annotationWrapper.engines = logEntry.engines ?? [];
|
|
||||||
annotationWrapper.section = logEntry.section;
|
|
||||||
annotationWrapper.reference = logEntry.reference || [];
|
|
||||||
annotationWrapper.hasBeenResized = !!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.RESIZE);
|
|
||||||
annotationWrapper.hasBeenResizedLocally =
|
|
||||||
annotationWrapper.hasBeenResized && annotationWrapper.engines.includes(LogEntryEngines.MANUAL);
|
|
||||||
annotationWrapper.hasBeenRecategorized = !!logEntry.manualChanges?.find(
|
|
||||||
c => c.manualRedactionType === ManualRedactionTypes.RECATEGORIZE,
|
|
||||||
);
|
);
|
||||||
annotationWrapper.hasLegalBasisChanged = !!logEntry.manualChanges?.find(
|
annotationWrapper.hasBeenRecategorized = !!redactionLogEntry.manualChanges?.find(
|
||||||
c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE,
|
c => c.manualRedactionType === ManualRedactionTypes.RECATEGORIZE && c.annotationStatus === LogEntryStatuses.APPROVED,
|
||||||
);
|
);
|
||||||
annotationWrapper.hasBeenForcedHint =
|
annotationWrapper.hasLegalBasisChanged = !!redactionLogEntry.manualChanges?.find(
|
||||||
!!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.FORCE) && annotationWrapper.HINT;
|
c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE && c.annotationStatus === LogEntryStatuses.APPROVED,
|
||||||
annotationWrapper.hasBeenForcedRedaction =
|
|
||||||
!!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.FORCE) && !annotationWrapper.HINT;
|
|
||||||
annotationWrapper.hasBeenRemovedByManualOverride = !!logEntry.manualChanges?.find(
|
|
||||||
c => c.manualRedactionType === ManualRedactionTypes.REMOVE,
|
|
||||||
);
|
);
|
||||||
|
annotationWrapper.hasBeenForcedHint = !!redactionLogEntry.manualChanges?.find(
|
||||||
|
c => c.manualRedactionType === ManualRedactionTypes.FORCE_HINT && c.annotationStatus === LogEntryStatuses.APPROVED,
|
||||||
|
);
|
||||||
|
annotationWrapper.hasBeenForcedRedaction = !!redactionLogEntry.manualChanges?.find(
|
||||||
|
c => c.manualRedactionType === ManualRedactionTypes.FORCE_REDACT && c.annotationStatus === LogEntryStatuses.APPROVED,
|
||||||
|
);
|
||||||
|
annotationWrapper.hasBeenRemovedByManualOverride = !!redactionLogEntry.manualChanges?.find(
|
||||||
|
c => c.manualRedactionType === ManualRedactionTypes.REMOVE_LOCALLY && c.annotationStatus === LogEntryStatuses.APPROVED,
|
||||||
|
);
|
||||||
|
annotationWrapper.legalBasisChangeValue = redactionLogEntry.manualChanges?.find(
|
||||||
|
c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE && c.annotationStatus === LogEntryStatuses.REQUESTED,
|
||||||
|
)?.propertyChanges.legalBasis;
|
||||||
|
|
||||||
const content = this.#createContent(annotationWrapper, logEntry, isDocumine);
|
this._createContent(annotationWrapper, redactionLogEntry);
|
||||||
annotationWrapper.shortContent = this.#getShortContent(annotationWrapper, legalBasisList) || content.untranslatedContent;
|
this._setSuperType(annotationWrapper, redactionLogEntry);
|
||||||
annotationWrapper.content = content;
|
this._handleRecommendations(annotationWrapper, redactionLogEntry);
|
||||||
|
annotationWrapper.typeLabel = this.#getTypeLabel(redactionLogEntry, annotationWrapper);
|
||||||
|
|
||||||
const lastRelevantManualChange = logEntry.manualChanges?.at(-1);
|
const entity = dictionaries.find(d => d.type === annotationWrapper.typeValue);
|
||||||
annotationWrapper.lastManualChange = lastRelevantManualChange?.manualRedactionType;
|
annotationWrapper.entity = entity.virtual ? null : entity;
|
||||||
annotationWrapper.superType = SuperTypeMapper[logEntry.entryType][logEntry.state](logEntry);
|
|
||||||
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
|
||||||
|
|
||||||
annotationWrapper.isRemoved = logEntry.state === EntryStates.REMOVED;
|
const colorKey = annotationWrapper.isSuperTypeBasedColor
|
||||||
annotationWrapper.isRemovedLocally =
|
? annotationDefaultColorConfig[annotationWrapper.superType]
|
||||||
lastRelevantManualChange?.manualRedactionType === ManualRedactionTypes.REMOVE &&
|
: annotationEntityColorConfig[annotationWrapper.superType];
|
||||||
logEntry.engines.includes(LogEntryEngines.MANUAL);
|
annotationWrapper.color = annotationWrapper.isSuperTypeBasedColor ? defaultColors[colorKey] : (entity[colorKey] as string);
|
||||||
|
|
||||||
annotationWrapper.typeLabel = dictionary?.virtual ? undefined : dictionary?.label;
|
|
||||||
|
|
||||||
if (annotationWrapper.pending) {
|
|
||||||
annotationWrapper.color = defaultColors[annotationDefaultColorConfig.analysis] as string;
|
|
||||||
} else {
|
|
||||||
const colorKey = annotationEntityColorConfig[annotationWrapper.superType];
|
|
||||||
const defaultColor = annotationDefaultColorConfig[annotationWrapper.superType];
|
|
||||||
annotationWrapper.color =
|
|
||||||
dictionary && !annotationWrapper.isRedactedImageHint
|
|
||||||
? (dictionary[colorKey] as string)
|
|
||||||
: (defaultColors[defaultColor] as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
annotationWrapper.entry = logEntry;
|
|
||||||
|
|
||||||
return annotationWrapper;
|
return annotationWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
static #createContent(annotationWrapper: AnnotationWrapper, logEntry: IEntityLogEntry, isDocumine: boolean) {
|
static #getTypeLabel(redactionLogEntry: RedactionLogEntry, annotation: AnnotationWrapper): string {
|
||||||
let untranslatedContent = '';
|
if (redactionLogEntry.reason?.toLowerCase() === 'false positive') {
|
||||||
const params: { [key: string]: string } = {};
|
return annotationTypesTranslations[SuggestionAddFalsePositive];
|
||||||
if (logEntry.matchedRule) {
|
|
||||||
params['hasRule'] = 'true';
|
|
||||||
params['matchedRule'] = logEntry.matchedRule.replace(/(^[, ]*)|([, ]*$)/g, '');
|
|
||||||
params['ruleSymbol'] = isDocumine ? ':' : '';
|
|
||||||
|
|
||||||
untranslatedContent += `Rule ${logEntry.matchedRule} matched${isDocumine ? ':' : ''} \n\n`;
|
|
||||||
}
|
}
|
||||||
|
return annotationTypesTranslations[annotation.superType];
|
||||||
|
}
|
||||||
|
|
||||||
if (logEntry.reason) {
|
private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntry) {
|
||||||
params['hasReason'] = 'true';
|
if (annotationWrapper.superType === SuperTypes.Recommendation) {
|
||||||
if (isDocumine && logEntry.reason.slice(-1) === '.') {
|
annotationWrapper.recommendationType = redactionLogEntry.type;
|
||||||
logEntry.reason = logEntry.reason.slice(0, -1);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _getLastRelevantManualChange(manualChanges: IManualChange[]) {
|
||||||
|
return manualChanges[manualChanges.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntry) {
|
||||||
|
if (redactionLogEntryWrapper.manualChanges?.length) {
|
||||||
|
const lastRelevantManualChange = this._getLastRelevantManualChange(redactionLogEntryWrapper.manualChanges);
|
||||||
|
|
||||||
|
annotationWrapper.pending = !lastRelevantManualChange.processed;
|
||||||
|
|
||||||
|
annotationWrapper.superType = AnnotationWrapper._selectSuperType(
|
||||||
|
redactionLogEntryWrapper,
|
||||||
|
lastRelevantManualChange,
|
||||||
|
annotationWrapper.hintDictionary,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (lastRelevantManualChange.annotationStatus === LogEntryStatuses.REQUESTED) {
|
||||||
|
annotationWrapper.recategorizationType = lastRelevantManualChange.propertyChanges.type;
|
||||||
}
|
}
|
||||||
if (!params['hasRule']) {
|
} else {
|
||||||
params['reason'] = logEntry.reason.substring(0, 1).toUpperCase() + logEntry.reason.substring(1);
|
if (redactionLogEntryWrapper.recommendation) {
|
||||||
|
annotationWrapper.superType = SuperTypes.Recommendation;
|
||||||
|
} else if (redactionLogEntryWrapper.redacted) {
|
||||||
|
annotationWrapper.superType = SuperTypes.Redaction;
|
||||||
|
} else if (redactionLogEntryWrapper.hint) {
|
||||||
|
annotationWrapper.superType = SuperTypes.Hint;
|
||||||
} else {
|
} else {
|
||||||
params['reason'] = logEntry.reason;
|
annotationWrapper.superType = SuperTypes.Skipped;
|
||||||
}
|
}
|
||||||
params['reason'] = params['reason'].replace(/(^[, ]*)|([, ]*$)/g, '');
|
}
|
||||||
untranslatedContent += logEntry.reason + '\n\n';
|
}
|
||||||
//remove leading and trailing commas and whitespaces
|
|
||||||
untranslatedContent = untranslatedContent.replace(/(^[, ]*)|([, ]*$)/g, '');
|
private static _createContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntry) {
|
||||||
untranslatedContent = untranslatedContent.substring(0, 1).toUpperCase() + untranslatedContent.substring(1);
|
let content = '';
|
||||||
|
if (entry.matchedRule) {
|
||||||
|
content += `Rule ${entry.matchedRule} matched \n\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (annotationWrapper.legalBasis && !isDocumine) {
|
if (entry.reason) {
|
||||||
params['hasLb'] = 'true';
|
content += entry.reason + '\n\n';
|
||||||
params['legalBasis'] = annotationWrapper.legalBasis;
|
//remove leading and trailing commas and whitespaces
|
||||||
untranslatedContent += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
|
content = content.replace(/^[, ]*|[, ]*$/g, '');
|
||||||
|
content = content.substring(0, 1).toUpperCase() + content.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (annotationWrapper.legalBasis) {
|
||||||
|
content += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (annotationWrapper.hasBeenRemovedByManualOverride) {
|
if (annotationWrapper.hasBeenRemovedByManualOverride) {
|
||||||
params['hasOverride'] = 'true';
|
content += 'Removed by manual override';
|
||||||
untranslatedContent += 'Removed by manual override';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logEntry.section) {
|
if (entry.section) {
|
||||||
params['hasSection'] = 'true';
|
let prefix = 'In section: ';
|
||||||
params['sectionSymbol'] = isDocumine ? '' : ':';
|
if (content.length) {
|
||||||
params['shouldLower'] = untranslatedContent.length.toString();
|
|
||||||
params['section'] = logEntry.section;
|
|
||||||
let prefix = `In section${isDocumine ? '' : ':'} `;
|
|
||||||
if (untranslatedContent.length) {
|
|
||||||
prefix = ` ${prefix.toLowerCase()}`;
|
prefix = ` ${prefix.toLowerCase()}`;
|
||||||
}
|
}
|
||||||
untranslatedContent += `${prefix} "${logEntry.section}"`;
|
content += `${prefix} "${entry.section}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { translation: _('annotation-content'), params: params, untranslatedContent: untranslatedContent };
|
annotationWrapper.shortContent = this._getShortContent(annotationWrapper, entry) || content;
|
||||||
|
annotationWrapper.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
static #getShortContent(annotationWrapper: AnnotationWrapper, legalBasisList: ILegalBasis[]) {
|
private static _getShortContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntry) {
|
||||||
if (annotationWrapper.legalBasis) {
|
if (annotationWrapper.legalBasis) {
|
||||||
const lb = legalBasisList.find(lbm => lbm.technicalName?.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase()));
|
const lb = entry.legalBasisList?.find(lbm => lbm.reason.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase()));
|
||||||
if (lb) {
|
if (lb) {
|
||||||
return lb.name;
|
return lb.name;
|
||||||
}
|
}
|
||||||
@ -386,4 +425,158 @@ export class AnnotationWrapper implements IListable {
|
|||||||
|
|
||||||
return annotationWrapper.legalBasis;
|
return annotationWrapper.legalBasis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static _selectSuperType(
|
||||||
|
redactionLogEntry: RedactionLogEntry,
|
||||||
|
lastManualChange: IManualChange,
|
||||||
|
isHintDictionary: boolean,
|
||||||
|
): SuperType {
|
||||||
|
switch (lastManualChange.manualRedactionType) {
|
||||||
|
case ManualRedactionTypes.ADD_LOCALLY:
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
return SuperTypes.ManualRedaction;
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return SuperTypes.DeclinedSuggestion;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionAdd;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ManualRedactionTypes.ADD_TO_DICTIONARY:
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
return isHintDictionary ? SuperTypes.Hint : SuperTypes.Redaction;
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return SuperTypes.DeclinedSuggestion;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionAddDictionary;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ManualRedactionTypes.REMOVE_LOCALLY:
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
return isHintDictionary ? SuperTypes.IgnoredHint : SuperTypes.Skipped;
|
||||||
|
case LogEntryStatuses.DECLINED: {
|
||||||
|
if (isHintDictionary) {
|
||||||
|
return SuperTypes.Hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redactionLogEntry.redacted) {
|
||||||
|
return SuperTypes.Redaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SuperTypes.Skipped;
|
||||||
|
}
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionRemove;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ManualRedactionTypes.REMOVE_FROM_DICTIONARY:
|
||||||
|
if (redactionLogEntry.redacted) {
|
||||||
|
if (lastManualChange.processed) {
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
return SuperTypes.Skipped;
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return SuperTypes.Redaction;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionRemoveDictionary;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return SuperTypes.Redaction;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionRemoveDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (lastManualChange.processed) {
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
return SuperTypes.Redaction;
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return isHintDictionary ? SuperTypes.Hint : SuperTypes.Skipped;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionRemoveDictionary;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return isHintDictionary ? SuperTypes.Hint : SuperTypes.Skipped;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionRemoveDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ManualRedactionTypes.FORCE_REDACT:
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
return SuperTypes.Redaction;
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return isHintDictionary ? SuperTypes.IgnoredHint : SuperTypes.Skipped;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionForceRedaction;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ManualRedactionTypes.FORCE_HINT:
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
return SuperTypes.Hint;
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return SuperTypes.IgnoredHint;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionForceHint;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ManualRedactionTypes.RECATEGORIZE:
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
case LogEntryStatuses.DECLINED: {
|
||||||
|
if (redactionLogEntry.recommendation) {
|
||||||
|
return SuperTypes.Recommendation;
|
||||||
|
} else if (redactionLogEntry.redacted) {
|
||||||
|
return SuperTypes.Redaction;
|
||||||
|
} else if (redactionLogEntry.hint) {
|
||||||
|
return SuperTypes.Hint;
|
||||||
|
}
|
||||||
|
return isHintDictionary ? SuperTypes.IgnoredHint : SuperTypes.Skipped;
|
||||||
|
}
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionRecategorizeImage;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ManualRedactionTypes.LEGAL_BASIS_CHANGE:
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
return redactionLogEntry.type === SuperTypes.ManualRedaction ? SuperTypes.ManualRedaction : SuperTypes.Redaction;
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionChangeLegalBasis;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ManualRedactionTypes.RESIZE:
|
||||||
|
switch (lastManualChange.annotationStatus) {
|
||||||
|
case LogEntryStatuses.APPROVED:
|
||||||
|
case LogEntryStatuses.DECLINED:
|
||||||
|
if (redactionLogEntry.recommendation) {
|
||||||
|
return SuperTypes.Recommendation;
|
||||||
|
} else if (redactionLogEntry.redacted) {
|
||||||
|
return redactionLogEntry.type === SuperTypes.ManualRedaction
|
||||||
|
? SuperTypes.ManualRedaction
|
||||||
|
: SuperTypes.Redaction;
|
||||||
|
} else if (redactionLogEntry.hint) {
|
||||||
|
return SuperTypes.Hint;
|
||||||
|
}
|
||||||
|
return isHintDictionary ? SuperTypes.IgnoredHint : SuperTypes.Skipped;
|
||||||
|
|
||||||
|
case LogEntryStatuses.REQUESTED:
|
||||||
|
return SuperTypes.SuggestionResize;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
export interface ListItem<T> {
|
|
||||||
item: T;
|
|
||||||
isSelected: boolean;
|
|
||||||
}
|
|
||||||
@ -2,8 +2,6 @@ import { IManualRedactionEntry } from '@red/domain';
|
|||||||
|
|
||||||
export const ManualRedactionEntryTypes = {
|
export const ManualRedactionEntryTypes = {
|
||||||
DICTIONARY: 'DICTIONARY',
|
DICTIONARY: 'DICTIONARY',
|
||||||
REDACT: 'REDACT',
|
|
||||||
HINT: 'HINT',
|
|
||||||
REDACTION: 'REDACTION',
|
REDACTION: 'REDACTION',
|
||||||
FALSE_POSITIVE: 'FALSE_POSITIVE',
|
FALSE_POSITIVE: 'FALSE_POSITIVE',
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
75
apps/red-ui/src/app/models/file/redaction-log.entry.ts
Normal file
75
apps/red-ui/src/app/models/file/redaction-log.entry.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { IChange, IComment, ILegalBasis, IManualChange, IRectangle, IRedactionLogEntry, LogEntryEngine } from '@red/domain';
|
||||||
|
|
||||||
|
export class RedactionLogEntry implements IRedactionLogEntry {
|
||||||
|
readonly changes?: IChange[];
|
||||||
|
readonly imported?: boolean;
|
||||||
|
readonly manualChanges?: IManualChange[];
|
||||||
|
readonly color?: number[];
|
||||||
|
readonly comments?: IComment[];
|
||||||
|
readonly dictionaryEntry?: boolean;
|
||||||
|
readonly dossierDictionaryEntry?: boolean;
|
||||||
|
readonly endOffset?: number;
|
||||||
|
readonly engines?: LogEntryEngine[];
|
||||||
|
readonly excluded?: boolean;
|
||||||
|
readonly hint?: boolean;
|
||||||
|
readonly rectangle?: boolean;
|
||||||
|
readonly id?: string;
|
||||||
|
readonly image?: boolean;
|
||||||
|
readonly imageHasTransparency?: boolean;
|
||||||
|
readonly legalBasis?: string;
|
||||||
|
readonly matchedRule?: number;
|
||||||
|
readonly positions?: IRectangle[];
|
||||||
|
readonly recommendation?: boolean;
|
||||||
|
readonly redacted?: boolean;
|
||||||
|
readonly reference?: string[];
|
||||||
|
readonly section?: string;
|
||||||
|
readonly sectionNumber?: number;
|
||||||
|
readonly startOffset?: number;
|
||||||
|
readonly textAfter?: string;
|
||||||
|
readonly textBefore?: string;
|
||||||
|
readonly type?: string;
|
||||||
|
readonly value?: string;
|
||||||
|
readonly sourceId?: string;
|
||||||
|
readonly isChangeLogEntry: boolean;
|
||||||
|
|
||||||
|
reason?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
redactionLogEntry: IRedactionLogEntry,
|
||||||
|
readonly changeLogType: 'ADDED' | 'REMOVED' | 'CHANGED' | null,
|
||||||
|
readonly legalBasisList: ILegalBasis[],
|
||||||
|
readonly hintDictionary: boolean,
|
||||||
|
) {
|
||||||
|
this.changes = redactionLogEntry.changes;
|
||||||
|
this.manualChanges = redactionLogEntry.manualChanges;
|
||||||
|
this.color = redactionLogEntry.color;
|
||||||
|
this.comments = redactionLogEntry.comments;
|
||||||
|
this.dictionaryEntry = redactionLogEntry.dictionaryEntry;
|
||||||
|
this.dossierDictionaryEntry = redactionLogEntry.dossierDictionaryEntry;
|
||||||
|
this.endOffset = redactionLogEntry.endOffset;
|
||||||
|
this.engines = redactionLogEntry.engines;
|
||||||
|
this.excluded = redactionLogEntry.excluded;
|
||||||
|
this.hint = redactionLogEntry.hint;
|
||||||
|
this.rectangle = redactionLogEntry.rectangle;
|
||||||
|
this.id = redactionLogEntry.id;
|
||||||
|
this.image = redactionLogEntry.image;
|
||||||
|
this.imageHasTransparency = redactionLogEntry.imageHasTransparency;
|
||||||
|
this.legalBasis = redactionLogEntry.legalBasis;
|
||||||
|
this.matchedRule = redactionLogEntry.matchedRule;
|
||||||
|
this.positions = redactionLogEntry.positions;
|
||||||
|
this.reason = redactionLogEntry.reason;
|
||||||
|
this.recommendation = redactionLogEntry.recommendation;
|
||||||
|
this.redacted = redactionLogEntry.redacted;
|
||||||
|
this.reference = redactionLogEntry.reference;
|
||||||
|
this.section = redactionLogEntry.section;
|
||||||
|
this.sectionNumber = redactionLogEntry.sectionNumber;
|
||||||
|
this.startOffset = redactionLogEntry.startOffset;
|
||||||
|
this.textAfter = redactionLogEntry.textAfter;
|
||||||
|
this.textBefore = redactionLogEntry.textBefore;
|
||||||
|
this.type = redactionLogEntry.type;
|
||||||
|
this.value = redactionLogEntry.value;
|
||||||
|
this.imported = redactionLogEntry.imported;
|
||||||
|
this.sourceId = redactionLogEntry.sourceId;
|
||||||
|
this.isChangeLogEntry = !!this.changeLogType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,12 @@
|
|||||||
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { CompositeRouteGuard, IqserAuthGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
|
||||||
import { RedRoleGuard } from '@users/red-role.guard';
|
import { RedRoleGuard } from '@users/red-role.guard';
|
||||||
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
|
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
|
||||||
import { PreferencesComponent } from './screens/preferences/preferences.component';
|
import { PreferencesComponent } from './screens/preferences/preferences.component';
|
||||||
import { Roles } from '@users/roles';
|
import { ROLES } from '@users/roles';
|
||||||
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
|
|
||||||
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
|
||||||
import { NotificationPreferencesService } from './services/notification-preferences.service';
|
|
||||||
|
|
||||||
export default [
|
const routes: IqserRoutes = [
|
||||||
{ path: '', redirectTo: 'user-profile', pathMatch: 'full' },
|
{ path: '', redirectTo: 'user-profile', pathMatch: 'full' },
|
||||||
{
|
{
|
||||||
path: 'user-profile',
|
path: 'user-profile',
|
||||||
@ -16,22 +15,20 @@ export default [
|
|||||||
data: {
|
data: {
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
},
|
},
|
||||||
loadChildren: () => import('./screens/user-profile/user-profile.routes'),
|
loadChildren: () => import('./screens/user-profile/user-profile.module').then(m => m.UserProfileModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'notifications',
|
path: 'notifications',
|
||||||
component: BaseAccountScreenComponent,
|
component: BaseAccountScreenComponent,
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
canDeactivate: [PendingChangesGuard],
|
|
||||||
data: {
|
data: {
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: [Roles.notifications.write, 'RED_USER'],
|
allow: [ROLES.notifications.write, 'RED_USER'],
|
||||||
redirectTo: '/',
|
redirectTo: '/',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
providers: [NotificationPreferencesService],
|
loadChildren: () => import('./screens/notifications/notifications.module').then(m => m.NotificationsModule),
|
||||||
loadChildren: () => import('./screens/notifications/notifications.routes'),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'preferences',
|
path: 'preferences',
|
||||||
@ -63,4 +60,10 @@ export default [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] satisfies IqserRoutes;
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class AccountRoutingModule {}
|
||||||
@ -2,9 +2,9 @@
|
|||||||
<ng-container *ngFor="let item of items">
|
<ng-container *ngFor="let item of items">
|
||||||
<div
|
<div
|
||||||
*ngIf="item.show"
|
*ngIf="item.show"
|
||||||
|
[iqserHelpMode]="'user_account'"
|
||||||
[routerLinkActiveOptions]="{ exact: false }"
|
[routerLinkActiveOptions]="{ exact: false }"
|
||||||
[routerLink]="'../' + item.screen"
|
[routerLink]="'../' + item.screen"
|
||||||
[attr.help-mode-key]="item.helpModeKey"
|
|
||||||
class="item"
|
class="item"
|
||||||
routerLinkActive="active"
|
routerLinkActive="active"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,19 +1,13 @@
|
|||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { IqserPermissionsService } from '@iqser/common-ui';
|
import { getCurrentUser, IqserPermissionsService } from '@iqser/common-ui';
|
||||||
import { Roles } from '@users/roles';
|
import { ROLES } from '@users/roles';
|
||||||
import { User } from '@red/domain';
|
import { User } from '@red/domain';
|
||||||
import { getCurrentUser } from '@iqser/common-ui/lib/users';
|
|
||||||
import { SideNavComponent } from '@common-ui/shared';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { NgForOf, NgIf } from '@angular/common';
|
|
||||||
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
readonly screen: string;
|
readonly screen: string;
|
||||||
readonly show?: boolean;
|
readonly show?: boolean;
|
||||||
readonly helpModeKey?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -21,7 +15,6 @@ interface NavItem {
|
|||||||
templateUrl: './account-side-nav.component.html',
|
templateUrl: './account-side-nav.component.html',
|
||||||
styleUrls: ['./account-side-nav.component.scss'],
|
styleUrls: ['./account-side-nav.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [SideNavComponent, TranslateModule, NgForOf, NgIf, RouterLinkActive, RouterLink],
|
|
||||||
})
|
})
|
||||||
export class AccountSideNavComponent {
|
export class AccountSideNavComponent {
|
||||||
readonly currentUser = getCurrentUser<User>();
|
readonly currentUser = getCurrentUser<User>();
|
||||||
@ -30,25 +23,21 @@ export class AccountSideNavComponent {
|
|||||||
screen: 'user-profile',
|
screen: 'user-profile',
|
||||||
label: _('user-profile'),
|
label: _('user-profile'),
|
||||||
show: true,
|
show: true,
|
||||||
helpModeKey: 'my_profile',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'notifications',
|
screen: 'notifications',
|
||||||
show: this.currentUser.isUser && this._permissionsService.has(Roles.notifications.write),
|
show: this.currentUser.isUser && this._permissionsService.has(ROLES.notifications.write),
|
||||||
label: _('notifications.label'),
|
label: _('notifications.label'),
|
||||||
helpModeKey: 'notification_preferences',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'preferences',
|
screen: 'preferences',
|
||||||
label: _('preferences-screen.label'),
|
label: _('preferences-screen.label'),
|
||||||
show: this.currentUser.isUser,
|
show: this.currentUser.isUser,
|
||||||
helpModeKey: 'user_preferences',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'warnings-preferences',
|
screen: 'warnings-preferences',
|
||||||
label: _('preferences-screen.warnings-label'),
|
label: _('preferences-screen.warnings-label'),
|
||||||
show: this.currentUser.isUser,
|
show: this.currentUser.isUser,
|
||||||
helpModeKey: 'prompts_and_dialogs',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
18
apps/red-ui/src/app/modules/account/account.module.ts
Normal file
18
apps/red-ui/src/app/modules/account/account.module.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { AccountRoutingModule } from './account-routing.module';
|
||||||
|
import { AccountSideNavComponent } from './account-side-nav/account-side-nav.component';
|
||||||
|
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
|
||||||
|
import { NotificationPreferencesService } from './services/notification-preferences.service';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { IqserSharedModule } from '@iqser/common-ui';
|
||||||
|
import { IqserHelpModeModule } from '@iqser/common-ui';
|
||||||
|
import { PreferencesComponent } from './screens/preferences/preferences.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AccountSideNavComponent, BaseAccountScreenComponent, PreferencesComponent],
|
||||||
|
imports: [CommonModule, SharedModule, AccountRoutingModule, TranslateModule, IqserSharedModule, IqserHelpModeModule],
|
||||||
|
providers: [NotificationPreferencesService],
|
||||||
|
})
|
||||||
|
export class AccountModule {}
|
||||||
@ -7,12 +7,11 @@
|
|||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
<div class="content-container full-height">
|
<div class="content-container full-height">
|
||||||
<div class="overlay-shadow"></div>
|
<div class="overlay-shadow"></div>
|
||||||
<div [ngClass]="!isWarningsScreen && 'dialog'">
|
<div class="dialog">
|
||||||
@if (!isWarningsScreen) {
|
<div class="dialog-header">
|
||||||
<div class="dialog-header">
|
<div class="heading-l" [translate]="translations[path]"></div>
|
||||||
<div class="heading-l" [translate]="translations[path]"></div>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,35 +1,26 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit, ViewContainerRef } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit, ViewContainerRef } from '@angular/core';
|
||||||
import { Router, RouterOutlet } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { accountTranslations } from '@translations/account-translations';
|
import { accountTranslations } from '@translations/account-translations';
|
||||||
import { NgClass } from '@angular/common';
|
|
||||||
import { AccountSideNavComponent } from '../account-side-nav/account-side-nav.component';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-base-account-screen',
|
selector: 'redaction-base-account-screen',
|
||||||
templateUrl: './base-account-screen-component.html',
|
templateUrl: './base-account-screen-component.html',
|
||||||
styleUrls: ['./base-account-screen-component.scss'],
|
styleUrls: ['./base-account-screen-component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [NgClass, RouterOutlet, AccountSideNavComponent, TranslateModule],
|
|
||||||
})
|
})
|
||||||
export class BaseAccountScreenComponent implements OnInit {
|
export class BaseAccountScreenComponent implements OnInit {
|
||||||
readonly translations = accountTranslations;
|
readonly translations = accountTranslations;
|
||||||
readonly path: string;
|
readonly path: string;
|
||||||
readonly isWarningsScreen: boolean;
|
|
||||||
|
|
||||||
constructor(
|
constructor(private readonly _router: Router, private readonly _hostRef: ViewContainerRef) {
|
||||||
private readonly _router: Router,
|
|
||||||
private readonly _hostRef: ViewContainerRef,
|
|
||||||
) {
|
|
||||||
this.path = this._router.url.split('/').pop();
|
this.path = this._router.url.split('/').pop();
|
||||||
this.isWarningsScreen = this.path === 'warnings-preferences';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.#setDialogWidth();
|
this._setDialogWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
#setDialogWidth() {
|
private _setDialogWidth() {
|
||||||
const element = this._hostRef.element.nativeElement as HTMLElement;
|
const element = this._hostRef.element.nativeElement as HTMLElement;
|
||||||
element.style.setProperty('--width', this.path === 'user-profile' ? 'unset' : '100%');
|
element.style.setProperty('--width', this.path === 'user-profile' ? 'unset' : '100%');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,22 +2,21 @@
|
|||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div *ngFor="let category of notificationCategories">
|
<div *ngFor="let category of notificationCategories">
|
||||||
<div class="iqser-input-group header w-full">
|
<div class="iqser-input-group header w-full">
|
||||||
<mat-slide-toggle color="primary" formControlName="{{ category }}Enabled"
|
<mat-slide-toggle color="primary" formControlName="{{ category }}Enabled">{{
|
||||||
>{{ translations[category] | translate }}
|
translations[category] | translate
|
||||||
</mat-slide-toggle>
|
}}</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TODO: This lots of getters-->
|
|
||||||
<div *ngIf="isCategoryActive(category)" class="options-content">
|
<div *ngIf="isCategoryActive(category)" class="options-content">
|
||||||
<div [translate]="'notifications-screen.options-title'" class="statement"></div>
|
<div [translate]="'notifications-screen.options-title'" class="statement"></div>
|
||||||
|
|
||||||
<div *ngFor="let key of notificationGroupsKeys; let i = index" class="group">
|
<div *ngFor="let key of notificationGroupsKeys; let i = index" class="group">
|
||||||
<div [translate]="translations[key]" class="group-title"></div>
|
<div [translate]="translations[key]" class="group-title"></div>
|
||||||
<div class="iqser-input-group">
|
<div class="iqser-input-group">
|
||||||
<ng-container *ngFor="let preference of getRssFilteredSettings(notificationGroupsValues[i])">
|
<ng-container *ngFor="let preference of notificationGroupsValues[i]">
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
(change)="addRemovePreference($event.checked, category, preference)"
|
|
||||||
*ngIf="!skipPreference(preference)"
|
*ngIf="!skipPreference(preference)"
|
||||||
|
(change)="addRemovePreference($event.checked, category, preference)"
|
||||||
[checked]="isPreferenceChecked(category, preference)"
|
[checked]="isPreferenceChecked(category, preference)"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { ReactiveFormsModule, UntypedFormBuilder } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
|
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
|
||||||
import { BaseFormComponent, getConfig, IconButtonComponent, LoadingService, Toaster } from '@iqser/common-ui';
|
import { BaseFormComponent, getCurrentUser, LoadingService, Toaster } from '@iqser/common-ui';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import {
|
import {
|
||||||
DocumentNotificationsTypes,
|
|
||||||
NotificationCategoriesValues,
|
NotificationCategoriesValues,
|
||||||
NotificationGroupsKeys,
|
NotificationGroupsKeys,
|
||||||
NotificationGroupsValues,
|
NotificationGroupsValues,
|
||||||
@ -13,46 +12,30 @@ import {
|
|||||||
} from '@red/domain';
|
} from '@red/domain';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { notificationsSettingsTranslations } from '@translations/notifications-settings-translations';
|
import { notificationsSettingsTranslations } from '@translations/notifications-settings-translations';
|
||||||
import { getCurrentUser } from '@iqser/common-ui/lib/users';
|
|
||||||
import { NgForOf, NgIf } from '@angular/common';
|
|
||||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
|
||||||
|
|
||||||
const RSS_EXCLUDED_SETTINGS = ['USER_PROMOTED_TO_APPROVER', 'USER_DEGRADED_TO_REVIEWER', 'ASSIGN_REVIEWER'];
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './notifications-screen.component.html',
|
templateUrl: './notifications-screen.component.html',
|
||||||
styleUrls: ['./notifications-screen.component.scss'],
|
styleUrls: ['./notifications-screen.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [ReactiveFormsModule, NgForOf, MatSlideToggle, TranslateModule, NgIf, MatCheckbox, IconButtonComponent],
|
|
||||||
})
|
})
|
||||||
export class NotificationsScreenComponent extends BaseFormComponent implements OnInit {
|
export class NotificationsScreenComponent extends BaseFormComponent implements OnInit {
|
||||||
readonly #toaster = inject(Toaster);
|
|
||||||
readonly #formBuilder = inject(UntypedFormBuilder);
|
|
||||||
readonly #loadingService = inject(LoadingService);
|
|
||||||
readonly #notificationPreferencesService = inject(NotificationPreferencesService);
|
|
||||||
readonly #cdRef = inject(ChangeDetectorRef);
|
|
||||||
readonly #config = getConfig();
|
|
||||||
readonly notificationCategories = NotificationCategoriesValues;
|
readonly notificationCategories = NotificationCategoriesValues;
|
||||||
readonly notificationGroupsKeys = NotificationGroupsKeys;
|
readonly notificationGroupsKeys = NotificationGroupsKeys;
|
||||||
readonly notificationGroupsValues = NotificationGroupsValues;
|
readonly notificationGroupsValues = NotificationGroupsValues;
|
||||||
readonly translations = notificationsSettingsTranslations;
|
readonly translations = notificationsSettingsTranslations;
|
||||||
readonly currentUser = getCurrentUser<User>();
|
readonly currentUser = getCurrentUser<User>();
|
||||||
|
constructor(
|
||||||
constructor() {
|
private readonly _toaster: Toaster,
|
||||||
|
private readonly _formBuilder: UntypedFormBuilder,
|
||||||
|
private readonly _loadingService: LoadingService,
|
||||||
|
private readonly _notificationPreferencesService: NotificationPreferencesService,
|
||||||
|
private readonly _changeRef: ChangeDetectorRef,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
await this.#initializeForm();
|
await this._initializeForm();
|
||||||
}
|
|
||||||
|
|
||||||
getRssFilteredSettings(settings: string[]) {
|
|
||||||
if (this.#config.IS_DOCUMINE) {
|
|
||||||
return settings.filter(s => !RSS_EXCLUDED_SETTINGS.includes(s));
|
|
||||||
}
|
|
||||||
return settings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isCategoryActive(category: string) {
|
isCategoryActive(category: string) {
|
||||||
@ -81,20 +64,19 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O
|
|||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
this.#loadingService.start();
|
this._loadingService.start();
|
||||||
try {
|
try {
|
||||||
const preferences = this.#filterNotificationPreferences();
|
await firstValueFrom(this._notificationPreferencesService.update(this.form.value));
|
||||||
await firstValueFrom(this.#notificationPreferencesService.update(preferences));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.#toaster.error(_('notifications-screen.error.generic'));
|
this._toaster.error(_('notifications-screen.error.generic'));
|
||||||
}
|
}
|
||||||
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
|
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
|
||||||
this.#cdRef.markForCheck();
|
this._changeRef.markForCheck();
|
||||||
this.#loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
#getForm() {
|
private _getForm(): UntypedFormGroup {
|
||||||
return this.#formBuilder.group({
|
return this._formBuilder.group({
|
||||||
inAppNotificationsEnabled: [undefined],
|
inAppNotificationsEnabled: [undefined],
|
||||||
emailNotificationsEnabled: [undefined],
|
emailNotificationsEnabled: [undefined],
|
||||||
emailNotificationType: [undefined],
|
emailNotificationType: [undefined],
|
||||||
@ -103,37 +85,14 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async #initializeForm() {
|
private async _initializeForm() {
|
||||||
this.#loadingService.start();
|
this._loadingService.start();
|
||||||
|
|
||||||
this.form = this.#getForm();
|
this.form = this._getForm();
|
||||||
const notificationPreferences = await firstValueFrom(this.#notificationPreferencesService.get());
|
const notificationPreferences = await firstValueFrom(this._notificationPreferencesService.get());
|
||||||
this.form.patchValue(notificationPreferences);
|
this.form.patchValue(notificationPreferences);
|
||||||
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
|
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
|
||||||
|
|
||||||
this.#loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
|
||||||
|
|
||||||
#filterNotificationPreferences() {
|
|
||||||
if (!this.#config.IS_DOCUMINE) {
|
|
||||||
return this.form.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isPreferenceChecked('inAppNotifications', DocumentNotificationsTypes.assignApprover)) {
|
|
||||||
return {
|
|
||||||
...this.form.value,
|
|
||||||
inAppNotifications: this.form
|
|
||||||
.get('inAppNotifications')
|
|
||||||
.value.filter((preference: string) => !RSS_EXCLUDED_SETTINGS.includes(preference)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!this.form.get('inAppNotifications').value.includes(DocumentNotificationsTypes.assignReviewer)) {
|
|
||||||
return {
|
|
||||||
...this.form.value,
|
|
||||||
inAppNotifications: [...this.form.get('inAppNotifications').value, DocumentNotificationsTypes.assignReviewer],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.form.value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
|
||||||
|
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
const routes = [{ path: '', component: NotificationsScreenComponent, canDeactivate: [PendingChangesGuard] }];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [NotificationsScreenComponent],
|
||||||
|
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, TranslateModule],
|
||||||
|
})
|
||||||
|
export class NotificationsModule {}
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
|
|
||||||
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
|
||||||
import { IqserRoutes } from '@iqser/common-ui';
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: NotificationsScreenComponent,
|
|
||||||
canDeactivate: [PendingChangesGuard],
|
|
||||||
},
|
|
||||||
] satisfies IqserRoutes;
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
<div class="dialog">
|
|
||||||
<form [formGroup]="form">
|
|
||||||
<div class="dialog-content">
|
|
||||||
<h1>{{ 'dialog-defaults-form.title' | translate }}</h1>
|
|
||||||
<h3>{{ 'dialog-defaults-form.redaction.title' | translate }}</h3>
|
|
||||||
<div class="iqser-input-group w-450">
|
|
||||||
<label translate="dialog-defaults-form.redaction.add-dialog"></label>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-select formControlName="addRedaction">
|
|
||||||
<mat-option *ngFor="let option of redactionAddOptions" [value]="option.value">{{
|
|
||||||
option.label | translate
|
|
||||||
}}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-checkbox *ngIf="displayExtraOptionAddRedaction" formControlName="addRedactionApplyToAll">{{
|
|
||||||
'dialog-defaults-form.extra-option-label' | translate
|
|
||||||
}}</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="iqser-input-group w-450">
|
|
||||||
<label translate="dialog-defaults-form.redaction.remove-dialog"></label>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-select formControlName="removeRedaction">
|
|
||||||
<mat-option *ngFor="let option of redactionRemoveOptions" [value]="option.value">{{
|
|
||||||
option.label | translate
|
|
||||||
}}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-checkbox *ngIf="displayExtraOptionRemoveRedaction" formControlName="removeRedactionApplyToAll">{{
|
|
||||||
'dialog-defaults-form.extra-option-label' | translate
|
|
||||||
}}</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<h3>{{ 'dialog-defaults-form.recommendation.title' | translate }}</h3>
|
|
||||||
<div class="iqser-input-group w-450">
|
|
||||||
<label translate="dialog-defaults-form.recommendation.remove-dialog"></label>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-select formControlName="removeRecommendation">
|
|
||||||
<mat-option *ngFor="let option of recommendationRemoveOptions" [value]="option.value">{{
|
|
||||||
option.label | translate
|
|
||||||
}}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-checkbox *ngIf="displayExtraOptionRemoveRecommendation" formControlName="removeRecommendationApplyToAll">{{
|
|
||||||
'dialog-defaults-form.extra-option-label' | translate
|
|
||||||
}}</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<h3>{{ 'dialog-defaults-form.hint.title' | translate }}</h3>
|
|
||||||
<div class="iqser-input-group w-450">
|
|
||||||
<label translate="dialog-defaults-form.hint.add-dialog"></label>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-select formControlName="addHint">
|
|
||||||
<mat-option *ngFor="let option of hintAddOptions" [value]="option.value">{{ option.label | translate }}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-checkbox *ngIf="displayExtraOptionAddHint" formControlName="addHintApplyToAll">{{
|
|
||||||
'dialog-defaults-form.extra-option-label' | translate
|
|
||||||
}}</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="iqser-input-group w-450">
|
|
||||||
<label translate="dialog-defaults-form.hint.remove-dialog"></label>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-select formControlName="removeHint">
|
|
||||||
<mat-option *ngFor="let option of removeOptions" [value]="option.value">{{ option.label | translate }}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-checkbox *ngIf="displayExtraOptionRemoveHint" formControlName="removeHintApplyToAll">{{
|
|
||||||
'dialog-defaults-form.extra-option-label' | translate
|
|
||||||
}}</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-actions">
|
|
||||||
<iqser-icon-button
|
|
||||||
(action)="save()"
|
|
||||||
[disabled]="!valid || !changed"
|
|
||||||
[label]="'preferences-screen.actions.save' | translate"
|
|
||||||
[type]="iconButtonTypes.primary"
|
|
||||||
></iqser-icon-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
mat-checkbox {
|
|
||||||
margin: 8px 0 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-top: 24px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .iqser-input-group:not(first-of-type) {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
import { NgForOf, NgIf } from '@angular/common';
|
|
||||||
import { ChangeDetectorRef, Component, inject } from '@angular/core';
|
|
||||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
|
||||||
import { MatFormField } from '@angular/material/form-field';
|
|
||||||
import { MatOption, MatSelect } from '@angular/material/select';
|
|
||||||
import { BaseFormComponent } from '@common-ui/form';
|
|
||||||
import { AsControl } from '@common-ui/utils';
|
|
||||||
import { IconButtonComponent } from '@iqser/common-ui';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
|
||||||
import {
|
|
||||||
RedactOrHintOption,
|
|
||||||
RedactOrHintOptions,
|
|
||||||
RemoveRedactionOption,
|
|
||||||
RemoveRedactionOptions,
|
|
||||||
} from '../../../../file-preview/utils/dialog-types';
|
|
||||||
import {
|
|
||||||
hintAddOptions,
|
|
||||||
recommendationRemoveOptions,
|
|
||||||
redactionAddOptions,
|
|
||||||
redactionRemoveOptions,
|
|
||||||
removeOptions,
|
|
||||||
SystemDefaultType,
|
|
||||||
} from '../../../utils/dialog-defaults';
|
|
||||||
|
|
||||||
interface DefaultOptionsForm {
|
|
||||||
addRedaction: RedactOrHintOption | SystemDefaultType;
|
|
||||||
addHint: RedactOrHintOption | SystemDefaultType;
|
|
||||||
removeRedaction: RemoveRedactionOption | SystemDefaultType;
|
|
||||||
removeRecommendation: RemoveRedactionOption | SystemDefaultType;
|
|
||||||
removeHint: RemoveRedactionOption | SystemDefaultType;
|
|
||||||
addRedactionApplyToAll: boolean;
|
|
||||||
removeRedactionApplyToAll: boolean;
|
|
||||||
removeRecommendationApplyToAll: boolean;
|
|
||||||
addHintApplyToAll: boolean;
|
|
||||||
removeHintApplyToAll: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'redaction-dialog-defaults',
|
|
||||||
templateUrl: './dialog-defaults.component.html',
|
|
||||||
styleUrl: './dialog-defaults.component.scss',
|
|
||||||
imports: [ReactiveFormsModule, TranslateModule, MatFormField, MatSelect, MatOption, NgForOf, MatCheckbox, NgIf, IconButtonComponent],
|
|
||||||
})
|
|
||||||
export class DialogDefaultsComponent extends BaseFormComponent {
|
|
||||||
readonly #formBuilder = inject(FormBuilder);
|
|
||||||
readonly #userPreferences = inject(UserPreferenceService);
|
|
||||||
readonly #changeDetectorRef = inject(ChangeDetectorRef);
|
|
||||||
form: FormGroup<AsControl<DefaultOptionsForm>> = this.#formBuilder.group({
|
|
||||||
addRedaction: this.#userPreferences.getAddRedactionDefaultOption(),
|
|
||||||
addHint: this.#userPreferences.getAddHintDefaultOption(),
|
|
||||||
removeRedaction: this.#userPreferences.getRemoveRedactionDefaultOption(),
|
|
||||||
removeRecommendation: this.#userPreferences.getRemoveRecommendationDefaultOption(),
|
|
||||||
removeHint: this.#userPreferences.getRemoveHintDefaultOption(),
|
|
||||||
addRedactionApplyToAll: this.#userPreferences.getBool(PreferencesKeys.addRedactionDefaultExtraOption),
|
|
||||||
removeRedactionApplyToAll: this.#userPreferences.getBool(PreferencesKeys.removeRedactionDefaultExtraOption),
|
|
||||||
removeRecommendationApplyToAll: this.#userPreferences.getBool(PreferencesKeys.removeRecommendationDefaultExtraOption),
|
|
||||||
addHintApplyToAll: this.#userPreferences.getBool(PreferencesKeys.addHintDefaultExtraOption),
|
|
||||||
removeHintApplyToAll: this.#userPreferences.getBool(PreferencesKeys.removeHintDefaultExtraOption),
|
|
||||||
});
|
|
||||||
initialFormValue = this.form.getRawValue();
|
|
||||||
|
|
||||||
readonly redactionAddOptions = redactionAddOptions;
|
|
||||||
readonly hintAddOptions = hintAddOptions;
|
|
||||||
readonly removeOptions = removeOptions;
|
|
||||||
readonly redactionRemoveOptions = redactionRemoveOptions;
|
|
||||||
readonly recommendationRemoveOptions = recommendationRemoveOptions;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayExtraOptionAddRedaction() {
|
|
||||||
return RedactOrHintOptions.IN_DOSSIER === this.form.controls.addRedaction.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayExtraOptionAddHint() {
|
|
||||||
return RedactOrHintOptions.IN_DOSSIER === this.form.controls.addHint.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayExtraOptionRemoveRedaction() {
|
|
||||||
return (
|
|
||||||
[RemoveRedactionOptions.IN_DOSSIER, RemoveRedactionOptions.FALSE_POSITIVE] as Partial<
|
|
||||||
RemoveRedactionOption | SystemDefaultType
|
|
||||||
>[]
|
|
||||||
).includes(this.form.controls.removeRedaction.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayExtraOptionRemoveHint() {
|
|
||||||
return RemoveRedactionOptions.IN_DOSSIER === this.form.controls.removeHint.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayExtraOptionRemoveRecommendation() {
|
|
||||||
return RemoveRedactionOptions.DO_NOT_RECOMMEND === this.form.controls.removeRecommendation.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async save(): Promise<any> {
|
|
||||||
const formValue = this.form.value;
|
|
||||||
|
|
||||||
if (this.initialFormValue.addRedaction !== this.form.controls.addRedaction.value) {
|
|
||||||
await this.#userPreferences.saveAddRedactionDefaultOption(this.form.controls.addRedaction.value);
|
|
||||||
}
|
|
||||||
if (this.initialFormValue.addHint !== this.form.controls.addHint.value) {
|
|
||||||
await this.#userPreferences.saveAddHintDefaultOption(this.form.controls.addHint.value);
|
|
||||||
}
|
|
||||||
if (this.initialFormValue.removeRedaction !== this.form.controls.removeRedaction.value) {
|
|
||||||
await this.#userPreferences.saveRemoveRedactionDefaultOption(this.form.controls.removeRedaction.value);
|
|
||||||
}
|
|
||||||
if (this.initialFormValue.removeRecommendation !== this.form.controls.removeRecommendation.value) {
|
|
||||||
await this.#userPreferences.saveRemoveRecommendationDefaultOption(this.form.controls.removeRecommendation.value);
|
|
||||||
}
|
|
||||||
if (this.initialFormValue.removeHint !== this.form.controls.removeHint.value) {
|
|
||||||
await this.#userPreferences.saveRemoveHintDefaultOption(this.form.controls.removeHint.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.displayExtraOptionAddRedaction) {
|
|
||||||
if (this.initialFormValue.addRedactionApplyToAll !== this.form.controls.addRedactionApplyToAll.value) {
|
|
||||||
await this.#userPreferences.saveAddRedactionDefaultExtraOption(this.form.controls.addRedactionApplyToAll.value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.#userPreferences.saveAddRedactionDefaultExtraOption('undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.displayExtraOptionAddHint) {
|
|
||||||
if (this.initialFormValue.addHintApplyToAll !== this.form.controls.addHintApplyToAll.value) {
|
|
||||||
await this.#userPreferences.saveAddHintDefaultExtraOption(this.form.controls.addHintApplyToAll.value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.#userPreferences.saveAddHintDefaultExtraOption('undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.displayExtraOptionRemoveRedaction) {
|
|
||||||
if (this.initialFormValue.removeRedactionApplyToAll !== this.form.controls.removeRedactionApplyToAll.value) {
|
|
||||||
await this.#userPreferences.saveRemoveRedactionDefaultExtraOption(this.form.controls.removeRedactionApplyToAll.value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.#userPreferences.saveRemoveRedactionDefaultExtraOption('undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.displayExtraOptionRemoveHint) {
|
|
||||||
if (this.initialFormValue.removeHintApplyToAll !== this.form.controls.removeHintApplyToAll.value) {
|
|
||||||
await this.#userPreferences.saveRemoveHintDefaultExtraOption(this.form.controls.removeHintApplyToAll.value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.#userPreferences.saveRemoveHintDefaultExtraOption('undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.displayExtraOptionRemoveRecommendation) {
|
|
||||||
if (this.initialFormValue.removeRecommendationApplyToAll !== this.form.controls.removeRecommendationApplyToAll.value) {
|
|
||||||
await this.#userPreferences.saveRemoveRecommendationDefaultExtraOption(
|
|
||||||
this.form.controls.removeRecommendationApplyToAll.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.#userPreferences.saveRemoveRecommendationDefaultExtraOption('undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.#userPreferences.reload();
|
|
||||||
this.#patchValues();
|
|
||||||
this.initialFormValue = this.form.getRawValue();
|
|
||||||
this.#changeDetectorRef.markForCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
#patchValues() {
|
|
||||||
this.form.patchValue({
|
|
||||||
addRedaction: this.#userPreferences.getAddRedactionDefaultOption(),
|
|
||||||
addHint: this.#userPreferences.getAddHintDefaultOption(),
|
|
||||||
removeRedaction: this.#userPreferences.getRemoveRedactionDefaultOption(),
|
|
||||||
removeRecommendation: this.#userPreferences.getRemoveRecommendationDefaultOption(),
|
|
||||||
removeHint: this.#userPreferences.getRemoveHintDefaultOption(),
|
|
||||||
addRedactionApplyToAll: this.#userPreferences.getBool(PreferencesKeys.addRedactionDefaultExtraOption),
|
|
||||||
removeRedactionApplyToAll: this.#userPreferences.getBool(PreferencesKeys.removeRedactionDefaultExtraOption),
|
|
||||||
removeRecommendationApplyToAll: this.#userPreferences.getBool(PreferencesKeys.removeRecommendationDefaultExtraOption),
|
|
||||||
addHintApplyToAll: this.#userPreferences.getBool(PreferencesKeys.addHintDefaultExtraOption),
|
|
||||||
removeHintApplyToAll: this.#userPreferences.getBool(PreferencesKeys.removeHintDefaultExtraOption),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,52 +1,45 @@
|
|||||||
<redaction-dialog-defaults *ngIf="currentScreen === screens.WARNING_PREFERENCES && !config.IS_DOCUMINE"></redaction-dialog-defaults>
|
<form (submit)="save()" [formGroup]="form">
|
||||||
|
<div class="dialog-content">
|
||||||
|
<div *ngIf="currentScreen === screens.WARNING_PREFERENCES" class="content-delimiter"></div>
|
||||||
|
<div class="dialog-content-left">
|
||||||
|
<ng-container *ngIf="currentScreen === screens.PREFERENCES">
|
||||||
|
<div class="iqser-input-group">
|
||||||
|
<mat-slide-toggle color="primary" formControlName="autoExpandFiltersOnActions">
|
||||||
|
{{ 'preferences-screen.form.auto-expand-filters-on-action' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
<div class="iqser-input-group">
|
||||||
|
<mat-slide-toggle color="primary" formControlName="displaySuggestionsInPreview">
|
||||||
|
{{ 'preferences-screen.form.show-suggestions-in-preview' | translate }}
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="currentScreen === screens.WARNING_PREFERENCES">
|
||||||
|
<p class="warnings-subtitle">{{ 'preferences-screen.warnings-subtitle' | translate }}</p>
|
||||||
|
<p class="warnings-description">{{ 'preferences-screen.warnings-description' | translate }}</p>
|
||||||
|
|
||||||
<div [ngClass]="currentScreen === screens.WARNING_PREFERENCES && 'dialog'">
|
<div class="iqser-input-group">
|
||||||
<form [formGroup]="form">
|
<mat-checkbox color="primary" formControlName="unapprovedSuggestionsWarning">
|
||||||
<div class="dialog-content">
|
{{ 'preferences-screen.form.unapproved-suggestions-warning' | translate }}
|
||||||
<div class="dialog-content-left">
|
</mat-checkbox>
|
||||||
<ng-container *ngIf="currentScreen === screens.PREFERENCES">
|
</div>
|
||||||
<div class="iqser-input-group">
|
|
||||||
<mat-slide-toggle color="primary" formControlName="autoExpandFiltersOnActions">
|
|
||||||
{{ 'preferences-screen.form.auto-expand-filters-on-action' | translate }}
|
|
||||||
</mat-slide-toggle>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *allow="roles.getTables" class="iqser-input-group">
|
<div *ngIf="userPreferenceService.areDevFeaturesEnabled" class="iqser-input-group">
|
||||||
<label [translate]="'preferences-screen.form.table-extraction-type'"></label>
|
<mat-checkbox color="primary" formControlName="loadAllAnnotationsWarning">
|
||||||
<input formControlName="tableExtractionType" />
|
{{ 'preferences-screen.form.load-all-annotations-warning' | translate }}
|
||||||
</div>
|
</mat-checkbox>
|
||||||
</ng-container>
|
</div>
|
||||||
|
</ng-container>
|
||||||
<ng-container *ngIf="currentScreen === screens.WARNING_PREFERENCES">
|
|
||||||
<h1>{{ 'preferences-screen.warnings-subtitle' | translate }}</h1>
|
|
||||||
<p class="warnings-description">{{ 'preferences-screen.warnings-description' | translate }}</p>
|
|
||||||
|
|
||||||
<div class="iqser-input-group">
|
|
||||||
<mat-checkbox color="primary" formControlName="loadAllAnnotationsWarning">
|
|
||||||
{{ 'preferences-screen.form.load-all-annotations-warning' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="iqser-input-group">
|
|
||||||
<mat-checkbox color="primary" formControlName="helpModeDialog">
|
|
||||||
{{ 'preferences-screen.form.help-mode-dialog' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="iqser-input-group">
|
|
||||||
<mat-checkbox color="primary" formControlName="overwriteFileOption">
|
|
||||||
{{ 'preferences-screen.form.overwrite-file-option' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="save()"
|
(action)="save()"
|
||||||
[disabled]="!valid || !changed"
|
[disabled]="!valid || !changed"
|
||||||
[label]="'preferences-screen.actions.save' | translate"
|
[label]="'preferences-screen.actions.save' | translate"
|
||||||
[type]="iconButtonTypes.primary"
|
[submit]="true"
|
||||||
></iqser-icon-button>
|
[type]="iconButtonTypes.primary"
|
||||||
</div>
|
></iqser-icon-button>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
|||||||
@ -1,3 +1,16 @@
|
|||||||
|
@use 'variables';
|
||||||
|
|
||||||
|
.content-delimiter {
|
||||||
|
border-top: 1px solid var(--iqser-separator);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warnings-subtitle {
|
||||||
|
font-size: var(--iqser-font-size);
|
||||||
|
color: var(--iqser-text);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.warnings-description {
|
.warnings-description {
|
||||||
width: 105%;
|
width: 105%;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +1,22 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import {
|
|
||||||
BaseFormComponent,
|
|
||||||
getConfig,
|
|
||||||
IconButtonComponent,
|
|
||||||
IqserAllowDirective,
|
|
||||||
IqserPermissionsService,
|
|
||||||
isIqserDevMode,
|
|
||||||
KEYS,
|
|
||||||
LoadingService,
|
|
||||||
} from '@iqser/common-ui';
|
|
||||||
import { AsControl } from '@iqser/common-ui/lib/utils';
|
|
||||||
import { Roles } from '@users/roles';
|
|
||||||
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { DialogDefaultsComponent } from './dialog-defaults/dialog-defaults.component';
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
||||||
import { NgClass, NgIf } from '@angular/common';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
import { BaseFormComponent, IqserPermissionsService } from '@iqser/common-ui';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { ROLES } from '@users/roles';
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
|
||||||
|
|
||||||
interface PreferencesForm {
|
interface PreferencesForm {
|
||||||
// preferences
|
// preferences
|
||||||
autoExpandFiltersOnActions: boolean;
|
autoExpandFiltersOnActions: boolean;
|
||||||
tableExtractionType: string;
|
displaySuggestionsInPreview: boolean;
|
||||||
// warnings preferences
|
// warnings preferences
|
||||||
|
unapprovedSuggestionsWarning: boolean;
|
||||||
loadAllAnnotationsWarning: boolean;
|
loadAllAnnotationsWarning: boolean;
|
||||||
helpModeDialog: boolean;
|
|
||||||
overwriteFileOption: boolean;
|
|
||||||
|
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AsControl<T> = { [K in keyof T]: FormControl<T[K]> };
|
||||||
type Screen = 'preferences' | 'warnings-preferences';
|
type Screen = 'preferences' | 'warnings-preferences';
|
||||||
|
|
||||||
const Screens = {
|
const Screens = {
|
||||||
@ -44,114 +29,66 @@ const Screens = {
|
|||||||
templateUrl: './preferences.component.html',
|
templateUrl: './preferences.component.html',
|
||||||
styleUrls: ['./preferences.component.scss'],
|
styleUrls: ['./preferences.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
|
||||||
DialogDefaultsComponent,
|
|
||||||
NgClass,
|
|
||||||
NgIf,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
MatSlideToggle,
|
|
||||||
TranslateModule,
|
|
||||||
IqserAllowDirective,
|
|
||||||
MatCheckbox,
|
|
||||||
IconButtonComponent,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class PreferencesComponent extends BaseFormComponent implements OnInit {
|
export class PreferencesComponent extends BaseFormComponent {
|
||||||
readonly #formBuilder = inject(FormBuilder);
|
|
||||||
readonly #permissionsService = inject(IqserPermissionsService);
|
|
||||||
readonly #changeRef = inject(ChangeDetectorRef);
|
|
||||||
readonly #loadingService = inject(LoadingService);
|
|
||||||
readonly #userPreferenceService = inject(UserPreferenceService);
|
|
||||||
|
|
||||||
readonly form: FormGroup<AsControl<PreferencesForm>>;
|
readonly form: FormGroup<AsControl<PreferencesForm>>;
|
||||||
readonly currentScreen: Screen;
|
readonly currentScreen: Screen;
|
||||||
readonly screens = Screens;
|
readonly screens = Screens;
|
||||||
initialFormValue: PreferencesForm;
|
initialFormValue: PreferencesForm;
|
||||||
readonly roles = Roles;
|
|
||||||
readonly config = getConfig();
|
|
||||||
readonly isIqserDevMode = isIqserDevMode();
|
|
||||||
|
|
||||||
get #isOverwriteFileOptionActive() {
|
constructor(
|
||||||
return !(this.#userPreferenceService.getOverwriteFileOption() === 'undefined');
|
readonly userPreferenceService: UserPreferenceService,
|
||||||
}
|
private readonly _formBuilder: FormBuilder,
|
||||||
|
private readonly _permissionsService: IqserPermissionsService,
|
||||||
constructor(route: ActivatedRoute) {
|
private readonly _route: ActivatedRoute,
|
||||||
|
private readonly _changeRef: ChangeDetectorRef,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.form = this.#formBuilder.group({
|
this.form = this._formBuilder.group({
|
||||||
// preferences
|
// preferences
|
||||||
autoExpandFiltersOnActions: [this.#userPreferenceService.getAutoExpandFiltersOnActions()],
|
autoExpandFiltersOnActions: [this.userPreferenceService.getAutoExpandFiltersOnActions()],
|
||||||
tableExtractionType: [this.#userPreferenceService.getTableExtractionType()],
|
displaySuggestionsInPreview: [this.userPreferenceService.getDisplaySuggestionsInPreview()],
|
||||||
// warnings preferences
|
// warnings preferences
|
||||||
loadAllAnnotationsWarning: [this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)],
|
unapprovedSuggestionsWarning: [this.userPreferenceService.getUnapprovedSuggestionsWarning()],
|
||||||
helpModeDialog: [this.#userPreferenceService.getBool(KEYS.helpModeDialog)],
|
loadAllAnnotationsWarning: [this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)],
|
||||||
overwriteFileOption: [this.#isOverwriteFileOptionActive],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.#permissionsService.has(Roles.managePreferences)) {
|
if (!this._permissionsService.has(ROLES.managePreferences)) {
|
||||||
this.form.disable();
|
this.form.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.#permissionsService.has(Roles.getTables)) {
|
|
||||||
this.form.controls.tableExtractionType.disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.#isOverwriteFileOptionActive) {
|
|
||||||
this.form.controls.overwriteFileOption.disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.initialFormValue = this.form.getRawValue();
|
this.initialFormValue = this.form.getRawValue();
|
||||||
this.currentScreen = route.snapshot.data.screen;
|
this.currentScreen = _route.snapshot.data.screen;
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.#loadingService.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(): Promise<any> {
|
async save(): Promise<any> {
|
||||||
if (this.form.controls.autoExpandFiltersOnActions.value !== this.#userPreferenceService.getAutoExpandFiltersOnActions()) {
|
if (this.form.controls.autoExpandFiltersOnActions.value !== this.userPreferenceService.getAutoExpandFiltersOnActions()) {
|
||||||
await this.#userPreferenceService.toggleAutoExpandFiltersOnActions();
|
await this.userPreferenceService.toggleAutoExpandFiltersOnActions();
|
||||||
}
|
}
|
||||||
|
if (this.form.controls.displaySuggestionsInPreview.value !== this.userPreferenceService.getDisplaySuggestionsInPreview()) {
|
||||||
if (this.form.controls.tableExtractionType.value !== this.#userPreferenceService.getTableExtractionType()) {
|
await this.userPreferenceService.toggleDisplaySuggestionsInPreview();
|
||||||
await this.#userPreferenceService.save(PreferencesKeys.tableExtractionType, this.form.controls.tableExtractionType.value);
|
}
|
||||||
|
if (this.form.controls.unapprovedSuggestionsWarning.value !== this.userPreferenceService.getUnapprovedSuggestionsWarning()) {
|
||||||
|
await this.userPreferenceService.toggleUnapprovedSuggestionsWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.form.controls.loadAllAnnotationsWarning.value !==
|
this.form.controls.loadAllAnnotationsWarning.value !==
|
||||||
this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)
|
this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)
|
||||||
) {
|
) {
|
||||||
await this.#userPreferenceService.save(
|
await this.userPreferenceService.save(
|
||||||
PreferencesKeys.loadAllAnnotationsWarning,
|
PreferencesKeys.loadAllAnnotationsWarning,
|
||||||
String(this.form.controls.loadAllAnnotationsWarning.value),
|
String(this.form.controls.loadAllAnnotationsWarning.value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.form.controls.helpModeDialog.value !== this.#userPreferenceService.getBool(KEYS.helpModeDialog)) {
|
await this.userPreferenceService.reload();
|
||||||
await this.#userPreferenceService.save(KEYS.helpModeDialog, String(this.form.controls.helpModeDialog.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.form.controls.overwriteFileOption.enabled && !this.form.controls.overwriteFileOption.value) {
|
|
||||||
await this.#userPreferenceService.saveOverwriteFileOption('undefined');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.#userPreferenceService.reload();
|
|
||||||
this.#patchValues();
|
|
||||||
|
|
||||||
this.initialFormValue = this.form.getRawValue();
|
|
||||||
this.#changeRef.markForCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
#patchValues() {
|
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
autoExpandFiltersOnActions: this.#userPreferenceService.getAutoExpandFiltersOnActions(),
|
autoExpandFiltersOnActions: this.userPreferenceService.getAutoExpandFiltersOnActions(),
|
||||||
tableExtractionType: this.#userPreferenceService.getTableExtractionType(),
|
displaySuggestionsInPreview: this.userPreferenceService.getDisplaySuggestionsInPreview(),
|
||||||
loadAllAnnotationsWarning: this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning),
|
unapprovedSuggestionsWarning: this.userPreferenceService.getUnapprovedSuggestionsWarning(),
|
||||||
helpModeDialog: this.#userPreferenceService.getBool(KEYS.helpModeDialog),
|
|
||||||
overwriteFileOption: this.#isOverwriteFileOptionActive,
|
|
||||||
});
|
});
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
if (!this.#isOverwriteFileOptionActive) {
|
this._changeRef.markForCheck();
|
||||||
this.form.controls.overwriteFileOption.disable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { BaseDialogComponent, CircleButtonComponent, IconButtonComponent } from '@iqser/common-ui';
|
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||||
import { MatDialogRef } from '@angular/material/dialog';
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
import { AbstractControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { AbstractControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
interface FormType {
|
interface FormType {
|
||||||
password: AbstractControl<string>;
|
password: AbstractControl<string>;
|
||||||
@ -10,7 +9,6 @@ interface FormType {
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './confirm-password-dialog.component.html',
|
templateUrl: './confirm-password-dialog.component.html',
|
||||||
imports: [ReactiveFormsModule, IconButtonComponent, TranslateModule, CircleButtonComponent],
|
|
||||||
})
|
})
|
||||||
export class ConfirmPasswordDialogComponent extends BaseDialogComponent {
|
export class ConfirmPasswordDialogComponent extends BaseDialogComponent {
|
||||||
constructor(protected readonly _dialogRef: MatDialogRef<ConfirmPasswordDialogComponent>) {
|
constructor(protected readonly _dialogRef: MatDialogRef<ConfirmPasswordDialogComponent>) {
|
||||||
|
|||||||
@ -16,37 +16,30 @@
|
|||||||
<input formControlName="lastName" name="lastName" type="text" />
|
<input formControlName="lastName" name="lastName" type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group">
|
<div *ngIf="userPreferences.areDevFeaturesEnabled" class="iqser-input-group">
|
||||||
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
|
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
|
||||||
<mat-form-field>
|
<mat-select formControlName="language">
|
||||||
<mat-select formControlName="language">
|
<mat-option *ngFor="let language of languages" [value]="language">
|
||||||
<mat-select-trigger>{{ languageSelectLabel() | translate }}</mat-select-trigger>
|
{{ translations[language] | translate }}
|
||||||
@for (language of languages; track language) {
|
</mat-option>
|
||||||
<mat-option [value]="language">
|
</mat-select>
|
||||||
{{ translations[language] | translate }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="iqser-input-group">
|
<div class="iqser-input-group">
|
||||||
<a (click)="resetPassword()" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
|
<a [href]="changePasswordUrl" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (devMode) {
|
<div *ngIf="devMode" class="iqser-input-group">
|
||||||
<div class="iqser-input-group">
|
<mat-slide-toggle color="primary" formControlName="darkTheme">
|
||||||
<mat-slide-toggle color="primary" formControlName="darkTheme">
|
{{ 'user-profile-screen.form.dark-theme' | translate }}
|
||||||
{{ 'user-profile-screen.form.dark-theme' | translate }}
|
</mat-slide-toggle>
|
||||||
</mat-slide-toggle>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
[disabled]="disabled"
|
[disabled]="form.invalid || !(profileChanged || languageChanged || themeChanged)"
|
||||||
[label]="'user-profile-screen.actions.save' | translate"
|
[label]="'user-profile-screen.actions.save' | translate"
|
||||||
[submit]="true"
|
[submit]="true"
|
||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
|
|||||||
@ -1,65 +1,36 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { FormGroup, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||||
import {
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
BaseFormComponent,
|
import { BaseFormComponent, IqserPermissionsService, LanguageService, LoadingService, Toaster } from '@iqser/common-ui';
|
||||||
IconButtonComponent,
|
|
||||||
IqserPermissionsService,
|
|
||||||
LanguageService,
|
|
||||||
LoadingService,
|
|
||||||
Toaster,
|
|
||||||
} from '@iqser/common-ui';
|
|
||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
|
||||||
import { IProfile } from '@red/domain';
|
import { IProfile } from '@red/domain';
|
||||||
import { languagesTranslations } from '@translations/languages-translations';
|
import { languagesTranslations } from '@translations/languages-translations';
|
||||||
import { Roles } from '@users/roles';
|
|
||||||
import { UserPreferenceService } from '@users/user-preference.service';
|
|
||||||
import { UserService } from '@users/user.service';
|
import { UserService } from '@users/user.service';
|
||||||
|
import { ConfigService } from '@services/config.service';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
import { UserPreferenceService } from '@users/user-preference.service';
|
||||||
|
import { ROLES } from '@users/roles';
|
||||||
import { UserProfileDialogService } from '../services/user-profile-dialog.service';
|
import { UserProfileDialogService } from '../services/user-profile-dialog.service';
|
||||||
import { MatFormField } from '@angular/material/form-field';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
|
|
||||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
|
||||||
import { PdfViewer } from '../../../../pdf-viewer/services/pdf-viewer.service';
|
|
||||||
import { formControlToSignal } from '@utils/functions';
|
|
||||||
import { AsControl } from '@common-ui/utils';
|
|
||||||
|
|
||||||
interface UserProfileForm {
|
|
||||||
email: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
language: string;
|
|
||||||
darkTheme: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
selector: 'redaction-user-profile-screen',
|
||||||
templateUrl: './user-profile-screen.component.html',
|
templateUrl: './user-profile-screen.component.html',
|
||||||
styleUrls: ['./user-profile-screen.component.scss'],
|
styleUrls: ['./user-profile-screen.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
|
||||||
ReactiveFormsModule,
|
|
||||||
MatFormField,
|
|
||||||
MatSelect,
|
|
||||||
MatOption,
|
|
||||||
TranslateModule,
|
|
||||||
MatSlideToggle,
|
|
||||||
IconButtonComponent,
|
|
||||||
MatSelectTrigger,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class UserProfileScreenComponent extends BaseFormComponent {
|
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
|
||||||
readonly form: FormGroup<AsControl<UserProfileForm>> = this.#getForm();
|
|
||||||
initialFormValue = this.form.getRawValue();
|
|
||||||
readonly translations = languagesTranslations;
|
readonly translations = languagesTranslations;
|
||||||
readonly devMode = this._userPreferenceService.isIqserDevMode;
|
readonly devMode = this._userPreferenceService.areDevFeaturesEnabled;
|
||||||
|
readonly changePasswordUrl: SafeResourceUrl;
|
||||||
|
|
||||||
readonly profileKeys = ['email', 'firstName', 'lastName'];
|
#profileModel: IProfile;
|
||||||
readonly languages = this._translateService.langs;
|
|
||||||
readonly language = formControlToSignal<UserProfileForm['language']>(this.form.controls.language);
|
|
||||||
readonly languageSelectLabel = computed(() => this.translations[this.language()]);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
domSanitizer: DomSanitizer,
|
||||||
|
configService: ConfigService,
|
||||||
private readonly _userService: UserService,
|
private readonly _userService: UserService,
|
||||||
|
readonly userPreferences: UserPreferenceService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _dialogService: UserProfileDialogService,
|
private readonly _dialogService: UserProfileDialogService,
|
||||||
private readonly _formBuilder: UntypedFormBuilder,
|
private readonly _formBuilder: UntypedFormBuilder,
|
||||||
@ -69,38 +40,55 @@ export class UserProfileScreenComponent extends BaseFormComponent {
|
|||||||
protected readonly _userPreferenceService: UserPreferenceService,
|
protected readonly _userPreferenceService: UserPreferenceService,
|
||||||
private readonly _changeRef: ChangeDetectorRef,
|
private readonly _changeRef: ChangeDetectorRef,
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
private readonly _pdfViewer: PdfViewer,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
if (!this._permissionsService.has(Roles.updateMyProfile)) {
|
this._loadingService.start();
|
||||||
this.form.disable();
|
this.changePasswordUrl = domSanitizer.bypassSecurityTrustResourceUrl(`${configService.values.OAUTH_URL}/account/password`);
|
||||||
}
|
|
||||||
this._loadingService.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get languageChanged(): boolean {
|
get languageChanged(): boolean {
|
||||||
return this.initialFormValue['language'] !== this.form.controls.language.value;
|
return this.#profileModel['language'] !== this.form.get('language').value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get themeChanged(): boolean {
|
get themeChanged(): boolean {
|
||||||
return this.initialFormValue['darkTheme'] !== this.form.controls.darkTheme.value;
|
return this.#profileModel['darkTheme'] !== this.form.get('darkTheme').value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get emailChanged(): boolean {
|
get emailChanged(): boolean {
|
||||||
return this.initialFormValue['email'] !== this.form.controls.email.value;
|
return this.#profileModel['email'] !== this.form.get('email').value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get profileChanged(): boolean {
|
get profileChanged(): boolean {
|
||||||
return this.profileKeys.some(key => this.initialFormValue[key] !== this.form.get(key).value);
|
const keys = Object.keys(this.form.getRawValue());
|
||||||
|
keys.splice(keys.indexOf('language'), 1);
|
||||||
|
keys.splice(keys.indexOf('darkTheme'), 1);
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
if (this.#profileModel[key] !== this.form.get(key).value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get languages(): string[] {
|
||||||
|
return this._translateService.langs;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this._initializeForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(): Promise<void> {
|
async save(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (this.profileChanged) {
|
if (this.profileChanged) {
|
||||||
const value = this.form.getRawValue() as IProfile;
|
const value = this.form.getRawValue() as IProfile;
|
||||||
|
// delete value.language;
|
||||||
|
// delete value.darkTheme;
|
||||||
|
|
||||||
if (this.emailChanged) {
|
if (this.emailChanged) {
|
||||||
const dialogRef = this._dialogService.openDialog('confirmPassword');
|
const dialogRef = this._dialogService.openDialog('confirmPassword', null, null);
|
||||||
const password = await firstValueFrom(dialogRef.afterClosed());
|
const password = await firstValueFrom(dialogRef.afterClosed());
|
||||||
if (!password) {
|
if (!password) {
|
||||||
return;
|
return;
|
||||||
@ -116,34 +104,50 @@ export class UserProfileScreenComponent extends BaseFormComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.languageChanged) {
|
if (this.languageChanged) {
|
||||||
await this._languageService.change(this.form.controls.language.value);
|
await this._languageService.change(this.form.get('language').value);
|
||||||
await this._pdfViewer.instance?.UI.setLanguage(this._languageService.currentLanguage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.themeChanged) {
|
if (this.themeChanged) {
|
||||||
await this._userPreferenceService.saveTheme(this.form.controls.darkTheme.value ? 'dark' : 'light');
|
await this._userPreferenceService.saveTheme(this.form.get('darkTheme').value ? 'dark' : 'light');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initialFormValue = this.form.getRawValue();
|
this._initializeForm();
|
||||||
this._changeRef.markForCheck();
|
|
||||||
this._loadingService.stop();
|
|
||||||
this._toaster.success(_('user-profile-screen.update.success'));
|
this._toaster.success(_('user-profile-screen.update.success'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetPassword() {
|
private _getForm(): UntypedFormGroup {
|
||||||
await this._userService.createResetPasswordAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
#getForm() {
|
|
||||||
return this._formBuilder.group({
|
return this._formBuilder.group({
|
||||||
email: [this._userService.currentUser.email ?? '', [Validators.required, Validators.email]],
|
email: ['', [Validators.required, Validators.email]],
|
||||||
firstName: [this._userService.currentUser.firstName ?? ''],
|
firstName: [''],
|
||||||
lastName: [this._userService.currentUser.lastName ?? ''],
|
lastName: [''],
|
||||||
language: [this._userPreferenceService.getLanguage()],
|
language: [''],
|
||||||
darkTheme: [this._userPreferenceService.getTheme() === 'dark'],
|
darkTheme: [false],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _initializeForm(): void {
|
||||||
|
try {
|
||||||
|
this.form = this._getForm();
|
||||||
|
if (!this._permissionsService.has(ROLES.updateMyProfile)) {
|
||||||
|
this.form.disable();
|
||||||
|
}
|
||||||
|
this.#profileModel = {
|
||||||
|
email: this._userService.currentUser.email ?? '',
|
||||||
|
firstName: this._userService.currentUser.firstName ?? '',
|
||||||
|
lastName: this._userService.currentUser.lastName ?? '',
|
||||||
|
language: this._languageService.currentLanguage ?? '',
|
||||||
|
darkTheme: this._userPreferenceService.getTheme() === 'dark',
|
||||||
|
};
|
||||||
|
this.form.patchValue(this.#profileModel, { emitEvent: false });
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
this._loadingService.stop();
|
||||||
|
this._changeRef.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
|
||||||
|
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ConfirmPasswordDialogComponent } from './confirm-password-dialog/confirm-password-dialog.component';
|
||||||
|
import { UserProfileDialogService } from './services/user-profile-dialog.service';
|
||||||
|
|
||||||
|
const routes = [{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard] }];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [UserProfileScreenComponent, ConfirmPasswordDialogComponent],
|
||||||
|
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, TranslateModule],
|
||||||
|
providers: [UserProfileDialogService],
|
||||||
|
})
|
||||||
|
export class UserProfileModule {}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
|
|
||||||
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
|
||||||
import { UserProfileDialogService } from './services/user-profile-dialog.service';
|
|
||||||
import { IqserRoutes } from '@iqser/common-ui';
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard], providers: [UserProfileDialogService] },
|
|
||||||
] satisfies IqserRoutes;
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { addHintTranslations } from '@translations/add-hint-translations';
|
|
||||||
import { redactTextTranslations } from '@translations/redact-text-translations';
|
|
||||||
import { removeRedactionTranslations } from '@translations/remove-redaction-translations';
|
|
||||||
import {
|
|
||||||
ForceAnnotationOptions,
|
|
||||||
RectangleRedactOptions,
|
|
||||||
RedactOrHintOptions,
|
|
||||||
RemoveRedactionOptions,
|
|
||||||
} from '../../file-preview/utils/dialog-types';
|
|
||||||
|
|
||||||
export const SystemDefaults = {
|
|
||||||
RECTANGLE_REDACT_DEFAULT: RectangleRedactOptions.ONLY_THIS_PAGE,
|
|
||||||
ADD_REDACTION_DEFAULT: RedactOrHintOptions.IN_DOSSIER,
|
|
||||||
ADD_HINT_DEFAULT: RedactOrHintOptions.IN_DOSSIER,
|
|
||||||
FORCE_REDACTION_DEFAULT: ForceAnnotationOptions.ONLY_HERE,
|
|
||||||
REMOVE_REDACTION_DEFAULT: RemoveRedactionOptions.ONLY_HERE,
|
|
||||||
REMOVE_HINT_DEFAULT: RemoveRedactionOptions.ONLY_HERE,
|
|
||||||
REMOVE_RECOMMENDATION_DEFAULT: RemoveRedactionOptions.DO_NOT_RECOMMEND,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const SystemDefaultOption = {
|
|
||||||
SYSTEM_DEFAULT: 'SYSTEM_DEFAULT',
|
|
||||||
label: _('dialog-defaults-form.system-default'),
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type SystemDefaultType = typeof SystemDefaultOption.SYSTEM_DEFAULT;
|
|
||||||
|
|
||||||
export const redactionAddOptions = [
|
|
||||||
{
|
|
||||||
label: SystemDefaultOption.label,
|
|
||||||
value: SystemDefaultOption.SYSTEM_DEFAULT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: redactTextTranslations.onlyHere.label,
|
|
||||||
value: RedactOrHintOptions.ONLY_HERE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: redactTextTranslations.inDocument.label,
|
|
||||||
value: RedactOrHintOptions.IN_DOCUMENT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: redactTextTranslations.inDossier.label,
|
|
||||||
value: RedactOrHintOptions.IN_DOSSIER,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const hintAddOptions = [
|
|
||||||
{
|
|
||||||
label: SystemDefaultOption.label,
|
|
||||||
value: SystemDefaultOption.SYSTEM_DEFAULT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: addHintTranslations.onlyHere.label,
|
|
||||||
value: RedactOrHintOptions.ONLY_HERE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: addHintTranslations.inDossier.label,
|
|
||||||
value: RedactOrHintOptions.IN_DOSSIER,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const removeOptions = [
|
|
||||||
{
|
|
||||||
label: SystemDefaultOption.label,
|
|
||||||
value: SystemDefaultOption.SYSTEM_DEFAULT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: removeRedactionTranslations.ONLY_HERE.label,
|
|
||||||
value: RemoveRedactionOptions.ONLY_HERE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: removeRedactionTranslations.IN_DOSSIER.label,
|
|
||||||
value: RemoveRedactionOptions.IN_DOSSIER,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const redactionRemoveOptions = [
|
|
||||||
...removeOptions,
|
|
||||||
{
|
|
||||||
label: removeRedactionTranslations.FALSE_POSITIVE.label,
|
|
||||||
value: RemoveRedactionOptions.FALSE_POSITIVE,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const recommendationRemoveOptions = [
|
|
||||||
{
|
|
||||||
label: SystemDefaultOption.label,
|
|
||||||
value: SystemDefaultOption.SYSTEM_DEFAULT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: removeRedactionTranslations.DO_NOT_RECOMMEND.label,
|
|
||||||
value: RemoveRedactionOptions.DO_NOT_RECOMMEND,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
264
apps/red-ui/src/app/modules/admin/admin-routing.module.ts
Normal file
264
apps/red-ui/src/app/modules/admin/admin-routing.module.ts
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CompositeRouteGuard, IqserAuthGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
|
||||||
|
import { RedRoleGuard } from '@users/red-role.guard';
|
||||||
|
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
|
||||||
|
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
||||||
|
import { FileAttributesListingScreenComponent } from './screens/file-attributes-listing/file-attributes-listing-screen.component';
|
||||||
|
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
|
||||||
|
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
|
||||||
|
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
|
||||||
|
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { DossierAttributesListingScreenComponent } from './screens/dossier-attributes-listing/dossier-attributes-listing-screen.component';
|
||||||
|
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
|
||||||
|
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
|
||||||
|
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
||||||
|
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
||||||
|
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
||||||
|
import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard';
|
||||||
|
import { EntityExistsGuard } from '@guards/entity-exists-guard.service';
|
||||||
|
import { DossierStatesListingScreenComponent } from './screens/dossier-states-listing/dossier-states-listing-screen.component';
|
||||||
|
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
|
||||||
|
import { PermissionsGuard } from '@guards/permissions-guard';
|
||||||
|
import { ROLES } from '@users/roles';
|
||||||
|
|
||||||
|
const dossierTemplateIdRoutes: IqserRoutes = [
|
||||||
|
{
|
||||||
|
path: 'info',
|
||||||
|
component: BaseDossierTemplateScreenComponent,
|
||||||
|
loadChildren: () => import('./screens/info/dossier-template-info.module').then(m => m.DossierTemplateInfoModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'entities',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: EntitiesListingScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `:${ENTITY_TYPE}`,
|
||||||
|
component: BaseEntityScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard],
|
||||||
|
loadChildren: () => import('./screens/entities/entities.module').then(m => m.EntitiesModule),
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard, EntityExistsGuard],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'rules',
|
||||||
|
component: BaseDossierTemplateScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.rules.read],
|
||||||
|
redirectTo: 'info',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'file-attributes',
|
||||||
|
component: FileAttributesListingScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'watermarks',
|
||||||
|
component: BaseDossierTemplateScreenComponent,
|
||||||
|
loadChildren: () => import('./screens/watermark/watermark.module').then(m => m.WatermarkModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'reports',
|
||||||
|
component: BaseDossierTemplateScreenComponent,
|
||||||
|
loadChildren: () => import('./screens/reports/reports.module').then(m => m.ReportsModule),
|
||||||
|
canActivate: [IqserAuthGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.reportTemplates.read],
|
||||||
|
redirectTo: '/auth-error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dossier-attributes',
|
||||||
|
component: DossierAttributesListingScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dossier-states',
|
||||||
|
component: DossierStatesListingScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'default-colors',
|
||||||
|
component: DefaultColorsScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'justifications',
|
||||||
|
component: BaseDossierTemplateScreenComponent,
|
||||||
|
loadChildren: () => import('./screens/justifications/justifications.module').then(m => m.JustificationsModule),
|
||||||
|
canActivate: [IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.legalBasis.read],
|
||||||
|
redirectTo: '/auth-error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ path: '', redirectTo: 'info', pathMatch: 'full' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const dossierTemplatesRoutes: IqserRoutes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: BaseAdminScreenComponent,
|
||||||
|
canActivate: [IqserAuthGuard],
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./screens/dossier-templates-listing/dossier-templates-listing.module').then(m => m.DossierTemplatesListingModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `:${DOSSIER_TEMPLATE_ID}`,
|
||||||
|
children: dossierTemplateIdRoutes,
|
||||||
|
canActivate: [CompositeRouteGuard],
|
||||||
|
data: { routeGuards: [DossierTemplateExistsGuard] },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const routes: IqserRoutes = [
|
||||||
|
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
|
||||||
|
{
|
||||||
|
path: 'dossier-templates',
|
||||||
|
children: dossierTemplatesRoutes,
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard],
|
||||||
|
requiredRoles: ['RED_MANAGER', 'RED_ADMIN'],
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.templates.read],
|
||||||
|
redirectTo: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'users',
|
||||||
|
component: BaseAdminScreenComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: UserListingScreenComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.users.read, 'RED_USER_ADMIN'],
|
||||||
|
redirectTo: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dossier-permissions',
|
||||||
|
component: BaseAdminScreenComponent,
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard, PermissionsGuard],
|
||||||
|
permissionsObject: 'Dossier',
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.manageAclPermissions, 'RED_ADMIN'],
|
||||||
|
redirectTo: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loadChildren: () => import('./screens/permissions/permissions.module').then(m => m.PermissionsModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'license-info',
|
||||||
|
component: BaseAdminScreenComponent,
|
||||||
|
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
|
||||||
|
data: {
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.license.readReport, 'RED_ADMIN'],
|
||||||
|
redirectTo: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loadChildren: () => import('./screens/license/license.module').then(m => m.LicenseModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'digital-signature',
|
||||||
|
component: BaseAdminScreenComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: DigitalSignatureScreenComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
|
||||||
|
data: {
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.digitalSignature.read, 'RED_ADMIN'],
|
||||||
|
redirectTo: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'audit',
|
||||||
|
component: BaseAdminScreenComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AuditScreenComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
|
||||||
|
data: {
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.searchAudit, 'RED_ADMIN'],
|
||||||
|
redirectTo: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'general-config',
|
||||||
|
component: BaseAdminScreenComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: GeneralConfigScreenComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||||
|
canDeactivate: [PendingChangesGuard],
|
||||||
|
data: {
|
||||||
|
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||||
|
permissions: {
|
||||||
|
allow: [ROLES.generalConfiguration.read, ROLES.smtp.read, 'RED_ADMIN'],
|
||||||
|
redirectTo: '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class AdminRoutingModule {}
|
||||||
@ -2,8 +2,8 @@
|
|||||||
<ng-container *ngFor="let item of items[type]">
|
<ng-container *ngFor="let item of items[type]">
|
||||||
<a
|
<a
|
||||||
*ngIf="item.show"
|
*ngIf="item.show"
|
||||||
[attr.help-mode-key]="item.helpModeKey"
|
|
||||||
[class.disabled]="isDisabled(item.screen)"
|
[class.disabled]="isDisabled(item.screen)"
|
||||||
|
[iqserHelpMode]="item.helpModeKey"
|
||||||
[routerLinkActiveOptions]="{ exact: false }"
|
[routerLinkActiveOptions]="{ exact: false }"
|
||||||
[routerLink]="prefix + item.screen"
|
[routerLink]="prefix + item.screen"
|
||||||
class="item"
|
class="item"
|
||||||
@ -1,14 +1,11 @@
|
|||||||
import { NgForOf, NgIf } from '@angular/common';
|
|
||||||
import { Component, HostBinding, Input, OnInit } from '@angular/core';
|
import { Component, HostBinding, Input, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
|
import { UserPreferenceService } from '@users/user-preference.service';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { getConfig, IqserPermissionsService, isIqserDevMode } from '@iqser/common-ui';
|
|
||||||
import { SideNavComponent } from '@iqser/common-ui/lib/shared';
|
|
||||||
import { getCurrentUser } from '@iqser/common-ui/lib/users';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { AdminSideNavType, AdminSideNavTypes, AppConfig, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User, WATERMARK_ID } from '@red/domain';
|
|
||||||
import { adminSideNavTranslations } from '@translations/admin-side-nav-translations';
|
import { adminSideNavTranslations } from '@translations/admin-side-nav-translations';
|
||||||
import { Roles } from '@users/roles';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { AdminSideNavType, AdminSideNavTypes, ENTITY_TYPE, User } from '@red/domain';
|
||||||
|
import { ROLES } from '@users/roles';
|
||||||
|
import { getCurrentUser, IqserPermissionsService } from '@iqser/common-ui';
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
@ -21,61 +18,57 @@ interface NavItem {
|
|||||||
selector: 'redaction-admin-side-nav [type]',
|
selector: 'redaction-admin-side-nav [type]',
|
||||||
templateUrl: './admin-side-nav.component.html',
|
templateUrl: './admin-side-nav.component.html',
|
||||||
styleUrls: ['./admin-side-nav.component.scss'],
|
styleUrls: ['./admin-side-nav.component.scss'],
|
||||||
imports: [TranslateModule, NgIf, RouterLink, RouterLinkActive, NgForOf, SideNavComponent],
|
|
||||||
})
|
})
|
||||||
export class AdminSideNavComponent implements OnInit {
|
export class AdminSideNavComponent implements OnInit {
|
||||||
readonly isIqserDevMode = isIqserDevMode();
|
|
||||||
@Input() type: AdminSideNavType;
|
@Input() type: AdminSideNavType;
|
||||||
@Input() disabledItems: string[] = [];
|
@Input() disabledItems: string[] = [];
|
||||||
readonly translations = adminSideNavTranslations;
|
readonly translations = adminSideNavTranslations;
|
||||||
readonly currentUser = getCurrentUser<User>();
|
readonly currentUser = getCurrentUser<User>();
|
||||||
readonly roles = Roles;
|
readonly roles = ROLES;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
readonly #config = getConfig<AppConfig>();
|
|
||||||
readonly isDocumine = this.#config.IS_DOCUMINE;
|
|
||||||
readonly canAccessRulesInDocumine = this.isDocumine && !this.#config.RULE_EDITOR_DEV_ONLY;
|
|
||||||
readonly items: { readonly [key in AdminSideNavType]: NavItem[] } = {
|
readonly items: { readonly [key in AdminSideNavType]: NavItem[] } = {
|
||||||
settings: [
|
settings: [
|
||||||
{
|
{
|
||||||
screen: 'dossier-templates',
|
screen: 'dossier-templates',
|
||||||
label: _('admin-side-nav.dossier-templates'),
|
label: _('admin-side-nav.dossier-templates'),
|
||||||
show: (this.currentUser.isManager || this.currentUser.isAdmin) && this._permissionsService.has(Roles.templates.read),
|
show: (this.currentUser.isManager || this.currentUser.isAdmin) && this._permissionsService.has(ROLES.templates.read),
|
||||||
helpModeKey: 'dossier_templates',
|
helpModeKey: 'dossier_templates',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'digital-signature',
|
screen: 'digital-signature',
|
||||||
label: _('admin-side-nav.digital-signature'),
|
label: _('admin-side-nav.digital-signature'),
|
||||||
show: this.currentUser.isAdmin && this._permissionsService.has(Roles.digitalSignature.read) && !this.isDocumine,
|
show: this.currentUser.isAdmin && this._permissionsService.has(ROLES.digitalSignature.read),
|
||||||
helpModeKey: 'digital_signature',
|
helpModeKey: 'digital_signature',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'license-info',
|
screen: 'license-info',
|
||||||
label: _('admin-side-nav.license-information'),
|
label: _('admin-side-nav.license-information'),
|
||||||
show: this.currentUser.isAdmin && this._permissionsService.has(Roles.license.readReport),
|
show: this.currentUser.isAdmin && this._permissionsService.has(ROLES.license.readReport),
|
||||||
helpModeKey: 'license_information',
|
helpModeKey: 'license_information',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'audit',
|
screen: 'audit',
|
||||||
label: _('admin-side-nav.audit'),
|
label: _('admin-side-nav.audit'),
|
||||||
show: this.currentUser.isAdmin && this._permissionsService.has(Roles.searchAudit),
|
show: this.currentUser.isAdmin && this._permissionsService.has(ROLES.searchAudit),
|
||||||
helpModeKey: 'audit',
|
helpModeKey: 'audit',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'users',
|
screen: 'users',
|
||||||
label: _('admin-side-nav.user-management'),
|
label: _('admin-side-nav.user-management'),
|
||||||
show: this.currentUser.isUserAdmin && this._permissionsService.has(Roles.users.read),
|
show: this.currentUser.isUserAdmin && this._permissionsService.has(ROLES.users.read),
|
||||||
helpModeKey: 'user_management',
|
helpModeKey: 'user_management',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'dossier-permissions',
|
screen: 'dossier-permissions',
|
||||||
label: _('dossier-permissions'),
|
label: _('dossier-permissions'),
|
||||||
show: this.currentUser.isAdmin && this._permissionsService.has(Roles.manageAclPermissions),
|
show: this.currentUser.isAdmin && this._permissionsService.has(ROLES.manageAclPermissions),
|
||||||
helpModeKey: 'dossier_permissions',
|
helpModeKey: 'dossier_permissions',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'general-config',
|
screen: 'general-config',
|
||||||
label: _('admin-side-nav.configurations'),
|
label: _('admin-side-nav.configurations'),
|
||||||
show: this.currentUser.isAdmin && this._permissionsService.has([Roles.generalConfiguration.read, Roles.smtp.read]),
|
show: this.currentUser.isAdmin && this._permissionsService.has([ROLES.generalConfiguration.read, ROLES.smtp.read]),
|
||||||
helpModeKey: 'configurations',
|
helpModeKey: 'configurations',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -83,41 +76,19 @@ export class AdminSideNavComponent implements OnInit {
|
|||||||
{
|
{
|
||||||
screen: 'info',
|
screen: 'info',
|
||||||
label: _('admin-side-nav.dossier-template-info'),
|
label: _('admin-side-nav.dossier-template-info'),
|
||||||
helpModeKey: this.currentUser.isAdmin ? 'dossier_templates_info' : 'user_dossier_template_info',
|
helpModeKey: 'dossier_templates_info',
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'entities',
|
screen: 'entities',
|
||||||
label: _('admin-side-nav.entities'),
|
label: _('admin-side-nav.entities'),
|
||||||
helpModeKey: this.currentUser.isAdmin ? 'entities' : 'user_dossier_template_entities',
|
helpModeKey: 'entities',
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'entity-rules',
|
screen: 'rules',
|
||||||
label: _('admin-side-nav.entity-rule-editor'),
|
label: _('admin-side-nav.rule-editor'),
|
||||||
helpModeKey: 'rule_editors',
|
show: this.userPreferenceService.areDevFeaturesEnabled && this._permissionsService.has(ROLES.rules.read),
|
||||||
show: (this.isIqserDevMode || this.canAccessRulesInDocumine) && this._permissionsService.has(Roles.rules.read),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
screen: 'component-rules',
|
|
||||||
label: _('admin-side-nav.component-rule-editor'),
|
|
||||||
helpModeKey: 'rule_editors',
|
|
||||||
show:
|
|
||||||
this.isDocumine &&
|
|
||||||
(this.isIqserDevMode || this.canAccessRulesInDocumine) &&
|
|
||||||
this._permissionsService.has(Roles.rules.read),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
screen: 'component-mappings',
|
|
||||||
label: _('admin-side-nav.component-mappings'),
|
|
||||||
helpModeKey: 'component_mappings',
|
|
||||||
show: this.isDocumine,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
screen: 'components',
|
|
||||||
label: _('admin-side-nav.components'),
|
|
||||||
helpModeKey: 'components_management',
|
|
||||||
show: this.isDocumine,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'default-colors',
|
screen: 'default-colors',
|
||||||
@ -129,7 +100,7 @@ export class AdminSideNavComponent implements OnInit {
|
|||||||
screen: 'watermarks',
|
screen: 'watermarks',
|
||||||
label: _('admin-side-nav.watermarks'),
|
label: _('admin-side-nav.watermarks'),
|
||||||
helpModeKey: 'watermarks',
|
helpModeKey: 'watermarks',
|
||||||
show: this._permissionsService.has(Roles.watermarks.read) && !this.isDocumine,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'file-attributes',
|
screen: 'file-attributes',
|
||||||
@ -146,19 +117,19 @@ export class AdminSideNavComponent implements OnInit {
|
|||||||
{
|
{
|
||||||
screen: 'dossier-states',
|
screen: 'dossier-states',
|
||||||
label: _('admin-side-nav.dossier-states'),
|
label: _('admin-side-nav.dossier-states'),
|
||||||
helpModeKey: this.currentUser.isAdmin ? 'dossier_states' : 'user_dossier_template_dossier_states',
|
helpModeKey: 'dossier_states',
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'reports',
|
screen: 'reports',
|
||||||
label: _('admin-side-nav.reports'),
|
label: _('admin-side-nav.reports'),
|
||||||
show: this._permissionsService.has([Roles.reportTemplates.read]),
|
show: this._permissionsService.has([ROLES.reportTemplates.read]),
|
||||||
helpModeKey: this.currentUser.isAdmin ? 'admin_reports' : 'user_dossier_template_reports',
|
helpModeKey: 'reports',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
screen: 'justifications',
|
screen: 'justifications',
|
||||||
label: _('admin-side-nav.justifications'),
|
label: _('admin-side-nav.justifications'),
|
||||||
show: this._permissionsService.has([Roles.legalBasis.read]) && !this.isDocumine,
|
show: this._permissionsService.has([ROLES.legalBasis.read]),
|
||||||
helpModeKey: 'justifications',
|
helpModeKey: 'justifications',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -166,7 +137,6 @@ export class AdminSideNavComponent implements OnInit {
|
|||||||
{
|
{
|
||||||
screen: 'info',
|
screen: 'info',
|
||||||
label: _('admin-side-nav.entity-info'),
|
label: _('admin-side-nav.entity-info'),
|
||||||
helpModeKey: 'entity_info',
|
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -193,6 +163,7 @@ export class AdminSideNavComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly _permissionsService: IqserPermissionsService,
|
private readonly _permissionsService: IqserPermissionsService,
|
||||||
private readonly _route: ActivatedRoute,
|
private readonly _route: ActivatedRoute,
|
||||||
|
readonly userPreferenceService: UserPreferenceService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@HostBinding('class.smaller') get isSmaller(): boolean {
|
@HostBinding('class.smaller') get isSmaller(): boolean {
|
||||||
@ -200,20 +171,7 @@ export class AdminSideNavComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const dossierTemplate = this._route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
|
this.prefix = this._route.snapshot.paramMap.get(ENTITY_TYPE) ? '' : '../';
|
||||||
const watermark = this._route.snapshot.paramMap.get(WATERMARK_ID);
|
|
||||||
const entity = this._route.snapshot.paramMap.get(ENTITY_TYPE);
|
|
||||||
if (!dossierTemplate) {
|
|
||||||
this.prefix = '/main/admin/';
|
|
||||||
} else {
|
|
||||||
this.prefix = `/main/admin/dossier-templates/${dossierTemplate}/`;
|
|
||||||
if (entity) {
|
|
||||||
this.prefix = this.prefix + `entities/${entity}/`;
|
|
||||||
}
|
|
||||||
if (watermark) {
|
|
||||||
this.prefix = this.prefix + `watermarks/${watermark}/`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isDisabled(screen: string): boolean {
|
isDisabled(screen: string): boolean {
|
||||||
137
apps/red-ui/src/app/modules/admin/admin.module.ts
Normal file
137
apps/red-ui/src/app/modules/admin/admin.module.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { AdminRoutingModule } from './admin-routing.module';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
|
||||||
|
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
|
||||||
|
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
|
||||||
|
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
|
||||||
|
import { FileAttributesListingScreenComponent } from './screens/file-attributes-listing/file-attributes-listing-screen.component';
|
||||||
|
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
|
||||||
|
import { DossierTemplateBreadcrumbsComponent } from './components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
|
||||||
|
import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
|
||||||
|
import { AddEditCloneDossierTemplateDialogComponent } from './dialogs/add-edit-dossier-template-dialog/add-edit-clone-dossier-template-dialog.component';
|
||||||
|
import { AddEntityDialogComponent } from './dialogs/add-entity-dialog/add-entity-dialog.component';
|
||||||
|
import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component';
|
||||||
|
import { AdminDialogService } from './services/admin-dialog.service';
|
||||||
|
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
|
||||||
|
import { SmtpAuthDialogComponent } from './dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
|
||||||
|
import { AddEditUserDialogComponent } from './dialogs/add-edit-user-dialog/add-edit-user-dialog.component';
|
||||||
|
import { UsersStatsComponent } from './components/users-stats/users-stats.component';
|
||||||
|
import { FileAttributesCsvImportDialogComponent } from './dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component';
|
||||||
|
import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component';
|
||||||
|
import { ResetPasswordComponent } from './dialogs/add-edit-user-dialog/reset-password/reset-password.component';
|
||||||
|
import { UserDetailsComponent } from './dialogs/add-edit-user-dialog/user-details/user-details.component';
|
||||||
|
import { AddEditDossierAttributeDialogComponent } from './dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component';
|
||||||
|
import { DossierAttributesListingScreenComponent } from './screens/dossier-attributes-listing/dossier-attributes-listing-screen.component';
|
||||||
|
import { AuditService } from './services/audit.service';
|
||||||
|
import { DigitalSignatureService } from './services/digital-signature.service';
|
||||||
|
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
|
||||||
|
import { RulesService } from './services/rules.service';
|
||||||
|
import { SmtpConfigService } from './services/smtp-config.service';
|
||||||
|
import { UploadDictionaryDialogComponent } from './dialogs/upload-dictionary-dialog/upload-dictionary-dialog.component';
|
||||||
|
import { GeneralConfigFormComponent } from './screens/general-config/general-config-form/general-config-form.component';
|
||||||
|
import { SmtpFormComponent } from './screens/general-config/smtp-form/smtp-form.component';
|
||||||
|
import { FileAttributesConfigurationsDialogComponent } from './dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component';
|
||||||
|
import { SharedAdminModule } from './shared/shared-admin.module';
|
||||||
|
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
||||||
|
import { DossierStatesListingScreenComponent } from './screens/dossier-states-listing/dossier-states-listing-screen.component';
|
||||||
|
import { AddEditDossierStateDialogComponent } from './dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component';
|
||||||
|
import { A11yModule } from '@angular/cdk/a11y';
|
||||||
|
import { ConfirmDeleteDossierStateDialogComponent } from './dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component';
|
||||||
|
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
|
||||||
|
import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component';
|
||||||
|
import { SystemPreferencesFormComponent } from './screens/general-config/system-preferences-form/system-preferences-form.component';
|
||||||
|
import { ConfigureCertificateDialogComponent } from './dialogs/configure-digital-signature-dialog/configure-certificate-dialog.component';
|
||||||
|
import { PkcsSignatureConfigurationComponent } from './dialogs/configure-digital-signature-dialog/form/pkcs-signature-configuration/pkcs-signature-configuration.component';
|
||||||
|
import { KmsSignatureConfigurationComponent } from './dialogs/configure-digital-signature-dialog/form/kms-signature-configuration/kms-signature-configuration.component';
|
||||||
|
import {
|
||||||
|
HumanizeCamelCasePipe,
|
||||||
|
HumanizePipe,
|
||||||
|
IqserButtonsModule,
|
||||||
|
IqserEmptyStatesModule,
|
||||||
|
IqserHelpModeModule,
|
||||||
|
IqserInputsModule,
|
||||||
|
IqserListingModule,
|
||||||
|
IqserPermissionsModule,
|
||||||
|
IqserScrollbarModule,
|
||||||
|
IqserSharedModule,
|
||||||
|
IqserUploadFileModule,
|
||||||
|
IqserUsersModule,
|
||||||
|
} from '@iqser/common-ui';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AuditInfoDialogComponent } from './dialogs/audit-info-dialog/audit-info-dialog.component';
|
||||||
|
|
||||||
|
const dialogs = [
|
||||||
|
AddEditCloneDossierTemplateDialogComponent,
|
||||||
|
AddEntityDialogComponent,
|
||||||
|
AddEditFileAttributeDialogComponent,
|
||||||
|
EditColorDialogComponent,
|
||||||
|
SmtpAuthDialogComponent,
|
||||||
|
AddEditUserDialogComponent,
|
||||||
|
FileAttributesConfigurationsDialogComponent,
|
||||||
|
FileAttributesCsvImportDialogComponent,
|
||||||
|
AddEditDossierAttributeDialogComponent,
|
||||||
|
UploadDictionaryDialogComponent,
|
||||||
|
AddEditDossierStateDialogComponent,
|
||||||
|
ConfirmDeleteDossierStateDialogComponent,
|
||||||
|
ConfigureCertificateDialogComponent,
|
||||||
|
AuditInfoDialogComponent,
|
||||||
|
];
|
||||||
|
|
||||||
|
const screens = [
|
||||||
|
AuditScreenComponent,
|
||||||
|
DefaultColorsScreenComponent,
|
||||||
|
EntitiesListingScreenComponent,
|
||||||
|
DigitalSignatureScreenComponent,
|
||||||
|
FileAttributesListingScreenComponent,
|
||||||
|
UserListingScreenComponent,
|
||||||
|
GeneralConfigScreenComponent,
|
||||||
|
DossierAttributesListingScreenComponent,
|
||||||
|
DossierStatesListingScreenComponent,
|
||||||
|
];
|
||||||
|
|
||||||
|
const components = [
|
||||||
|
DossierTemplateBreadcrumbsComponent,
|
||||||
|
UsersStatsComponent,
|
||||||
|
AdminSideNavComponent,
|
||||||
|
ActiveFieldsListingComponent,
|
||||||
|
ResetPasswordComponent,
|
||||||
|
UserDetailsComponent,
|
||||||
|
BaseAdminScreenComponent,
|
||||||
|
BaseDossierTemplateScreenComponent,
|
||||||
|
BaseEntityScreenComponent,
|
||||||
|
GeneralConfigFormComponent,
|
||||||
|
SmtpFormComponent,
|
||||||
|
SystemPreferencesFormComponent,
|
||||||
|
PkcsSignatureConfigurationComponent,
|
||||||
|
KmsSignatureConfigurationComponent,
|
||||||
|
|
||||||
|
...dialogs,
|
||||||
|
...screens,
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [...components],
|
||||||
|
providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, SmtpConfigService],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
AdminRoutingModule,
|
||||||
|
SharedAdminModule,
|
||||||
|
A11yModule,
|
||||||
|
IqserUsersModule,
|
||||||
|
TranslateModule,
|
||||||
|
HumanizePipe,
|
||||||
|
IqserButtonsModule,
|
||||||
|
IqserListingModule,
|
||||||
|
IqserScrollbarModule,
|
||||||
|
IqserInputsModule,
|
||||||
|
IqserUploadFileModule,
|
||||||
|
IqserEmptyStatesModule,
|
||||||
|
IqserSharedModule,
|
||||||
|
IqserHelpModeModule,
|
||||||
|
IqserPermissionsModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AdminModule {}
|
||||||
@ -1,302 +0,0 @@
|
|||||||
import { inject, provideEnvironmentInitializer } from '@angular/core';
|
|
||||||
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
|
|
||||||
import { templateExistsWhenEnteringAdmin } from '@guards/dossier-template-exists.guard';
|
|
||||||
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
|
|
||||||
import { entityExistsGuard } from '@guards/entity-exists-guard.service';
|
|
||||||
import { PermissionsGuard } from '@guards/permissions-guard';
|
|
||||||
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
|
|
||||||
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
|
|
||||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
|
||||||
import { CopilotService } from '@services/copilot.service';
|
|
||||||
import { RedRoleGuard } from '@users/red-role.guard';
|
|
||||||
import { Roles } from '@users/roles';
|
|
||||||
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
|
|
||||||
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
|
|
||||||
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
|
|
||||||
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
|
|
||||||
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
|
|
||||||
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
|
|
||||||
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
|
|
||||||
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
|
|
||||||
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
|
|
||||||
import { AdminDialogService } from './services/admin-dialog.service';
|
|
||||||
import { AuditService } from './services/audit.service';
|
|
||||||
import { DigitalSignatureService } from './services/digital-signature.service';
|
|
||||||
import { RulesService } from './services/rules.service';
|
|
||||||
import { SmtpConfigService } from './services/smtp-config.service';
|
|
||||||
|
|
||||||
const entityRoutes: IqserRoutes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: BaseDossierTemplateScreenComponent,
|
|
||||||
children: [{ path: '', component: EntitiesListingScreenComponent }],
|
|
||||||
canActivate: [CompositeRouteGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `:${ENTITY_TYPE}`,
|
|
||||||
component: BaseEntityScreenComponent,
|
|
||||||
canActivate: [CompositeRouteGuard, entityExistsGuard()],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
},
|
|
||||||
loadChildren: () => import('./screens/entities/entities.routes'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const dossierTemplateIdRoutes: IqserRoutes = [
|
|
||||||
{ path: '', redirectTo: 'info', pathMatch: 'full' },
|
|
||||||
{
|
|
||||||
path: 'info',
|
|
||||||
component: BaseDossierTemplateScreenComponent,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
canDeactivate: [PendingChangesGuard],
|
|
||||||
loadComponent: () => import('./screens/info/dossier-template-info-screen/dossier-template-info-screen.component'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'entities',
|
|
||||||
children: entityRoutes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: BaseDossierTemplateScreenComponent,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'entity-rules',
|
|
||||||
loadComponent: () => import('./screens/rules/rules-screen/rules-screen.component'),
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
|
||||||
canDeactivate: [PendingChangesGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.rules.read],
|
|
||||||
redirectTo: 'info',
|
|
||||||
},
|
|
||||||
type: 'ENTITY',
|
|
||||||
},
|
|
||||||
providers: [
|
|
||||||
RulesService,
|
|
||||||
provideEnvironmentInitializer(() => {
|
|
||||||
return inject(CopilotService).connectAsync('/api/llm/llm-websocket');
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'component-rules',
|
|
||||||
loadComponent: () => import('./screens/rules/rules-screen/rules-screen.component'),
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
|
||||||
canDeactivate: [PendingChangesGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.rules.read],
|
|
||||||
redirectTo: 'info',
|
|
||||||
},
|
|
||||||
type: 'COMPONENT',
|
|
||||||
},
|
|
||||||
providers: [RulesService],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'component-mappings',
|
|
||||||
loadComponent: () => import('./screens/component-mappings/component-mappings-screen.component'),
|
|
||||||
canActivate: [CompositeRouteGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'components',
|
|
||||||
loadComponent: () => import('./screens/component-definitions/component-definitions.component'),
|
|
||||||
canActivate: [CompositeRouteGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'file-attributes',
|
|
||||||
loadComponent: () => import('./screens/file-attributes-listing/file-attributes-listing-screen.component'),
|
|
||||||
canActivate: [CompositeRouteGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'watermarks',
|
|
||||||
loadChildren: () => import('./screens/watermark/watermark.routes'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'reports',
|
|
||||||
loadComponent: () => import('./screens/reports/reports-screen/reports-screen.component'),
|
|
||||||
canActivate: [IqserAuthGuard, IqserPermissionsGuard],
|
|
||||||
data: {
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.reportTemplates.read],
|
|
||||||
redirectTo: '/auth-error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dossier-attributes',
|
|
||||||
loadComponent: () => import('./screens/dossier-attributes-listing/dossier-attributes-listing-screen.component'),
|
|
||||||
canActivate: [CompositeRouteGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dossier-states',
|
|
||||||
loadComponent: () =>
|
|
||||||
import('./screens/dossier-states-listing/dossier-states-listing-screen/dossier-states-listing-screen.component'),
|
|
||||||
canActivate: [CompositeRouteGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'default-colors',
|
|
||||||
component: DefaultColorsScreenComponent,
|
|
||||||
canActivate: [CompositeRouteGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'justifications',
|
|
||||||
loadComponent: () => import('./screens/justifications/justifications-screen/justifications-screen.component'),
|
|
||||||
canActivate: [IqserPermissionsGuard],
|
|
||||||
data: {
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.legalBasis.read],
|
|
||||||
redirectTo: '/auth-error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const dossierTemplatesRoutes: IqserRoutes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: BaseAdminScreenComponent,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
loadComponent: () =>
|
|
||||||
import(
|
|
||||||
'./screens/dossier-templates-listing/dossier-templates-listing-screen/dossier-templates-listing-screen.component'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
canActivate: [IqserAuthGuard],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: `:${DOSSIER_TEMPLATE_ID}`,
|
|
||||||
children: dossierTemplateIdRoutes,
|
|
||||||
canActivate: [templateExistsWhenEnteringAdmin()],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
|
|
||||||
{
|
|
||||||
path: 'dossier-templates',
|
|
||||||
children: dossierTemplatesRoutes,
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard],
|
|
||||||
requiredRoles: ['RED_MANAGER', 'RED_ADMIN'],
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.templates.read],
|
|
||||||
redirectTo: '/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: BaseAdminScreenComponent,
|
|
||||||
providers: [AdminDialogService],
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'users',
|
|
||||||
component: UserListingScreenComponent,
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.users.read, 'RED_USER_ADMIN'],
|
|
||||||
redirectTo: '/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dossier-permissions',
|
|
||||||
loadComponent: () => import('./screens/permissions/permissions-screen/permissions-screen.component'),
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard, PermissionsGuard],
|
|
||||||
permissionsObject: 'Dossier',
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.manageAclPermissions, 'RED_ADMIN'],
|
|
||||||
redirectTo: '/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'license-info',
|
|
||||||
loadChildren: () => import('./screens/license/license.routes'),
|
|
||||||
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
|
|
||||||
data: {
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.license.readReport, 'RED_ADMIN'],
|
|
||||||
redirectTo: '/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'digital-signature',
|
|
||||||
component: DigitalSignatureScreenComponent,
|
|
||||||
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
|
|
||||||
data: {
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.digitalSignature.read, 'RED_ADMIN'],
|
|
||||||
redirectTo: '/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
providers: [DigitalSignatureService],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'audit',
|
|
||||||
component: AuditScreenComponent,
|
|
||||||
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
|
|
||||||
data: {
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.searchAudit, 'RED_ADMIN'],
|
|
||||||
redirectTo: '/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
providers: [AuditService],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'general-config',
|
|
||||||
component: GeneralConfigScreenComponent,
|
|
||||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
|
||||||
canDeactivate: [PendingChangesGuard],
|
|
||||||
data: {
|
|
||||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
|
||||||
permissions: {
|
|
||||||
allow: [Roles.generalConfiguration.read, Roles.smtp.read, 'RED_ADMIN'],
|
|
||||||
redirectTo: '/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
providers: [SmtpConfigService],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] satisfies IqserRoutes;
|
|
||||||
@ -1,10 +1,8 @@
|
|||||||
import { Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { AdminSideNavComponent } from '../shared/components/admin-side-nav/admin-side-nav.component';
|
|
||||||
import { RouterOutlet } from '@angular/router';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './base-admin-screen.component.html',
|
templateUrl: './base-admin-screen.component.html',
|
||||||
styleUrls: ['./base-admin-screen.component.scss'],
|
styleUrls: ['./base-admin-screen.component.scss'],
|
||||||
imports: [AdminSideNavComponent, RouterOutlet],
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class BaseAdminScreenComponent {}
|
export class BaseAdminScreenComponent {}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
<!--TODO: Use this for all dossier template screens -->
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
|
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
|
||||||
@ -8,7 +10,6 @@
|
|||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
[routerLink]="['../..']"
|
[routerLink]="['../..']"
|
||||||
[tooltip]="'common.close' | translate"
|
[tooltip]="'common.close' | translate"
|
||||||
buttonId="close-view-btn"
|
|
||||||
icon="iqser:close"
|
icon="iqser:close"
|
||||||
tooltipPosition="below"
|
tooltipPosition="below"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
|||||||
@ -1,22 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { DossierTemplateBreadcrumbsComponent } from '../shared/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
|
|
||||||
import { DossierTemplateActionsComponent } from '../shared/components/dossier-template-actions/dossier-template-actions.component';
|
|
||||||
import { CircleButtonComponent } from '@iqser/common-ui';
|
|
||||||
import { RouterLink, RouterOutlet } from '@angular/router';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { AdminSideNavComponent } from '../shared/components/admin-side-nav/admin-side-nav.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './base-dossier-template-screen.component.html',
|
templateUrl: './base-dossier-template-screen.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
|
||||||
DossierTemplateBreadcrumbsComponent,
|
|
||||||
DossierTemplateActionsComponent,
|
|
||||||
CircleButtonComponent,
|
|
||||||
RouterLink,
|
|
||||||
TranslateModule,
|
|
||||||
AdminSideNavComponent,
|
|
||||||
RouterOutlet,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class BaseDossierTemplateScreenComponent {}
|
export class BaseDossierTemplateScreenComponent {}
|
||||||
|
|||||||
@ -1,39 +1,24 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
||||||
import { Router, RouterLink, RouterOutlet } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { firstValueFrom, Observable } from 'rxjs';
|
import { firstValueFrom, Observable } from 'rxjs';
|
||||||
import { AdminDialogService } from '../services/admin-dialog.service';
|
import { AdminDialogService } from '../services/admin-dialog.service';
|
||||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||||
import { CircleButtonComponent, LoadingService } from '@iqser/common-ui';
|
import { getParam, LoadingService } from '@iqser/common-ui';
|
||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { getParam } from '@iqser/common-ui/lib/utils';
|
|
||||||
import { DossierTemplateBreadcrumbsComponent } from '../shared/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
|
|
||||||
import { AsyncPipe, NgIf } from '@angular/common';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { AdminSideNavComponent } from '../shared/components/admin-side-nav/admin-side-nav.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './base-entity-screen.component.html',
|
templateUrl: './base-entity-screen.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
|
||||||
DossierTemplateBreadcrumbsComponent,
|
|
||||||
CircleButtonComponent,
|
|
||||||
NgIf,
|
|
||||||
AsyncPipe,
|
|
||||||
TranslateModule,
|
|
||||||
RouterLink,
|
|
||||||
AdminSideNavComponent,
|
|
||||||
RouterOutlet,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class BaseEntityScreenComponent implements OnInit {
|
export class BaseEntityScreenComponent {
|
||||||
readonly disabledItems$: Observable<string[]>;
|
readonly disabledItems$: Observable<string[]>;
|
||||||
readonly canDeleteEntity$: Observable<boolean>;
|
readonly canDeleteEntity$: Observable<boolean>;
|
||||||
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
|
readonly #dossierTemplateId: string = getParam(DOSSIER_TEMPLATE_ID);
|
||||||
readonly #entityType = getParam(ENTITY_TYPE);
|
readonly #entityType: string = getParam(ENTITY_TYPE);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _router: Router,
|
private readonly _router: Router,
|
||||||
@ -47,22 +32,16 @@ export class BaseEntityScreenComponent implements OnInit {
|
|||||||
const entity$ = dictionaryMapService.watch$(this.#dossierTemplateId, this.#entityType);
|
const entity$ = dictionaryMapService.watch$(this.#dossierTemplateId, this.#entityType);
|
||||||
this.canDeleteEntity$ = entity$.pipe(map(entity => this._permissionsService.canDeleteEntities(entity)));
|
this.canDeleteEntity$ = entity$.pipe(map(entity => this._permissionsService.canDeleteEntities(entity)));
|
||||||
this.disabledItems$ = entity$.pipe(
|
this.disabledItems$ = entity$.pipe(
|
||||||
map(entity =>
|
map(entity => (entity.hasDictionary ? [] : ['dictionary', 'false-positive', 'false-recommendations'])),
|
||||||
entity.hasDictionary && !entity.dossierDictionaryOnly ? [] : ['dictionary', 'false-positive', 'false-recommendations'],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this._loadingService.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
openDeleteDictionariesDialog() {
|
openDeleteDictionariesDialog() {
|
||||||
this._dialogService.openDialog('confirm', null, async () => {
|
this._dialogService.openDialog('confirm', null, null, async () => {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
const dossierTemplate = this._dossierTemplatesService.find(this.#dossierTemplateId);
|
const dossierTemplate = this._dossierTemplatesService.find(this.#dossierTemplateId);
|
||||||
await firstValueFrom(this._dictionaryService.deleteDictionaries([this.#entityType], this.#dossierTemplateId));
|
await firstValueFrom(this._dictionaryService.deleteDictionaries([this.#entityType], this.#dossierTemplateId));
|
||||||
await this._router.navigate([`/${dossierTemplate.routerLink}/entities`]);
|
await this._router.navigate([`${dossierTemplate.routerLink}/entities`]);
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,18 +2,14 @@ import { Component, Input } from '@angular/core';
|
|||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Dictionary, DOSSIER_TEMPLATE_ID, DossierTemplate, ENTITY_TYPE } from '@red/domain';
|
import { Dictionary, DOSSIER_TEMPLATE_ID, DossierTemplate, ENTITY_TYPE } from '@red/domain';
|
||||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||||
import { AsyncPipe, NgIf } from '@angular/common';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-dossier-template-breadcrumbs',
|
selector: 'redaction-dossier-template-breadcrumbs',
|
||||||
templateUrl: './dossier-template-breadcrumbs.component.html',
|
templateUrl: './dossier-template-breadcrumbs.component.html',
|
||||||
styleUrls: ['./dossier-template-breadcrumbs.component.scss'],
|
styleUrls: ['./dossier-template-breadcrumbs.component.scss'],
|
||||||
imports: [NgIf, AsyncPipe, RouterLink, MatIconModule, TranslateModule, RouterLinkActive],
|
|
||||||
})
|
})
|
||||||
export class DossierTemplateBreadcrumbsComponent {
|
export class DossierTemplateBreadcrumbsComponent {
|
||||||
@Input() root = false;
|
@Input() root = false;
|
||||||
@ -1,6 +1,5 @@
|
|||||||
<div class="collapsed-wrapper">
|
<div class="collapsed-wrapper">
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
buttonId="user-stats-expand-btn"
|
|
||||||
(action)="toggleCollapse.emit()"
|
(action)="toggleCollapse.emit()"
|
||||||
[tooltip]="'user-stats.expand' | translate"
|
[tooltip]="'user-stats.expand' | translate"
|
||||||
icon="iqser:expand"
|
icon="iqser:expand"
|
||||||
@ -13,7 +12,6 @@
|
|||||||
<div [translate]="'user-stats.title'" class="heading-xl flex-1"></div>
|
<div [translate]="'user-stats.title'" class="heading-xl flex-1"></div>
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
buttonId="user-stats-collapse-btn"
|
|
||||||
(action)="toggleCollapse.emit()"
|
(action)="toggleCollapse.emit()"
|
||||||
[tooltip]="'user-stats.collapse' | translate"
|
[tooltip]="'user-stats.collapse' | translate"
|
||||||
icon="iqser:collapse"
|
icon="iqser:collapse"
|
||||||
@ -23,7 +21,7 @@
|
|||||||
|
|
||||||
<div class="mt-44">
|
<div class="mt-44">
|
||||||
<redaction-donut-chart
|
<redaction-donut-chart
|
||||||
[config]="chartConfig()"
|
[config]="chartConfig"
|
||||||
[radius]="63"
|
[radius]="63"
|
||||||
[strokeWidth]="15"
|
[strokeWidth]="15"
|
||||||
[subtitles]="['user-stats.chart.users' | translate]"
|
[subtitles]="['user-stats.chart.users' | translate]"
|
||||||
|
|||||||
@ -1,16 +1,12 @@
|
|||||||
import { Component, EventEmitter, input, Input, output, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { DonutChartConfig } from '@red/domain';
|
import { DonutChartConfig } from '@red/domain';
|
||||||
import { CircleButtonComponent } from '@iqser/common-ui';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-users-stats',
|
selector: 'redaction-users-stats',
|
||||||
templateUrl: './users-stats.component.html',
|
templateUrl: './users-stats.component.html',
|
||||||
styleUrls: ['./users-stats.component.scss'],
|
styleUrls: ['./users-stats.component.scss'],
|
||||||
imports: [CircleButtonComponent, TranslateModule, DonutChartComponent],
|
|
||||||
})
|
})
|
||||||
export class UsersStatsComponent {
|
export class UsersStatsComponent {
|
||||||
readonly chartConfig = input.required<DonutChartConfig[]>();
|
@Output() toggleCollapse = new EventEmitter();
|
||||||
readonly toggleCollapse = output();
|
@Input() chartConfig: DonutChartConfig[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
<section class="dialog">
|
|
||||||
<div [innerHTML]="'add-clone-dossier-template.title' | translate: translateParams" class="dialog-header heading-l"></div>
|
|
||||||
|
|
||||||
<form [formGroup]="form">
|
|
||||||
<div class="dialog-content">
|
|
||||||
<div class="iqser-input-group required w-300">
|
|
||||||
<label [translate]="'add-edit-clone-dossier-template.form.name'"></label>
|
|
||||||
<input
|
|
||||||
[placeholder]="'add-edit-clone-dossier-template.form.name-placeholder' | translate"
|
|
||||||
formControlName="name"
|
|
||||||
name="name"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="iqser-input-group w-400">
|
|
||||||
<label [translate]="'add-edit-clone-dossier-template.form.description'"></label>
|
|
||||||
<textarea
|
|
||||||
[placeholder]="'add-edit-clone-dossier-template.form.description-placeholder' | translate"
|
|
||||||
formControlName="description"
|
|
||||||
name="description"
|
|
||||||
rows="4"
|
|
||||||
type="text"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dialog-actions">
|
|
||||||
<iqser-icon-button
|
|
||||||
(action)="save()"
|
|
||||||
[buttonId]="'saveButton'"
|
|
||||||
[disabled]="disabled"
|
|
||||||
[label]="'add-clone-dossier-template.save' | translate: translateParams"
|
|
||||||
[type]="iconButtonTypes.primary"
|
|
||||||
></iqser-icon-button>
|
|
||||||
|
|
||||||
<iqser-icon-button
|
|
||||||
(action)="save({ nextAction: true })"
|
|
||||||
[buttonId]="'saveButton'"
|
|
||||||
[disabled]="disabled"
|
|
||||||
[label]="'add-clone-dossier-template.save-and-edit' | translate"
|
|
||||||
[type]="iconButtonTypes.dark"
|
|
||||||
></iqser-icon-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
|
||||||
</section>
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
import { HttpStatusCode } from '@angular/common/http';
|
|
||||||
import { Component, Inject } from '@angular/core';
|
|
||||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { BaseDialogComponent, CircleButtonComponent, getConfig, IconButtonComponent, SaveOptions } from '@iqser/common-ui';
|
|
||||||
import { DossierTemplate } from '@red/domain';
|
|
||||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
export interface CloneTemplateData {
|
|
||||||
dossierTemplateId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: './add-clone-dossier-template-dialog.component.html',
|
|
||||||
styleUrls: ['./add-clone-dossier-template-dialog.component.scss'],
|
|
||||||
imports: [TranslateModule, ReactiveFormsModule, IconButtonComponent, CircleButtonComponent],
|
|
||||||
})
|
|
||||||
export class AddCloneDossierTemplateDialogComponent extends BaseDialogComponent {
|
|
||||||
readonly dossierTemplate?: DossierTemplate;
|
|
||||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
|
||||||
readonly translateParams: { type: string; dossierTemplateName?: string };
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
|
||||||
private readonly _router: Router,
|
|
||||||
protected readonly _dialogRef: MatDialogRef<AddCloneDossierTemplateDialogComponent>,
|
|
||||||
@Inject(MAT_DIALOG_DATA) readonly data: CloneTemplateData,
|
|
||||||
) {
|
|
||||||
super(_dialogRef);
|
|
||||||
this.dossierTemplate = this._dossierTemplatesService.find(this.data.dossierTemplateId);
|
|
||||||
|
|
||||||
this.translateParams = {
|
|
||||||
type: this.dossierTemplate ? 'clone' : 'create',
|
|
||||||
dossierTemplateName: this.dossierTemplate?.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.form = this.#getForm();
|
|
||||||
this.initialFormValue = this.form.getRawValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
override get disabled(): boolean {
|
|
||||||
// Ignore 'changed' value, doesn't make sense in this context
|
|
||||||
return !this.valid || this._hasErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
async save(options?: SaveOptions): Promise<void> {
|
|
||||||
let dossierTemplate: DossierTemplate;
|
|
||||||
this._loadingService.start();
|
|
||||||
const body = {
|
|
||||||
...this.dossierTemplate,
|
|
||||||
...this.form.getRawValue(),
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
if (this.dossierTemplate) {
|
|
||||||
dossierTemplate = await this._dossierTemplatesService.clone(this.dossierTemplate.id, body);
|
|
||||||
} else {
|
|
||||||
dossierTemplate = await this._dossierTemplatesService.createOrUpdate(body);
|
|
||||||
}
|
|
||||||
if (options?.nextAction) {
|
|
||||||
await this._router.navigate([dossierTemplate.routerLink]);
|
|
||||||
}
|
|
||||||
this._dialogRef.close(true);
|
|
||||||
} catch (error) {
|
|
||||||
if (error.status === HttpStatusCode.Conflict) {
|
|
||||||
this._toaster.error(_('add-edit-clone-dossier-template.error.conflict'), { error });
|
|
||||||
} else {
|
|
||||||
this._toaster.rawError(error.error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._loadingService.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
#getForm() {
|
|
||||||
return this._formBuilder.group({
|
|
||||||
name: [this.dossierTemplate ? this.#getCloneName(this.dossierTemplate) : undefined, Validators.required],
|
|
||||||
description: [this.dossierTemplate?.description],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#getCloneName(initialTemplate: DossierTemplate): string {
|
|
||||||
const templateName = initialTemplate.name.trim();
|
|
||||||
let nameOfClonedTemplate: string = templateName.split('Copy of ').filter(n => n)[0];
|
|
||||||
nameOfClonedTemplate = nameOfClonedTemplate.split(/\(\s*\d+\s*\)$/)[0].trim();
|
|
||||||
const allTemplatesNames = this._dossierTemplatesService.all.map(t => t.name);
|
|
||||||
|
|
||||||
let clonesCount = 0;
|
|
||||||
for (const name of allTemplatesNames) {
|
|
||||||
const splitName = name.split(nameOfClonedTemplate);
|
|
||||||
const suffixRegExp = new RegExp(/^\(\s*\d+\s*\)$/);
|
|
||||||
if (splitName[0] === 'Copy of ' && (splitName[1].trim().match(suffixRegExp) || splitName[1] === '')) {
|
|
||||||
clonesCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clonesCount >= 1) {
|
|
||||||
return `Copy of ${nameOfClonedTemplate} (${clonesCount})`;
|
|
||||||
}
|
|
||||||
return `Copy of ${nameOfClonedTemplate}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,14 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<div [innerHTML]="'add-edit-dossier-attribute.title' | translate : titleTranslateParams" class="dialog-header heading-l"></div>
|
<div
|
||||||
|
[translateParams]="{
|
||||||
|
name: dossierAttribute?.label,
|
||||||
|
type: dossierAttribute ? 'edit' : 'create'
|
||||||
|
}"
|
||||||
|
[translate]="'add-edit-dossier-attribute.title'"
|
||||||
|
class="dialog-header heading-l"
|
||||||
|
></div>
|
||||||
|
|
||||||
<form (submit)="save()" [formGroup]="form">
|
<form [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div class="iqser-input-group required w-300">
|
<div class="iqser-input-group required w-300">
|
||||||
<label [translate]="'add-edit-dossier-attribute.form.label'"></label>
|
<label [translate]="'add-edit-dossier-attribute.form.label'"></label>
|
||||||
@ -20,16 +27,13 @@
|
|||||||
|
|
||||||
<div class="iqser-input-group w-300">
|
<div class="iqser-input-group w-300">
|
||||||
<label [translate]="'add-edit-dossier-attribute.form.type'"></label>
|
<label [translate]="'add-edit-dossier-attribute.form.type'"></label>
|
||||||
<mat-form-field>
|
<mat-select formControlName="type">
|
||||||
<mat-select formControlName="type">
|
<mat-option *ngFor="let type of typeOptions" [value]="type">
|
||||||
<mat-option *ngFor="let type of typeOptions" [value]="type">
|
{{ translations[type] | translate }}
|
||||||
{{ translations[type] | translate }}
|
</mat-option>
|
||||||
</mat-option>
|
</mat-select>
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="save()"
|
(action)="save()"
|
||||||
@ -1,44 +1,31 @@
|
|||||||
import { Component, Inject, OnDestroy } from '@angular/core';
|
import { Component, HostListener, Inject, OnDestroy } from '@angular/core';
|
||||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
import { UntypedFormGroup, Validators } from '@angular/forms';
|
||||||
import { DossierAttributeConfigTypes, FileAttributeConfigTypes, IDossierAttributeConfig } from '@red/domain';
|
import { DossierAttributeConfigTypes, FileAttributeConfigTypes, IDossierAttributeConfig } from '@red/domain';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { BaseDialogComponent, CircleButtonComponent, IconButtonComponent } from '@iqser/common-ui';
|
import { BaseDialogComponent, IqserEventTarget } from '@iqser/common-ui';
|
||||||
import { HttpErrorResponse } from '@angular/common/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service';
|
import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service';
|
||||||
import { dossierAttributeTypesTranslations } from '@translations/dossier-attribute-types-translations';
|
import { dossierAttributeTypesTranslations } from '@translations/dossier-attribute-types-translations';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { NgForOf, NgIf } from '@angular/common';
|
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
|
||||||
|
|
||||||
export interface AddEditDossierAttributeDialogData {
|
|
||||||
readonly dossierAttribute: IDossierAttributeConfig;
|
|
||||||
readonly dossierTemplateId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
|
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
|
||||||
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'],
|
|
||||||
imports: [
|
|
||||||
TranslateModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
NgIf,
|
|
||||||
MatFormFieldModule,
|
|
||||||
MatSelectModule,
|
|
||||||
NgForOf,
|
|
||||||
IconButtonComponent,
|
|
||||||
CircleButtonComponent,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent implements OnDestroy {
|
export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent implements OnDestroy {
|
||||||
readonly dossierAttribute = this.data.dossierAttribute;
|
dossierAttribute: IDossierAttributeConfig = this.data.dossierAttribute;
|
||||||
readonly translations = dossierAttributeTypesTranslations;
|
readonly translations = dossierAttributeTypesTranslations;
|
||||||
readonly typeOptions = Object.keys(DossierAttributeConfigTypes);
|
readonly typeOptions = Object.keys(DossierAttributeConfigTypes);
|
||||||
readonly titleTranslateParams = {
|
|
||||||
name: this.data.dossierAttribute?.label,
|
constructor(
|
||||||
type: this.data.dossierAttribute ? 'edit' : 'create',
|
private readonly _dossierAttributesService: DossierAttributesService,
|
||||||
};
|
protected readonly _dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA)
|
||||||
|
readonly data: { readonly dossierAttribute: IDossierAttributeConfig; dossierTemplateId: string },
|
||||||
|
) {
|
||||||
|
super(_dialogRef, !!data.dossierAttribute);
|
||||||
|
this.form = this._getForm(this.dossierAttribute);
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
|
}
|
||||||
|
|
||||||
get changed(): boolean {
|
get changed(): boolean {
|
||||||
if (!this.dossierAttribute) {
|
if (!this.dossierAttribute) {
|
||||||
@ -54,8 +41,35 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get #form() {
|
save() {
|
||||||
const dossierAttribute = this.data.dossierAttribute;
|
this._loadingService.start();
|
||||||
|
|
||||||
|
const attribute: IDossierAttributeConfig = {
|
||||||
|
id: this.dossierAttribute?.id,
|
||||||
|
editable: true,
|
||||||
|
...this.form.getRawValue(),
|
||||||
|
};
|
||||||
|
|
||||||
|
this._dossierAttributesService.createOrUpdate(attribute, this.data.dossierTemplateId).subscribe(
|
||||||
|
() => {
|
||||||
|
this._dialogRef.close(true);
|
||||||
|
},
|
||||||
|
(error: HttpErrorResponse) => {
|
||||||
|
this._loadingService.stop();
|
||||||
|
this._toaster.error(_('add-edit-dossier-attribute.error.generic'), { error });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:keydown.Enter', ['$event'])
|
||||||
|
onEnter(event: KeyboardEvent): void {
|
||||||
|
const node = (event.target as IqserEventTarget).localName;
|
||||||
|
if (this.form.valid && this.changed && node !== 'textarea') {
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getForm(dossierAttribute: IDossierAttributeConfig): UntypedFormGroup {
|
||||||
return this._formBuilder.group({
|
return this._formBuilder.group({
|
||||||
label: [dossierAttribute?.label, Validators.required],
|
label: [dossierAttribute?.label, Validators.required],
|
||||||
...(!!dossierAttribute && {
|
...(!!dossierAttribute && {
|
||||||
@ -67,35 +81,4 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent
|
|||||||
type: [dossierAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required],
|
type: [dossierAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _dossierAttributesService: DossierAttributesService,
|
|
||||||
protected readonly _dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
|
||||||
@Inject(MAT_DIALOG_DATA) readonly data: AddEditDossierAttributeDialogData,
|
|
||||||
) {
|
|
||||||
super(_dialogRef, !!data.dossierAttribute);
|
|
||||||
this.form = this.#form;
|
|
||||||
this.initialFormValue = this.form.getRawValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
async save() {
|
|
||||||
this._loadingService.start();
|
|
||||||
|
|
||||||
const attribute: IDossierAttributeConfig = {
|
|
||||||
id: this.dossierAttribute?.id,
|
|
||||||
editable: true,
|
|
||||||
...this.form.getRawValue(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const createOrUpdate = this._dossierAttributesService.createOrUpdate(attribute, this.data.dossierTemplateId);
|
|
||||||
const result = await createOrUpdate.catch((error: HttpErrorResponse) => {
|
|
||||||
this._loadingService.stop();
|
|
||||||
this._toaster.rawError(error.error.message);
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
this._dialogRef.close(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user