Compare commits

..

40 Commits

Author SHA1 Message Date
Dominique Eifländer
2aa20085b0 RED-10148: Use rectangular text selection mode when space is pressed 2024-11-13 14:31:28 +01:00
Adina Țeudan
bd66f39474 RED-10422 - improved number of requests with help of new changes endpoint 2024-11-12 16:43:25 +02:00
Valentin Mihai
969e375986 RED-10396 - fixed current page active annotations re evaluation when they are updated 2024-11-12 11:16:39 +02:00
Nicoleta Panaghiu
d59a4c02a0 RED-10258: add all available fonts. 2024-11-06 17:28:30 +02:00
Valentin Mihai
53e9725243 RED-9944 - fix build 2024-11-01 14:39:35 +02:00
Valentin Mihai
a3b2382bad RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-11-01 14:33:51 +02:00
Nicoleta Panaghiu
327620d5f6 RED-9663: removed UNASSIGNED option from the status filter. 2024-10-30 16:03:43 +02:00
Nicoleta Panaghiu
471d111d69 RED-10275: differentiate available types by applyToAll flag state. 2024-10-29 15:21:51 +02:00
Valentin Mihai
46b48de214 RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-10-28 16:58:00 +02:00
Valentin Mihai
ae1c137b61 RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-10-22 16:42:40 +03:00
Valentin Mihai
84aa3f1c00 RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-10-18 14:39:44 +03:00
Valentin Mihai
c8c632a2c1 RED-9944 - cancel the bulk-selection mode when a user clicks somewhere else 2024-10-16 23:12:09 +03:00
Nicoleta Panaghiu
26e8d9fff9 RED-10190: fixed notifications redirect links and padding.. 2024-10-16 13:59:40 +03:00
Valentin Mihai
1fcc95b138 RED-9944 - Action Items don't appear in document area in webviewer when bulk-select is still unintenionally active 2024-10-15 12:07:09 +03:00
Nicoleta Panaghiu
aa171f992b RED-10144: fixed multi-selection randomly activating. 2024-10-09 16:55:14 +03:00
Dan Percic
33bacfd967 Merge branch 'RED-10084-bp' into 'release/4.830.x'
RED-10084: All members of a dossier are able to reanalyse files in error...

See merge request redactmanager/red-ui!596
2024-10-02 09:52:00 +02:00
Dominique Eifländer
ce262a5f3a RED-10084: All members of a dossier are able to reanalyse files in error state, no only the owner or assignee 2024-10-02 09:19:01 +02:00
Nicoleta Panaghiu
c6ec428bc4 RED-9486: fixed naming of images in remove dialog. 2024-09-25 14:56:47 +03:00
Nicoleta Panaghiu
edf9e61132 RED-9993: fixed license labels. 2024-09-23 15:57:50 +03:00
Nicoleta Panaghiu
e57d54a755 RED-10028: added OPTIMIZED_PREVIEW file type in download selection. 2024-09-23 12:52:21 +03:00
Nicoleta Panaghiu
036bfa2878 RED-9993: fixed license information. 2024-09-13 11:01:26 +03:00
Nicoleta Panaghiu
bf9e6816f8 RED-9372: update common ui. 2024-09-10 11:46:24 +03:00
Nicoleta Panaghiu
0bff02a3e7 RED-9372: fixed scrolling on dossier details. 2024-09-10 11:45:34 +03:00
Marius Schummer
9016ff91b3 RED-9937 - Backport of the tooltip label change for the document preview 2024-09-09 16:47:18 +02:00
Nicoleta Panaghiu
afa399baf1 RED-9372: fixed annotation details moving on hover. 2024-09-05 17:22:17 +03:00
Nicoleta Panaghiu
41b8407862 RED-9372: update common ui. 2024-09-05 17:22:04 +03:00
Nicoleta Panaghiu
303aa0a1a4 RED-9937: made preview tab available & trigger reanalysis when required. 2024-09-04 13:32:03 +03:00
Valentin Mihai
262e30e2c0 RED-9454 - Differences between Add Hint- and Add Redaction-Dialog 2024-09-03 17:55:01 +03:00
Nicoleta Panaghiu
901ba934af RED-9987: added sendSetPasswordMail flag in add user dialog. 2024-09-03 14:51:06 +03:00
Nicoleta Panaghiu
ca0fe4cf13 RED-9893: show missing type toaster only once. 2024-08-21 10:55:34 +03:00
Valentin Mihai
5d3eb3751c RED-9862 - Broken resize dialog for imported redaction from preview file with disabled auto-analysis on upload 2024-08-15 17:02:17 +03:00
Nicoleta Panaghiu
1db68cbb7a RED-9589: removed FP option for locally removed and forced redactions. 2024-08-14 11:25:37 +03:00
Nicoleta Panaghiu
c9b25e2af4 RED-9874: added user preference for overwrite file option. 2024-08-13 17:22:59 +03:00
Nicoleta Panaghiu
9881c3e321 RED-9504: fix some translations. 2024-08-02 10:54:51 +03:00
Nicoleta Panaghiu
e4cb33978c RED-9731: fixed help button position on resize. 2024-08-01 14:42:26 +03:00
Nicoleta Panaghiu
1376e1a486 RED-9516: fixed filter options alignment. 2024-08-01 13:17:56 +03:00
Nicoleta Panaghiu
1894a378b5 RED-9742: fixed logo router link. 2024-07-31 13:21:03 +03:00
Nicoleta Panaghiu
701a69db95 RED-9571: fixed initials-avatar not updating in some cases. 2024-07-31 12:48:44 +03:00
Nicoleta Panaghiu
77f94a7983 RED-9589: enabled false positive option for some annotation configs. 2024-07-29 12:20:06 +03:00
Nicoleta Panaghiu
108a3315da RED-9657: additional fixes for help mode links. 2024-07-23 12:46:30 +03:00
443 changed files with 11343 additions and 11585 deletions

View File

@ -16,7 +16,6 @@
},
"rules": {
"rxjs/no-ignored-subscription": "warn",
"@angular-eslint/prefer-standalone": "off",
"@angular-eslint/no-conflicting-lifecycle": "error",
"@angular-eslint/no-host-metadata-property": "error",
"@angular-eslint/no-input-rename": "error",

View File

@ -4,60 +4,38 @@ variables:
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/"
- if: $CI_PIPELINE_SOURCE != "schedule"
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"
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
- 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}
fi
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"

View File

@ -50,7 +50,7 @@
{
"glob": "**/*",
"input": "node_modules/@pdftron/webviewer/public/",
"output": "/assets/wv-resources/11.1.0/"
"output": "/assets/wv-resources/10.10.1/"
},
{
"glob": "**/*",
@ -73,7 +73,7 @@
"stylePreprocessorOptions": {
"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", "node_modules/chart.js/auto/auto.cjs"],
"extractLicenses": false,
"sourceMap": true,
"optimization": false,

View File

@ -16,7 +16,7 @@ import { CompositeRouteGuard, DEFAULT_REDIRECT_KEY, IqserPermissionsGuard, Iqser
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 { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, 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';
@ -81,6 +81,7 @@ const dossierTemplateIdRoutes: IqserRoutes = [
canActivate: [CompositeRouteGuard, loadArchivedDossiersGuard()],
data: {
routeGuards: [FeaturesGuard],
features: [DOSSIERS_ARCHIVE],
},
},
{

View File

@ -4,27 +4,26 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { getConfig } from '@iqser/common-ui';
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';
import { filter, map, switchMap, take } from 'rxjs/operators';
export function loadCustomTheme(cssFileName: string) {
const head = document.getElementsByTagName('head')[0];
const link = document.createElement('link');
link.id = cssFileName;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'assets/styles/themes/' + cssFileName + '.css';
link.media = 'all';
head.appendChild(link);
function loadCustomTheme() {
const cssFileName = getConfig<AppConfig>().THEME;
if (cssFileName) {
const head = document.getElementsByTagName('head')[0];
const link = document.createElement('link');
link.id = cssFileName;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'assets/styles/themes/' + cssFileName + '.css';
link.media = 'all';
head.appendChild(link);
}
}
@Component({
selector: 'redaction-root',
templateUrl: './app.component.html',
standalone: false,
})
export class AppComponent {
constructor(
@ -35,12 +34,9 @@ export class AppComponent {
userPreferenceService: UserPreferenceService,
renderer: Renderer2,
private readonly _router: Router,
private readonly _iconRegistry: MatIconRegistry,
private readonly _sanitizer: DomSanitizer,
private readonly _tenantsService: TenantsService,
) {
const config = getConfig<AppConfig>();
renderer.addClass(document.body, userPreferenceService.getTheme());
loadCustomTheme();
const removeQueryParams = _router.events.pipe(
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
@ -51,25 +47,9 @@ export class AppComponent {
);
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();
if (getConfig().IS_DOCUMINE) {
document.getElementById('favicon').setAttribute('href', 'assets/icons/documine-logo.ico');
}
}
#removeKeycloakQueryParams() {

View File

@ -1,6 +1,6 @@
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 { ENVIRONMENT_INITIALIZER, ErrorHandler, inject, NgModule } from '@angular/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatIcon } from '@angular/material/icon';
import { MatMenu, MatMenuContent, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
@ -117,7 +117,7 @@ export const appModuleFactory = (config: AppConfig) => {
resetTimeoutOnDuplicate: true,
}),
TenantsModule.forRoot(),
IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY }),
IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY || '/assets/i18n/redact/' }),
IqserLoadingModule.forRoot(),
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
LoggerModule.forRoot(undefined, {
@ -135,20 +135,20 @@ export const appModuleFactory = (config: AppConfig) => {
features: {
ANNOTATIONS: {
color: 'aqua',
enabled: false,
enabled: true,
level: NgxLoggerLevel.DEBUG,
},
FILTERS: {
enabled: false,
},
TENANTS: {
enabled: false,
enabled: true,
},
ROUTES: {
enabled: false,
enabled: true,
},
PDF: {
enabled: false,
enabled: true,
},
FILE: {
enabled: false,
@ -171,9 +171,6 @@ export const appModuleFactory = (config: AppConfig) => {
DOSSIERS_CHANGES: {
enabled: false,
},
GUARDS: {
enabled: false,
},
},
} as ILoggerConfig,
},
@ -233,10 +230,14 @@ export const appModuleFactory = (config: AppConfig) => {
provide: ErrorHandler,
useClass: GlobalErrorHandler,
},
provideEnvironmentInitializer(async () => {
const languageService = inject(LanguageService);
return languageService.setInitialLanguage();
}),
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: async () => {
const languageService = inject(LanguageService);
return languageService.setInitialLanguage();
},
},
{
provide: MAX_RETRIES_ON_SERVER_ERROR,
useFactory: () => config.MAX_RETRIES_ON_SERVER_ERROR,
@ -257,7 +258,6 @@ export const appModuleFactory = (config: AppConfig) => {
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
useValue: {
disableTooltipInteractivity: true,
showDelay: 1,
},
},
BaseDatePipe,

View File

@ -7,7 +7,6 @@ import { AppConfig } from '@red/domain';
selector: 'redaction-auth-error',
templateUrl: './auth-error.component.html',
styleUrls: ['./auth-error.component.scss'],
standalone: false,
})
export class AuthErrorComponent {
readonly #config = getConfig<AppConfig>();

View File

@ -8,7 +8,8 @@ import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-se
import { filter, map, startWith } from 'rxjs/operators';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { BreadcrumbsService } from '@services/breadcrumbs.service';
import { ARCHIVE_ROUTE, DOSSIERS_ROUTE } from '@red/domain';
import { FeaturesService } from '@services/features.service';
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
import { Roles } from '@users/roles';
import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -20,7 +21,6 @@ const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
@Component({
templateUrl: './base-screen.component.html',
styleUrls: ['./base-screen.component.scss'],
standalone: false,
})
export class BaseScreenComponent {
readonly roles = Roles;
@ -36,6 +36,7 @@ export class BaseScreenComponent {
{
text: this._translateService.instant('search.active-dossiers'),
icon: 'red:enter',
hide: () => !this._featuresService.isEnabled(DOSSIERS_ARCHIVE),
action: (query): void => this.#search(query, [], true),
},
{
@ -57,6 +58,7 @@ export class BaseScreenComponent {
private readonly _router: Router,
activatedRoute: ActivatedRoute,
private readonly _translateService: TranslateService,
private readonly _featuresService: FeaturesService,
readonly permissionsService: IqserPermissionsService,
readonly userService: UserService,
readonly userPreferenceService: UserPreferenceService,

View File

@ -7,7 +7,6 @@ import { Breadcrumb, BreadcrumbDisplayType, BreadcrumbsService } from '@services
selector: 'redaction-breadcrumbs',
templateUrl: './breadcrumbs.component.html',
styleUrls: ['./breadcrumbs.component.scss'],
standalone: false,
})
export class BreadcrumbsComponent {
constructor(readonly breadcrumbsService: BreadcrumbsService) {}

View File

@ -13,7 +13,6 @@ import { firstValueFrom } from 'rxjs';
entitiesService: FileDownloadService,
component: DownloadsListScreenComponent,
}),
standalone: false,
})
export class DownloadsListScreenComponent extends ListingComponent<DownloadStatus> implements OnDestroy {
readonly #interval: number;

View File

@ -25,7 +25,6 @@ function chronologically(first: string, second: string) {
selector: 'redaction-notifications',
templateUrl: './notifications.component.html',
styleUrls: ['./notifications.component.scss'],
standalone: false,
})
export class NotificationsComponent {
readonly hasUnreadNotifications$: Observable<boolean>;

View File

@ -9,7 +9,7 @@
.container {
padding: 32px;
width: 1000px;
width: 900px;
max-width: 100%;
box-sizing: border-box;
}

View File

@ -5,6 +5,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
templateUrl: './dashboard-skeleton.component.html',
styleUrls: ['./dashboard-skeleton.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class DashboardSkeletonComponent {}

View File

@ -5,7 +5,6 @@ import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } fr
templateUrl: './dossier-skeleton.component.html',
styleUrls: ['./dossier-skeleton.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class DossierSkeletonComponent implements OnInit {
@ViewChild('workload1', { static: true }) workload1: TemplateRef<unknown>;

View File

@ -5,6 +5,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
templateUrl: './skeleton-stats.component.html',
styleUrls: ['./skeleton-stats.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class SkeletonStatsComponent {}

View File

@ -6,7 +6,6 @@ import { Title } from '@angular/platform-browser';
templateUrl: './skeleton-top-bar.component.html',
styleUrls: ['./skeleton-top-bar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class SkeletonTopBarComponent {
constructor(readonly titleService: Title) {}

View File

@ -9,7 +9,6 @@ import { MatMenuTrigger } from '@angular/material/menu';
templateUrl: './spotlight-search.component.html',
styleUrls: ['./spotlight-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class SpotlightSearchComponent {
@Input() actions: readonly SpotlightSearchAction[];

View File

@ -9,7 +9,6 @@ interface ITenant extends IStoredTenantId {
selector: 'app-tenants-menu',
templateUrl: './tenants-menu.component.html',
styleUrls: ['./tenants-menu.component.scss'],
standalone: false,
})
export class TenantsMenuComponent {
readonly tenantsService = inject(TenantsService);

View File

@ -20,7 +20,6 @@ interface MenuItem {
selector: 'app-user-menu',
templateUrl: './user-menu.component.html',
styleUrls: ['./user-menu.component.scss'],
standalone: false,
})
export class UserMenuComponent {
readonly currentUser = getCurrentUser<User>();

View File

@ -11,9 +11,7 @@ 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';
import { getConfig } from '@iqser/common-ui';
export function templateExistsWhenEnteringAdmin(): CanActivateFn {
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
@ -23,14 +21,12 @@ export function templateExistsWhenEnteringAdmin(): CanActivateFn {
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);
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
await firstValueFrom(rulesService.getFor(dossierTemplateId));
if (!isDocumine) {
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
}
@ -54,8 +50,6 @@ export function templateExistsWhenEnteringDossierList(): CanActivateFn {
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));
@ -70,10 +64,6 @@ export function templateExistsWhenEnteringDossierList(): CanActivateFn {
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));
}

View File

@ -40,7 +40,8 @@ export function ifLoggedIn(): AsyncGuard {
logger.info('[KEYCLOAK] Keycloak init...');
await keycloakInitializer(tenant);
logger.info('[KEYCLOAK] Keycloak init done for tenant: ', { tenant });
logger.info('[KEYCLOAK] Keycloak init done!');
console.log({ tenant });
await tenantsService.selectTenant(tenant);
await usersService.initialize();
await licenseService.loadLicenses();
@ -50,7 +51,6 @@ export function ifLoggedIn(): AsyncGuard {
const jwtToken = jwtDecode(token) as JwtToken;
const authTime = (jwtToken.auth_time || jwtToken.iat).toString();
localStorage.setItem('authTime', authTime);
localStorage.setItem('token', token);
}
}

View File

@ -16,7 +16,11 @@ export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
annotation.canBeMarkedAsFalsePositive && !annotation.hasBeenResizedLocally && annotationEntity?.hasDictionary;
annotation.canBeMarkedAsFalsePositive &&
!annotation.hasBeenResizedLocally &&
!annotation.isRemovedLocally &&
!annotation.hasBeenForcedRedaction &&
annotationEntity?.hasDictionary;
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
canAddRedaction &&
@ -24,7 +28,7 @@ export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction
(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.isModifyDictionary &&
(annotation.isRedacted || annotation.isSkipped || annotation.isHint || (annotation.isIgnoredHint && !annotation.isRuleBased)) &&
(autoAnalysisDisabled || !annotation.pending) &&
annotation.isDictBased;
@ -69,5 +73,3 @@ 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;

View File

@ -17,7 +17,6 @@ import {
canRemoveRedaction,
canResizeAnnotation,
canResizeInDictionary,
canRevertChanges,
canUndo,
} from './annotation-permissions.utils';
import { AnnotationWrapper } from './annotation.wrapper';
@ -38,7 +37,6 @@ export class AnnotationPermissions {
canEditAnnotations = true;
canEditHints = true;
canEditImages = true;
canRevertChanges = true;
static forUser(
isApprover: boolean,
@ -77,7 +75,6 @@ export class AnnotationPermissions {
permissions.canEditAnnotations = canEditAnnotation(annotation);
permissions.canEditHints = canEditHint(annotation);
permissions.canEditImages = canEditImage(annotation);
permissions.canRevertChanges = canRevertChanges(annotation);
summedPermissions._merge(permissions);
}
return summedPermissions;
@ -100,7 +97,6 @@ export class AnnotationPermissions {
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;
}

View File

@ -26,12 +26,6 @@ import {
} from '@red/domain';
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
interface AnnotationContent {
translation: string;
params: { [key: string]: string };
untranslatedContent: string;
}
export class AnnotationWrapper implements IListable {
id: string;
superType: SuperType;
@ -40,9 +34,9 @@ export class AnnotationWrapper implements IListable {
typeLabel?: string;
color: string;
numberOfComments = 0;
firstBottomLeftPoint: IPoint;
firstTopLeftPoint: IPoint;
shortContent: string;
content: AnnotationContent;
content: string;
value: string;
pageNumber: number;
dictionaryOperation = false;
@ -84,14 +78,7 @@ export class AnnotationWrapper implements IListable {
}
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;
return this.IMAGE_HINT && this.superType === SuperTypes.Redaction;
}
get searchKey(): string {
@ -112,10 +99,7 @@ export class AnnotationWrapper implements IListable {
get canBeMarkedAsFalsePositive() {
return (
(this.isRecommendation ||
this.superType === SuperTypes.Redaction ||
(this.isSkipped && this.isDictBased) ||
(this.isRemovedLocally && this.isDictBased)) &&
(this.isRecommendation || this.superType === SuperTypes.Redaction || (this.isSkipped && this.isDictBased)) &&
!this.isImage &&
!this.imported &&
!this.pending &&
@ -199,11 +183,11 @@ export class AnnotationWrapper implements IListable {
}
get x() {
return this.firstBottomLeftPoint.x;
return this.firstTopLeftPoint.x;
}
get y() {
return this.firstBottomLeftPoint.y;
return this.firstTopLeftPoint.y;
}
get legalBasis() {
@ -228,7 +212,7 @@ export class AnnotationWrapper implements IListable {
annotationWrapper.value = 'Imported';
annotationWrapper.color = earmark.hexColor;
annotationWrapper.positions = earmark.positions;
annotationWrapper.firstBottomLeftPoint = earmark.positions[0]?.topLeft;
annotationWrapper.firstTopLeftPoint = earmark.positions[0]?.topLeft;
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
return annotationWrapper;
@ -249,7 +233,7 @@ export class AnnotationWrapper implements IListable {
annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType;
annotationWrapper.type = logEntry.type;
annotationWrapper.value = logEntry.value;
annotationWrapper.firstBottomLeftPoint = { x: logEntry.positions[0].rectangle[0], y: logEntry.positions[0].rectangle[1] };
annotationWrapper.firstTopLeftPoint = { x: logEntry.positions[0].rectangle[0], y: logEntry.positions[0].rectangle[1] };
annotationWrapper.pageNumber = logEntry.positions[0].pageNumber;
annotationWrapper.positions = logEntry.positions.map(p => ({
page: p.pageNumber,
@ -291,7 +275,7 @@ export class AnnotationWrapper implements IListable {
);
const content = this.#createContent(annotationWrapper, logEntry, isDocumine);
annotationWrapper.shortContent = this.#getShortContent(annotationWrapper, legalBasisList) || content.untranslatedContent;
annotationWrapper.shortContent = this.#getShortContent(annotationWrapper, legalBasisList) || content;
annotationWrapper.content = content;
const lastRelevantManualChange = logEntry.manualChanges?.at(-1);
@ -323,62 +307,44 @@ export class AnnotationWrapper implements IListable {
}
static #createContent(annotationWrapper: AnnotationWrapper, logEntry: IEntityLogEntry, isDocumine: boolean) {
let untranslatedContent = '';
const params: { [key: string]: string } = {};
let content = '';
if (logEntry.matchedRule) {
params['hasRule'] = 'true';
params['matchedRule'] = logEntry.matchedRule.replace(/(^[, ]*)|([, ]*$)/g, '');
params['ruleSymbol'] = isDocumine ? ':' : '';
untranslatedContent += `Rule ${logEntry.matchedRule} matched${isDocumine ? ':' : ''} \n\n`;
content += `Rule ${logEntry.matchedRule} matched${isDocumine ? ':' : ''} \n\n`;
}
if (logEntry.reason) {
params['hasReason'] = 'true';
if (isDocumine && logEntry.reason.slice(-1) === '.') {
logEntry.reason = logEntry.reason.slice(0, -1);
}
if (!params['hasRule']) {
params['reason'] = logEntry.reason.substring(0, 1).toUpperCase() + logEntry.reason.substring(1);
} else {
params['reason'] = logEntry.reason;
}
params['reason'] = params['reason'].replace(/(^[, ]*)|([, ]*$)/g, '');
untranslatedContent += logEntry.reason + '\n\n';
content += logEntry.reason + '\n\n';
//remove leading and trailing commas and whitespaces
untranslatedContent = untranslatedContent.replace(/(^[, ]*)|([, ]*$)/g, '');
untranslatedContent = untranslatedContent.substring(0, 1).toUpperCase() + untranslatedContent.substring(1);
content = content.replace(/(^[, ]*)|([, ]*$)/g, '');
content = content.substring(0, 1).toUpperCase() + content.substring(1);
}
if (annotationWrapper.legalBasis && !isDocumine) {
params['hasLb'] = 'true';
params['legalBasis'] = annotationWrapper.legalBasis;
untranslatedContent += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
content += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
}
if (annotationWrapper.hasBeenRemovedByManualOverride) {
params['hasOverride'] = 'true';
untranslatedContent += 'Removed by manual override';
content += 'Removed by manual override';
}
if (logEntry.section) {
params['hasSection'] = 'true';
params['sectionSymbol'] = isDocumine ? '' : ':';
params['shouldLower'] = untranslatedContent.length.toString();
params['section'] = logEntry.section;
let prefix = `In section${isDocumine ? '' : ':'} `;
if (untranslatedContent.length) {
if (content.length) {
prefix = ` ${prefix.toLowerCase()}`;
}
untranslatedContent += `${prefix} "${logEntry.section}"`;
content += `${prefix} "${logEntry.section}"`;
}
return { translation: _('annotation-content'), params: params, untranslatedContent: untranslatedContent };
return content;
}
static #getShortContent(annotationWrapper: AnnotationWrapper, legalBasisList: ILegalBasis[]) {
if (annotationWrapper.legalBasis) {
const lb = legalBasisList.find(lbm => lbm.technicalName?.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase()));
const lb = legalBasisList.find(lbm => lbm.reason?.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase()));
if (lb) {
return lb.name;
}

View File

@ -21,6 +21,7 @@ interface NavItem {
templateUrl: './account-side-nav.component.html',
styleUrls: ['./account-side-nav.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SideNavComponent, TranslateModule, NgForOf, NgIf, RouterLinkActive, RouterLink],
})
export class AccountSideNavComponent {

View File

@ -8,11 +8,10 @@
<div class="content-container full-height">
<div class="overlay-shadow"></div>
<div [ngClass]="!isWarningsScreen && 'dialog'">
@if (!isWarningsScreen) {
<div class="dialog-header">
<div class="heading-l" [translate]="translations[path]"></div>
</div>
}
<div *ngIf="!isWarningsScreen" class="dialog-header">
<div class="heading-l" [translate]="translations[path]"></div>
</div>
<router-outlet></router-outlet>
</div>
</div>

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewContainerRef } from '@angular/core';
import { Router, RouterOutlet } from '@angular/router';
import { accountTranslations } from '@translations/account-translations';
import { NgClass } from '@angular/common';
import { NgClass, NgIf } from '@angular/common';
import { AccountSideNavComponent } from '../account-side-nav/account-side-nav.component';
import { TranslateModule } from '@ngx-translate/core';
@ -10,7 +10,8 @@ import { TranslateModule } from '@ngx-translate/core';
templateUrl: './base-account-screen-component.html',
styleUrls: ['./base-account-screen-component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgClass, RouterOutlet, AccountSideNavComponent, TranslateModule],
standalone: true,
imports: [NgClass, NgIf, RouterOutlet, AccountSideNavComponent, TranslateModule],
})
export class BaseAccountScreenComponent implements OnInit {
readonly translations = accountTranslations;

View File

@ -25,6 +25,7 @@ const RSS_EXCLUDED_SETTINGS = ['USER_PROMOTED_TO_APPROVER', 'USER_DEGRADED_TO_RE
templateUrl: './notifications-screen.component.html',
styleUrls: ['./notifications-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [ReactiveFormsModule, NgForOf, MatSlideToggle, TranslateModule, NgIf, MatCheckbox, IconButtonComponent],
})
export class NotificationsScreenComponent extends BaseFormComponent implements OnInit {

View File

@ -41,6 +41,7 @@ interface DefaultOptionsForm {
selector: 'redaction-dialog-defaults',
templateUrl: './dialog-defaults.component.html',
styleUrl: './dialog-defaults.component.scss',
standalone: true,
imports: [ReactiveFormsModule, TranslateModule, MatFormField, MatSelect, MatOption, NgForOf, MatCheckbox, NgIf, IconButtonComponent],
})
export class DialogDefaultsComponent extends BaseFormComponent {

View File

@ -11,6 +11,12 @@
</mat-slide-toggle>
</div>
<div *ngIf="config.IS_DOCUMINE" class="iqser-input-group">
<mat-slide-toggle color="primary" formControlName="openScmDialogByDefault">
{{ 'preferences-screen.form.open-structured-view-by-default' | translate }}
</mat-slide-toggle>
</div>
<div *allow="roles.getTables" class="iqser-input-group">
<label [translate]="'preferences-screen.form.table-extraction-type'"></label>
<input formControlName="tableExtractionType" />

View File

@ -23,6 +23,7 @@ import { MatCheckbox } from '@angular/material/checkbox';
interface PreferencesForm {
// preferences
autoExpandFiltersOnActions: boolean;
openScmDialogByDefault: boolean;
tableExtractionType: string;
// warnings preferences
loadAllAnnotationsWarning: boolean;
@ -44,6 +45,7 @@ const Screens = {
templateUrl: './preferences.component.html',
styleUrls: ['./preferences.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
DialogDefaultsComponent,
NgClass,
@ -80,6 +82,7 @@ export class PreferencesComponent extends BaseFormComponent implements OnInit {
this.form = this.#formBuilder.group({
// preferences
autoExpandFiltersOnActions: [this.#userPreferenceService.getAutoExpandFiltersOnActions()],
openScmDialogByDefault: [this.#userPreferenceService.getOpenScmDialogByDefault()],
tableExtractionType: [this.#userPreferenceService.getTableExtractionType()],
// warnings preferences
loadAllAnnotationsWarning: [this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)],
@ -111,6 +114,9 @@ export class PreferencesComponent extends BaseFormComponent implements OnInit {
if (this.form.controls.autoExpandFiltersOnActions.value !== this.#userPreferenceService.getAutoExpandFiltersOnActions()) {
await this.#userPreferenceService.toggleAutoExpandFiltersOnActions();
}
if (this.form.controls.openScmDialogByDefault.value !== this.#userPreferenceService.getOpenScmDialogByDefault()) {
await this.#userPreferenceService.toggleOpenScmDialogByDefault();
}
if (this.form.controls.tableExtractionType.value !== this.#userPreferenceService.getTableExtractionType()) {
await this.#userPreferenceService.save(PreferencesKeys.tableExtractionType, this.form.controls.tableExtractionType.value);
@ -144,6 +150,7 @@ export class PreferencesComponent extends BaseFormComponent implements OnInit {
#patchValues() {
this.form.patchValue({
autoExpandFiltersOnActions: this.#userPreferenceService.getAutoExpandFiltersOnActions(),
openScmDialogByDefault: this.#userPreferenceService.getOpenScmDialogByDefault(),
tableExtractionType: this.#userPreferenceService.getTableExtractionType(),
loadAllAnnotationsWarning: this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning),
helpModeDialog: this.#userPreferenceService.getBool(KEYS.helpModeDialog),

View File

@ -10,6 +10,7 @@ interface FormType {
@Component({
templateUrl: './confirm-password-dialog.component.html',
standalone: true,
imports: [ReactiveFormsModule, IconButtonComponent, TranslateModule, CircleButtonComponent],
})
export class ConfirmPasswordDialogComponent extends BaseDialogComponent {

View File

@ -16,16 +16,13 @@
<input formControlName="lastName" name="lastName" type="text" />
</div>
<div class="iqser-input-group">
<div *ngIf="devMode" class="iqser-input-group">
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
<mat-form-field>
<mat-select formControlName="language">
<mat-select-trigger>{{ languageSelectLabel() | translate }}</mat-select-trigger>
@for (language of languages; track language) {
<mat-option [value]="language">
{{ translations[language] | translate }}
</mat-option>
}
<mat-option *ngFor="let language of languages" [value]="language">
{{ translations[language] | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
@ -34,19 +31,17 @@
<a (click)="resetPassword()" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
</div>
@if (devMode) {
<div class="iqser-input-group">
<mat-slide-toggle color="primary" formControlName="darkTheme">
{{ 'user-profile-screen.form.dark-theme' | translate }}
</mat-slide-toggle>
</div>
}
<div *ngIf="devMode" class="iqser-input-group">
<mat-slide-toggle color="primary" formControlName="darkTheme">
{{ 'user-profile-screen.form.dark-theme' | translate }}
</mat-slide-toggle>
</div>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[disabled]="disabled"
[disabled]="form.invalid || !(profileChanged || languageChanged || themeChanged)"
[label]="'user-profile-screen.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed } from '@angular/core';
import { FormGroup, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import {
BaseFormComponent,
@ -17,47 +17,23 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { UserService } from '@users/user.service';
import { firstValueFrom } from 'rxjs';
import { UserProfileDialogService } from '../services/user-profile-dialog.service';
import { NgForOf, NgIf } from '@angular/common';
import { MatFormField } from '@angular/material/form-field';
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { MatOption, MatSelect } 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({
templateUrl: './user-profile-screen.component.html',
styleUrls: ['./user-profile-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ReactiveFormsModule,
MatFormField,
MatSelect,
MatOption,
TranslateModule,
MatSlideToggle,
IconButtonComponent,
MatSelectTrigger,
],
standalone: true,
imports: [ReactiveFormsModule, NgIf, MatFormField, MatSelect, MatOption, NgForOf, TranslateModule, MatSlideToggle, IconButtonComponent],
})
export class UserProfileScreenComponent extends BaseFormComponent {
readonly form: FormGroup<AsControl<UserProfileForm>> = this.#getForm();
initialFormValue = this.form.getRawValue();
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
#profileModel: IProfile;
readonly translations = languagesTranslations;
readonly devMode = this._userPreferenceService.isIqserDevMode;
readonly profileKeys = ['email', 'firstName', 'lastName'];
readonly languages = this._translateService.langs;
readonly language = formControlToSignal<UserProfileForm['language']>(this.form.controls.language);
readonly languageSelectLabel = computed(() => this.translations[this.language()]);
constructor(
private readonly _userService: UserService,
private readonly _loadingService: LoadingService,
@ -69,29 +45,43 @@ export class UserProfileScreenComponent extends BaseFormComponent {
protected readonly _userPreferenceService: UserPreferenceService,
private readonly _changeRef: ChangeDetectorRef,
private readonly _toaster: Toaster,
private readonly _pdfViewer: PdfViewer,
) {
super();
if (!this._permissionsService.has(Roles.updateMyProfile)) {
this.form.disable();
}
this._loadingService.stop();
this._loadingService.start();
}
get languageChanged(): boolean {
return this.initialFormValue['language'] !== this.form.controls.language.value;
return this.#profileModel['language'] !== this.form.get('language').value;
}
get themeChanged(): boolean {
return this.initialFormValue['darkTheme'] !== this.form.controls.darkTheme.value;
return this.#profileModel['darkTheme'] !== this.form.get('darkTheme').value;
}
get emailChanged(): boolean {
return this.initialFormValue['email'] !== this.form.controls.email.value;
return this.#profileModel['email'] !== this.form.get('email').value;
}
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> {
@ -116,17 +106,15 @@ export class UserProfileScreenComponent extends BaseFormComponent {
}
if (this.languageChanged) {
await this._languageService.change(this.form.controls.language.value);
await this._pdfViewer.instance?.UI.setLanguage(this._languageService.currentLanguage);
await this._languageService.change(this.form.get('language').value);
}
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._changeRef.markForCheck();
this._loadingService.stop();
this._initializeForm();
this._toaster.success(_('user-profile-screen.update.success'));
} catch (e) {
this._loadingService.stop();
@ -137,13 +125,35 @@ export class UserProfileScreenComponent extends BaseFormComponent {
await this._userService.createResetPasswordAction();
}
#getForm() {
private _getForm(): UntypedFormGroup {
return this._formBuilder.group({
email: [this._userService.currentUser.email ?? '', [Validators.required, Validators.email]],
firstName: [this._userService.currentUser.firstName ?? ''],
lastName: [this._userService.currentUser.lastName ?? ''],
language: [this._userPreferenceService.getLanguage()],
darkTheme: [this._userPreferenceService.getTheme() === 'dark'],
email: ['', [Validators.required, Validators.email]],
firstName: [''],
lastName: [''],
language: [''],
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();
}
}
}

View File

@ -2,18 +2,11 @@ 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';
import { 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,
@ -35,10 +28,6 @@ export const redactionAddOptions = [
label: redactTextTranslations.onlyHere.label,
value: RedactOrHintOptions.ONLY_HERE,
},
{
label: redactTextTranslations.inDocument.label,
value: RedactOrHintOptions.IN_DOCUMENT,
},
{
label: redactTextTranslations.inDossier.label,
value: RedactOrHintOptions.IN_DOSSIER,

View File

@ -1,24 +1,22 @@
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 { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
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 { 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 { templateExistsWhenEnteringAdmin } from '@guards/dossier-template-exists.guard';
import { entityExistsGuard } from '@guards/entity-exists-guard.service';
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 { PermissionsGuard } from '@guards/permissions-guard';
import { Roles } from '@users/roles';
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
import { AdminDialogService } from './services/admin-dialog.service';
import { AuditService } from './services/audit.service';
import { DigitalSignatureService } from './services/digital-signature.service';
@ -80,12 +78,7 @@ const dossierTemplateIdRoutes: IqserRoutes = [
},
type: 'ENTITY',
},
providers: [
RulesService,
provideEnvironmentInitializer(() => {
return inject(CopilotService).connectAsync('/api/llm/llm-websocket');
}),
],
providers: [RulesService],
},
{
path: 'component-rules',
@ -110,14 +103,6 @@ const dossierTemplateIdRoutes: IqserRoutes = [
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'),

View File

@ -5,6 +5,7 @@ import { RouterOutlet } from '@angular/router';
@Component({
templateUrl: './base-admin-screen.component.html',
styleUrls: ['./base-admin-screen.component.scss'],
standalone: true,
imports: [AdminSideNavComponent, RouterOutlet],
})
export class BaseAdminScreenComponent {}

View File

@ -9,6 +9,7 @@ import { AdminSideNavComponent } from '../shared/components/admin-side-nav/admin
@Component({
templateUrl: './base-dossier-template-screen.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
DossierTemplateBreadcrumbsComponent,
DossierTemplateActionsComponent,

View File

@ -18,6 +18,7 @@ import { AdminSideNavComponent } from '../shared/components/admin-side-nav/admin
@Component({
templateUrl: './base-entity-screen.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
DossierTemplateBreadcrumbsComponent,
CircleButtonComponent,

View File

@ -23,7 +23,7 @@
<div class="mt-44">
<redaction-donut-chart
[config]="chartConfig()"
[config]="chartConfig"
[radius]="63"
[strokeWidth]="15"
[subtitles]="['user-stats.chart.users' | translate]"

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, input, Input, output, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DonutChartConfig } from '@red/domain';
import { CircleButtonComponent } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
@ -8,9 +8,10 @@ import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.
selector: 'redaction-users-stats',
templateUrl: './users-stats.component.html',
styleUrls: ['./users-stats.component.scss'],
standalone: true,
imports: [CircleButtonComponent, TranslateModule, DonutChartComponent],
})
export class UsersStatsComponent {
readonly chartConfig = input.required<DonutChartConfig[]>();
readonly toggleCollapse = output();
@Output() toggleCollapse = new EventEmitter();
@Input() chartConfig: DonutChartConfig[];
}

View File

@ -16,6 +16,7 @@ export interface CloneTemplateData {
@Component({
templateUrl: './add-clone-dossier-template-dialog.component.html',
styleUrls: ['./add-clone-dossier-template-dialog.component.scss'],
standalone: true,
imports: [TranslateModule, ReactiveFormsModule, IconButtonComponent, CircleButtonComponent],
})
export class AddCloneDossierTemplateDialogComponent extends BaseDialogComponent {

View File

@ -8,6 +8,7 @@ import { UserDetailsComponent } from './user-details/user-details.component';
@Component({
selector: 'redaction-add-edit-user-dialog',
templateUrl: './add-edit-user-dialog.component.html',
standalone: true,
imports: [UserDetailsComponent, ResetPasswordComponent, CircleButtonComponent],
})
export class AddEditUserDialogComponent extends BaseDialogComponent {

View File

@ -10,6 +10,7 @@ import { NamePipe } from '@common-ui/users/name.pipe';
@Component({
selector: 'redaction-reset-password',
templateUrl: './reset-password.component.html',
standalone: true,
imports: [TranslateModule, NamePipe, ReactiveFormsModule, IconButtonComponent],
})
export class ResetPasswordComponent {

View File

@ -32,7 +32,7 @@
[formControlName]="role"
color="primary"
>
{{ translations[role] | translate: { count: 1 } }}
{{ translations[role] | translate }}
</mat-checkbox>
</div>
</div>

View File

@ -3,8 +3,10 @@ import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators }
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { BaseFormComponent, IconButtonComponent, LoadingService, Toaster } from '@iqser/common-ui';
import { rolesTranslations } from '@translations/roles-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { User } from '@red/domain';
import { UserService } from '@users/user.service';
import { HttpStatusCode } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { IProfileUpdateRequest } from '@iqser/common-ui/lib/users';
import { TranslateModule } from '@ngx-translate/core';
@ -15,6 +17,7 @@ import { NgForOf, NgIf } from '@angular/common';
selector: 'redaction-user-details',
templateUrl: './user-details.component.html',
styleUrls: ['./user-details.component.scss'],
standalone: true,
imports: [TranslateModule, ReactiveFormsModule, MatCheckbox, NgForOf, IconButtonComponent, NgIf],
})
export class UserDetailsComponent extends BaseFormComponent implements OnInit {
@ -98,7 +101,11 @@ export class UserDetailsComponent extends BaseFormComponent implements OnInit {
this.closeDialog.emit(true);
})
.catch(error => {
this._toaster.error(null, { error });
if (error.status === HttpStatusCode.Conflict) {
this._toaster.error(_('add-edit-user.error.email-already-used'));
} else {
this._toaster.error(_('add-edit-user.error.generic'));
}
this._loadingService.stop();
});
} else {

View File

@ -19,6 +19,7 @@ interface DialogData {
@Component({
templateUrl: './add-entity-dialog.component.html',
styleUrls: ['./add-entity-dialog.component.scss'],
standalone: true,
imports: [AddEditEntityComponent, TranslateModule, IconButtonComponent, NgIf, CircleButtonComponent, HelpButtonComponent],
})
export class AddEntityDialogComponent extends BaseDialogComponent {

View File

@ -6,11 +6,9 @@
<div class="table-header">Key</div>
<div class="table-header">Value</div>
<ng-container *ngFor="let entry of data.auditEntry.details | keyvalue: originalOrder">
<ng-container *ngFor="let entry of data.auditEntry.details | keyvalue : originalOrder">
<div class="bold">{{ entry.key | humanize }}</div>
<div>
<pre>{{ entry.value | json }}</pre>
</div>
<div>{{ entry.value }}</div>
</ng-container>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { JsonPipe, KeyValue, KeyValuePipe, NgForOf } from '@angular/common';
import { KeyValue, KeyValuePipe, NgForOf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { BaseDialogComponent, CircleButtonComponent, HumanizePipe } from '@iqser/common-ui';
@ -16,7 +16,8 @@ type OrderFn = (a: KeyValue<string, string>, b: KeyValue<string, string>) => num
templateUrl: './audit-info-dialog.component.html',
styleUrls: ['./audit-info-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TranslateModule, NgForOf, KeyValuePipe, HumanizePipe, CircleButtonComponent, JsonPipe],
standalone: true,
imports: [TranslateModule, NgForOf, KeyValuePipe, HumanizePipe, CircleButtonComponent],
})
export class AuditInfoDialogComponent extends BaseDialogComponent {
constructor(

View File

@ -20,6 +20,7 @@ const KMS_SIGNATURE_DIALOG_WIDTH = '810px';
@Component({
templateUrl: './configure-certificate-dialog.component.html',
styleUrls: ['./configure-certificate-dialog.component.scss'],
standalone: true,
imports: [
DetailsRadioComponent,
NgIf,

View File

@ -13,6 +13,7 @@ import { TranslateModule } from '@ngx-translate/core';
templateUrl: './kms-signature-configuration.component.html',
styleUrls: ['./kms-signature-configuration.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [UploadFileComponent, ReactiveFormsModule, NgIf, TranslateModule],
})
export class KmsSignatureConfigurationComponent extends BaseSignatureConfigurationComponent implements OnInit {

View File

@ -14,6 +14,7 @@ import { TranslateModule } from '@ngx-translate/core';
templateUrl: './pkcs-signature-configuration.component.html',
styleUrls: ['./pkcs-signature-configuration.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [UploadFileComponent, ReactiveFormsModule, NgIf, TranslateModule],
})
export class PkcsSignatureConfigurationComponent extends BaseSignatureConfigurationComponent implements OnInit {

View File

@ -20,6 +20,7 @@ interface IEditColorData {
@Component({
templateUrl: './edit-color-dialog.component.html',
styleUrls: ['./edit-color-dialog.component.scss'],
standalone: true,
imports: [ReactiveFormsModule, TranslateModule, ColorPickerModule, MatIcon, NgIf, IconButtonComponent, CircleButtonComponent],
})
export class EditColorDialogComponent extends BaseDialogComponent {

View File

@ -9,6 +9,7 @@ import { TranslateModule } from '@ngx-translate/core';
@Component({
selector: 'redaction-smtp-auth-dialog',
templateUrl: './smtp-auth-dialog.component.html',
standalone: true,
imports: [ReactiveFormsModule, TranslateModule, IconButtonComponent, CircleButtonComponent, MatDialogClose],
})
export class SmtpAuthDialogComponent extends BaseDialogComponent {

View File

@ -7,6 +7,7 @@ import { TranslateModule } from '@ngx-translate/core';
templateUrl: './upload-dictionary-dialog.component.html',
styleUrls: ['./upload-dictionary-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [IconButtonComponent, TranslateModule],
})
export class UploadDictionaryDialogComponent {

View File

@ -17,7 +17,7 @@ import { RouterHistoryService } from '@services/router-history.service';
import { auditCategoriesTranslations } from '@translations/audit-categories-translations';
import { Roles } from '@users/roles';
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
import dayjs, { Dayjs } from 'dayjs';
import { Dayjs } from 'dayjs';
import { firstValueFrom } from 'rxjs';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { AuditService } from '../../services/audit.service';
@ -36,6 +36,7 @@ const PAGE_SIZE = 50;
templateUrl: './audit-screen.component.html',
styleUrls: ['./audit-screen.component.scss'],
providers: listingProvidersFactory(AuditScreenComponent),
standalone: true,
imports: [
IqserListingModule,
TranslateModule,
@ -138,9 +139,16 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
const promises = [];
const category = this.form.get('category').value;
const userId = this.form.get('userId').value;
const from = this.form.get('from').value ? dayjs(this.form.get('from').value).startOf('day').toISOString() : null;
const to = this.form.get('to').value ? dayjs(this.form.get('to').value).endOf('day').toISOString() : null;
const from = this.form.get('from').value;
let to = this.form.get('to').value;
if (to) {
const hoursLeft = new Date(to).getHours();
const minutesLeft = new Date(to).getMinutes();
to = to
.clone()
.add(24 - hoursLeft - 1, 'h')
.add(60 - minutesLeft - 1);
}
const logsRequestBody: IAuditSearchRequest = {
pageSize: PAGE_SIZE,
page: page,

View File

@ -1,130 +0,0 @@
@if (componentDefinitions$ | async; as componentDefinitions) {
<div class="content-container">
<div class="content-header">
<div class="header-title">
<span
class="all-caps-label"
[innerHTML]="'component-definitions.title' | translate: { length: componentDefinitions.length }"
></span>
</div>
<div class="actions">
@if (permissionsService.canEditEntities()) {
<iqser-icon-button
(action)="createEmptyComponentDefinition()"
[label]="'component-definitions.add-new' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:plus"
></iqser-icon-button>
}
</div>
</div>
<div class="content">
<div class="components-list">
<div class="header">
<div class="item-content">
<div class="all-caps-label">{{ 'component-definitions.columns.position' | translate }}</div>
<div class="all-caps-label">{{ 'component-definitions.columns.name' | translate }}</div>
</div>
</div>
<div (cdkDropListDropped)="drop($event)" cdkDropList class="list-content">
@for (component of componentDefinitions; track component) {
<div
class="list-item"
[class.selected]="selectedComponent?.id === component.id"
cdkDrag
(click)="selectComponent(component)"
>
<div class="item-content">
<div class="table-item-title heading">
<mat-icon cdkDragHandle class="draggable" svgIcon="red:draggable-dots"></mat-icon>
<span> {{ component.rank }} </span>
</div>
<div class="table-item-title heading">{{ component.displayName }}</div>
<div class="right-content">
@if (permissionsService.canEditEntities()) {
<iqser-circle-button
[class]="'delete-component-definition'"
(action)="deleteComponent(component.id)"
[tooltip]="'trash.action.delete' | translate"
icon="iqser:trash"
></iqser-circle-button>
}
<mat-icon
[class.not-visible]="selectedComponent?.id !== component.id"
class="arrow-right"
svgIcon="red:arrow-right"
></mat-icon>
</div>
</div>
</div>
}
</div>
</div>
@if (selectedComponent) {
<section class="dialog">
<div class="dialog-header">
@if (selectedComponent.id) {
<div
class="heading-l"
[innerHTML]="'component-definitions.edit-title' | translate: { displayName: selectedComponent.displayName }"
></div>
} @else {
<div class="heading-l" [innerHTML]="'component-definitions.add-title' | translate"></div>
}
</div>
@if (form) {
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">
<div class="iqser-input-group w-300 required">
<label [translate]="'component-definitions.form.display-name'"></label>
<input
formControlName="displayName"
name="displayName"
placeholder="{{ 'component-definitions.form.display-name-placeholder' | translate }}"
type="text"
/>
</div>
<div class="iqser-input-group w-450">
<label [translate]="'component-definitions.form.description'"></label>
<textarea
[placeholder]="'component-definitions.form.description-placeholder' | translate"
formControlName="description"
iqserHasScrollbar
name="description"
rows="5"
type="text"
></textarea>
</div>
<div class="iqser-input-group w-450">
<label [translate]="'component-definitions.form.technical-name-label'"></label>
<span class="technical-name"> {{ technicalName }} </span>
<label [translate]="'component-definitions.form.autogenerated-label'"></label>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[disabled]="disabled"
[label]="'component-definitions.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
@if (selectedComponent.id) {
<div
(click)="initForm()"
[class.disabled]="disabled"
[translate]="'component-definitions.actions.revert'"
class="all-caps-label cancel"
></div>
}
</div>
</form>
}
</section>
}
</div>
</div>
}

View File

@ -1,145 +0,0 @@
%item-content-style {
position: relative;
display: flex;
flex: 1;
margin: 5px;
div:first-child {
width: 100px;
}
div {
display: flex;
align-items: center;
justify-content: center;
.draggable {
cursor: grab;
transform: scale(0.7);
width: 40px;
margin-left: -30px;
::ng-deep svg {
fill: var(--iqser-grey-7);
}
}
}
.right-content {
visibility: hidden;
flex: 1;
justify-content: flex-end;
align-items: center;
gap: 5px;
iqser-circle-button {
visibility: hidden;
}
.arrow-right {
transform: scale(0.7);
&.not-visible {
visibility: hidden;
}
}
}
}
.content-container {
background-color: var(--iqser-grey-6);
height: 100vh;
display: flex;
flex-direction: column;
.content-header {
display: flex;
height: 50px;
background-color: var(--iqser-grey-6);
align-items: center;
padding: 0 24px;
.actions {
display: flex;
flex: 1;
justify-content: flex-end;
}
}
.content {
flex: 1;
display: flex;
gap: 20px;
overflow: hidden;
.components-list {
flex: 1;
background-color: var(--iqser-white);
display: flex;
flex-direction: column;
.header {
height: 30px;
}
.list-content {
overflow-y: auto;
height: calc(100vh - 30px);
outline: none;
}
.list-item {
height: 80px;
transition: background 0.3s ease;
}
.header,
.list-item {
display: flex;
border-bottom: 1px solid var(--iqser-separator);
.item-content {
@extend %item-content-style;
}
}
.list-item:hover,
.selected {
cursor: pointer;
.item-content {
background-color: var(--iqser-grey-8);
.right-content {
visibility: visible;
}
}
}
.list-item:hover > .item-content > .right-content > iqser-circle-button {
visibility: visible;
}
}
section {
background-color: var(--iqser-white);
border-radius: 8px;
width: 700px;
height: 470px;
margin-right: 150px;
.technical-name {
min-height: 16px;
}
}
}
}
.cdk-drag-preview {
.list-item {
transition: background 0.3s ease;
}
.item-content {
@extend %item-content-style;
}
}

View File

@ -1,138 +0,0 @@
import { Component, OnInit, signal } from '@angular/core';
import {
BaseFormComponent,
CircleButtonComponent,
HasScrollbarDirective,
IconButtonComponent,
listingProvidersFactory,
LoadingService,
} from '@iqser/common-ui';
import { ComponentDefinitionsService } from '@services/entity-services/component-definitions.service';
import { firstValueFrom } from 'rxjs';
import { getParam } from '@common-ui/utils';
import { DOSSIER_TEMPLATE_ID, IComponentDefinition } from '@red/domain';
import { toObservable } from '@angular/core/rxjs-interop';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { PermissionsService } from '@services/permissions.service';
import { MatIcon } from '@angular/material/icon';
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { AdminDialogService } from '../../services/admin-dialog.service';
@Component({
templateUrl: './component-definitions.component.html',
styleUrls: ['./component-definitions.component.scss'],
providers: listingProvidersFactory(ComponentDefinitionsComponent),
imports: [
IconButtonComponent,
TranslateModule,
CommonModule,
MatIcon,
CdkDragHandle,
CdkDropList,
CdkDrag,
FormsModule,
ReactiveFormsModule,
CircleButtonComponent,
HasScrollbarDirective,
],
})
export default class ComponentDefinitionsComponent extends BaseFormComponent implements OnInit {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly #componentDefinitions = signal<IComponentDefinition[]>([]);
protected readonly componentDefinitions$ = toObservable(this.#componentDefinitions);
protected selectedComponent: IComponentDefinition | null = null;
constructor(
private readonly _loadingService: LoadingService,
private readonly _componentDefinitionsService: ComponentDefinitionsService,
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: AdminDialogService,
protected readonly permissionsService: PermissionsService,
) {
super();
}
async ngOnInit() {
this._loadingService.stop();
await this.#loadData();
}
async #loadData() {
const componentDefinitions = await firstValueFrom(
this._componentDefinitionsService.getComponentDefinitions(this.#dossierTemplateId),
);
this.#componentDefinitions.set(componentDefinitions);
}
async createEmptyComponentDefinition() {
this.selectedComponent = {
displayName: '',
description: '',
} as IComponentDefinition;
this.initForm();
}
async save() {
if (this.selectedComponent.id) {
const component = { ...this.form.getRawValue(), id: this.selectedComponent.id };
await firstValueFrom(this._componentDefinitionsService.updateComponentDefinition(this.#dossierTemplateId, component));
} else {
const component = {
...this.form.getRawValue(),
dossierTemplateId: this.#dossierTemplateId,
technicalName: this.technicalName,
};
await firstValueFrom(this._componentDefinitionsService.createComponentDefinition(this.#dossierTemplateId, component));
}
await this.#loadData();
this.selectComponent();
await this.initForm();
}
async drop(event: CdkDragDrop<string>) {
if (event.isPointerOverContainer) {
moveItemInArray(this.#componentDefinitions(), event.previousIndex, event.currentIndex);
const componentIds = this.#componentDefinitions().map(c => c.id);
const componentDefinitions = await firstValueFrom(
this._componentDefinitionsService.reorderComponentDefinitions(this.#dossierTemplateId, componentIds),
);
this.#componentDefinitions.set(componentDefinitions);
}
}
async deleteComponent(componentId: string) {
this._dialogService.openDialog('confirm', null, async () => {
await firstValueFrom(this._componentDefinitionsService.deleteComponentDefinitions(this.#dossierTemplateId, [componentId]));
await this.#loadData();
this.selectedComponent = null;
});
}
initForm() {
this.form = this.#getForm();
this.initialFormValue = this.form.getRawValue();
}
#getForm() {
return this._formBuilder.group({
displayName: [this.selectedComponent?.displayName, Validators.required],
description: [this.selectedComponent?.description],
});
}
selectComponent(component?: IComponentDefinition) {
if (component && this.selectedComponent?.id !== component.id) {
this.selectedComponent = component;
this.initForm();
return;
}
this.selectedComponent = null;
}
get technicalName() {
return this.selectedComponent.technicalName ?? this.form.get('displayName')?.value?.toLowerCase().trim().replace(/\s+/g, '_');
}
}

View File

@ -24,15 +24,10 @@
<div class="iqser-input-group required">
<label translate="add-edit-component-mapping.form.file"></label>
<iqser-upload-file (fileChanged)="changeFile($event)" [file]="activeFile" [accept]="'.csv'" />
<iqser-upload-file (fileChanged)="fileChanged($event)" [file]="activeFile" [accept]="'.csv'" />
</div>
<div
class="row"
[matTooltip]="'add-edit-component-mapping.disabled-file-options' | translate"
[matTooltipDisabled]="!form.get('encoding')?.disabled"
[matTooltipPosition]="'above'"
>
<div class="row">
<div class="iqser-input-group required w-150">
<label translate="add-edit-component-mapping.form.delimiter"></label>
<input
@ -42,15 +37,6 @@
type="text"
/>
</div>
<div class="iqser-input-group required w-150">
<label translate="add-edit-component-mapping.form.quote-char"></label>
<input
[placeholder]="'add-edit-component-mapping.form.quote-char-placeholder' | translate"
formControlName="quoteChar"
name="quoteChar"
type="text"
/>
</div>
<div class="iqser-input-group required w-150">
<label translate="add-edit-component-mapping.form.encoding-type"></label>

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { CircleButtonComponent, IconButtonComponent, IqserDialogComponent, UploadFileComponent } from '@iqser/common-ui';
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, IqserDialogComponent, UploadFileComponent } from '@iqser/common-ui';
import { FileAttributeEncodingTypes, IComponentMapping } from '@red/domain';
import { FormBuilder, ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
@ -7,30 +7,27 @@ import { NgForOf, NgIf } from '@angular/common';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatDialogModule } from '@angular/material/dialog';
import { MatOption } from '@angular/material/autocomplete';
import { MatSelect } from '@angular/material/select';
import { MatSelect, MatSelectTrigger } from '@angular/material/select';
import { fileAttributeEncodingTypesTranslations } from '@translations/file-attribute-encoding-types-translations';
import { firstValueFrom } from 'rxjs';
import { ComponentMappingsService } from '@services/entity-services/component-mappings.service';
import { MatTooltip } from '@angular/material/tooltip';
interface DialogData {
dossierTemplateId: string;
mapping: IComponentMapping;
}
interface DialogResult {
id: string;
name: string;
file: Blob;
encoding: string;
delimiter: string;
quoteChar: string;
fileName?: string;
}
@Component({
templateUrl: './add-edit-component-mapping-dialog.component.html',
styleUrls: ['./add-edit-component-mapping-dialog.component.scss'],
standalone: true,
imports: [
TranslateModule,
ReactiveFormsModule,
@ -40,10 +37,10 @@ interface DialogResult {
CircleButtonComponent,
MatDialogModule,
MatOption,
MatSelectTrigger,
MatSelect,
IconButtonComponent,
UploadFileComponent,
MatTooltip,
],
})
export class AddEditComponentMappingDialogComponent
@ -66,20 +63,16 @@ export class AddEditComponentMappingDialogComponent
async ngOnInit() {
if (this.data.mapping?.fileName) {
this.activeFile = { name: this.data.mapping.fileName } as File;
const fileContent = await firstValueFrom(
const file = await firstValueFrom(
this._componentMappingService.getComponentMappingFile(this.data.dossierTemplateId, this.data.mapping.id),
);
const file = new Blob([fileContent.body as Blob], { type: 'text/csv' });
this.form.get('file').setValue(file);
this.initialFormValue = this.form.getRawValue();
this.#disableEncodingAndQuoteCharAndDelimiter();
}
}
changeFile(file: File) {
fileChanged(file: Blob) {
this.form.get('file').setValue(file);
this.form.get('fileName').setValue(file?.name);
this.#enableEncodingAndQuoteCharAndDelimiter();
}
save() {
@ -90,22 +83,8 @@ export class AddEditComponentMappingDialogComponent
return this._formBuilder.group({
name: [this.data?.mapping?.name, Validators.required],
file: [null, Validators.required],
fileName: [this.data?.mapping?.fileName, Validators.required],
encoding: this.encodingTypeOptions.find(e => e === this.data?.mapping?.encoding) ?? this.encodingTypeOptions[0],
delimiter: [this.data?.mapping?.delimiter ?? ',', Validators.required],
quoteChar: [this.data?.mapping?.quoteChar ?? '"', Validators.required],
});
}
#disableEncodingAndQuoteCharAndDelimiter() {
this.form.get('encoding').disable();
this.form.get('delimiter').disable();
this.form.get('quoteChar').disable();
}
#enableEncodingAndQuoteCharAndDelimiter() {
this.form.get('encoding').enable();
this.form.get('delimiter').enable();
this.form.get('quoteChar').enable();
}
}

View File

@ -44,10 +44,6 @@
<span>{{ entity.numberOfLines }}</span>
</div>
<div class="cell" [matTooltip]="entity.columnLabelsString" [matTooltipPosition]="'above'" [matTooltipClass]="'custom-tooltip'">
<span class="ellipsis">{{ entity.columnLabelsString }}</span>
</div>
<div class="cell">
<div *allow="roles.componentMappings.write; if: currentUser.isAdmin" class="action-buttons">
<iqser-circle-button

View File

@ -1,11 +0,0 @@
.ellipsis {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
::ng-deep .custom-tooltip {
max-width: 300px;
white-space: normal;
word-wrap: break-word;
}

View File

@ -1,5 +1,6 @@
import { AsyncPipe, NgIf } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { RouterLink } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
import { getCurrentUser } from '@common-ui/users';
@ -24,24 +25,29 @@ import { Roles } from '@users/roles';
import { combineLatest, firstValueFrom } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { AdminSideNavComponent } from '../../shared/components/admin-side-nav/admin-side-nav.component';
import { DossierTemplateActionsComponent } from '../../shared/components/dossier-template-actions/dossier-template-actions.component';
import { DossierTemplateBreadcrumbsComponent } from '../../shared/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
import { AddEditComponentMappingDialogComponent } from './add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component';
import { download } from '@utils/file-download-utils';
import { MatTooltip } from '@angular/material/tooltip';
@Component({
templateUrl: './component-mappings-screen.component.html',
styleUrls: ['./component-mappings-screen.component.scss'],
providers: listingProvidersFactory(ComponentMappingsScreenComponent),
standalone: true,
imports: [
DossierTemplateBreadcrumbsComponent,
AsyncPipe,
NgIf,
DossierTemplateActionsComponent,
CircleButtonComponent,
TranslateModule,
RouterLink,
AdminSideNavComponent,
IqserListingModule,
InputWithActionComponent,
IconButtonComponent,
IqserAllowDirective,
MatTooltip,
],
})
export default class ComponentMappingsScreenComponent extends ListingComponent<ComponentMapping> implements OnInit {
@ -55,7 +61,6 @@ export default class ComponentMappingsScreenComponent extends ListingComponent<C
{ label: _('component-mappings-screen.table-col-names.name'), sortByKey: 'searchKey' },
{ label: _('component-mappings-screen.table-col-names.version') },
{ label: _('component-mappings-screen.table-col-names.number-of-lines') },
{ label: _('component-mappings-screen.table-col-names.column-labels') },
];
readonly tableHeaderLabel = _('component-mappings-screen.table-header.title');
@ -90,8 +95,8 @@ export default class ComponentMappingsScreenComponent extends ListingComponent<C
const result = await dialog.result();
if (result) {
this._loadingService.start();
const { id, name, encoding, delimiter, fileName, quoteChar } = result;
const newMapping = { id, name, encoding, delimiter, fileName, quoteChar };
const { id, name, encoding, delimiter } = result;
const newMapping = { id, name, encoding, delimiter };
await firstValueFrom(
this._componentMappingService.createUpdateComponentMapping(this.#dossierTemplateId, newMapping, result.file),
);

View File

@ -33,6 +33,7 @@ interface ListItem extends IListable {
styleUrls: ['./default-colors-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: listingProvidersFactory(DefaultColorsScreenComponent),
standalone: true,
imports: [IqserListingModule, NgStyle, CircleButtonComponent, IqserAllowDirective, TranslateModule, AsyncPipe, NgIf],
})
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> implements OnInit {

View File

@ -25,6 +25,7 @@ import { DigitalSignatureService } from '../../services/digital-signature.servic
selector: 'redaction-digital-signature-screen',
templateUrl: './digital-signature-screen.component.html',
styleUrls: ['./digital-signature-screen.component.scss'],
standalone: true,
imports: [
IqserListingModule,
EmptyStateComponent,

View File

@ -20,6 +20,7 @@ export interface AddEditDossierAttributeDialogData {
@Component({
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'],
standalone: true,
imports: [
TranslateModule,
ReactiveFormsModule,
@ -90,7 +91,7 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent
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);
this._toaster.error(_('add-edit-dossier-attribute.error.generic'), { error });
return undefined;
});

View File

@ -35,6 +35,7 @@ import { TableItemComponent } from './table-item/table-item.component';
entitiesService: DossierAttributesService,
component: DossierAttributesListingScreenComponent,
}),
standalone: true,
imports: [
IqserListingModule,
TranslateModule,

View File

@ -10,6 +10,7 @@ import { NgIf } from '@angular/common';
selector: 'redaction-table-item [attribute] [canEditDossierAttributes]',
templateUrl: './table-item.component.html',
styleUrls: ['./table-item.component.scss'],
standalone: true,
imports: [MatTooltip, TranslateModule, CircleButtonComponent, NgIf],
})
export class TableItemComponent {

View File

@ -47,7 +47,6 @@
<div class="dialog-actions">
<iqser-icon-button
(action)="save()"
[buttonId]="'save-dossier-state'"
[disabled]="disabled"
[label]="'add-edit-dossier-state.save' | translate"
[submit]="true"

View File

@ -18,6 +18,7 @@ export interface AddEditDossierStateDialogData {
@Component({
templateUrl: './add-edit-dossier-state-dialog.component.html',
styleUrls: ['./add-edit-dossier-state-dialog.component.scss'],
standalone: true,
imports: [TranslateModule, ReactiveFormsModule, ColorPickerModule, MatIcon, NgIf, IconButtonComponent, CircleButtonComponent],
})
export class AddEditDossierStateDialogComponent extends BaseDialogComponent {

View File

@ -4,12 +4,12 @@
</div>
<div class="dialog-content">
<div [innerHTML]="'confirm-delete-dossier-state.warning' | translate: translateArgs" class="heading"></div>
<div [innerHTML]="'confirm-delete-dossier-state.warning' | translate : translateArgs" class="heading"></div>
<form *ngIf="data.dossierCount !== 0 && data.otherStates.length > 0" [formGroup]="form" class="mt-16">
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="replace">
{{ 'confirm-delete-dossier-state.question' | translate: { count: data.dossierCount } }}
{{ 'confirm-delete-dossier-state.question' | translate : { count: data.dossierCount } }}
</mat-checkbox>
</div>
@ -30,12 +30,7 @@
</div>
<div class="dialog-actions">
<iqser-icon-button
(action)="save()"
[buttonId]="'confirm-delete-dossier-state'"
[label]="label | translate"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
<iqser-icon-button (action)="save()" [label]="label | translate" [type]="iconButtonTypes.primary"></iqser-icon-button>
<div [translate]="'confirm-delete-dossier-state.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
</div>

View File

@ -11,7 +11,7 @@ import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.se
import { TranslateModule } from '@ngx-translate/core';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatFormField } from '@angular/material/form-field';
import { MatOption, MatSelect } from '@angular/material/select';
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { NgForOf, NgIf } from '@angular/common';
export interface ConfirmDeleteDossierStateDialogData {
@ -23,11 +23,13 @@ export interface ConfirmDeleteDossierStateDialogData {
@Component({
templateUrl: './confirm-delete-dossier-state-dialog.component.html',
styleUrls: ['./confirm-delete-dossier-state-dialog.component.scss'],
standalone: true,
imports: [
TranslateModule,
ReactiveFormsModule,
MatCheckbox,
MatFormField,
MatSelectTrigger,
MatSelect,
MatOption,
NgForOf,

View File

@ -33,6 +33,7 @@ import { DossierStatesTableItemComponent } from '../dossier-states-table-item/do
templateUrl: './dossier-states-listing-screen.component.html',
styleUrls: ['./dossier-states-listing-screen.component.scss'],
providers: listingProvidersFactory(DossierStatesListingScreenComponent),
standalone: true,
imports: [
IqserListingModule,
DonutChartComponent,

View File

@ -13,20 +13,18 @@
<span class="small-label">{{ state.dossierCount }}</span>
</div>
<div [id]="'dossier_' + (state.name | snakeCase)" class="cell">
<div class="cell">
<div *ngIf="permissionsService.canPerformDossierStatesActions()" class="action-buttons">
<div [attr.help-mode-key]="'edit_delete_dossier_state'">
<iqser-circle-button
(action)="openEditStateDialog(state)"
[tooltip]="'dossier-states-listing.action.edit' | translate"
[buttonId]="'dossier-state-edit-button'"
icon="iqser:edit"
></iqser-circle-button>
<iqser-circle-button
(action)="openConfirmDeleteStateDialog(state)"
[tooltip]="'dossier-states-listing.action.delete' | translate"
[buttonId]="'dossier-state-delete-button'"
icon="iqser:trash"
></iqser-circle-button>
</div>

View File

@ -14,13 +14,13 @@ import {
import { MatTooltip } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { NgIf } from '@angular/common';
import { SnakeCasePipe } from '@common-ui/pipes/snake-case.pipe';
@Component({
selector: 'redaction-dossier-states-table-item',
templateUrl: './dossier-states-table-item.component.html',
styleUrls: ['./dossier-states-table-item.component.scss'],
imports: [MatTooltip, CircleButtonComponent, TranslateModule, NgIf, SnakeCasePipe],
standalone: true,
imports: [MatTooltip, CircleButtonComponent, TranslateModule, NgIf],
})
export class DossierStatesTableItemComponent {
readonly #dialog = inject(MatDialog);

View File

@ -33,6 +33,7 @@ import { TableItemComponent } from '../table-item/table-item.component';
entitiesService: DossierTemplatesService,
component: DossierTemplatesListingScreenComponent,
}),
standalone: true,
imports: [
IqserListingModule,
TranslateModule,

View File

@ -17,6 +17,7 @@ import { DatePipe } from '@shared/pipes/date.pipe';
templateUrl: './table-item.component.html',
styleUrls: ['./table-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [MatTooltip, NgIf, AsyncPipe, MatIcon, TranslateModule, DatePipe, DossierTemplateActionsComponent, InitialsAvatarComponent],
})
export class TableItemComponent implements OnChanges {

View File

@ -87,7 +87,6 @@
[routerLink]="dict.routerLink"
[tooltip]="'entities-listing.action.edit' | translate"
icon="iqser:edit"
iqserStopPropagation
></iqser-circle-button>
</div>
</div>

View File

@ -11,7 +11,6 @@ import {
ListingComponent,
listingProvidersFactory,
LoadingService,
StopPropagationDirective,
TableColumnConfig,
} from '@iqser/common-ui';
import { getParam } from '@iqser/common-ui/lib/utils';
@ -30,6 +29,7 @@ import { AdminDialogService } from '../../services/admin-dialog.service';
templateUrl: './entities-listing-screen.component.html',
styleUrls: ['./entities-listing-screen.component.scss'],
providers: listingProvidersFactory(EntitiesListingScreenComponent),
standalone: true,
imports: [
IqserListingModule,
TranslateModule,
@ -41,7 +41,6 @@ import { AdminDialogService } from '../../services/admin-dialog.service';
AnnotationIconComponent,
AsyncPipe,
RouterLink,
StopPropagationDirective,
],
})
export class EntitiesListingScreenComponent extends ListingComponent<Dictionary> implements OnInit {

View File

@ -16,6 +16,7 @@ import { AsyncPipe } from '@angular/common';
templateUrl: './dictionary-screen.component.html',
styleUrls: ['./dictionary-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [AsyncPipe, DictionaryManagerComponent],
})
export class DictionaryScreenComponent implements OnInit {

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, HostListener, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { getConfig, HasScrollbarDirective, IconButtonComponent, IconButtonTypes } from '@iqser/common-ui';
import { getConfig, HasScrollbarDirective, HelpButtonComponent, IconButtonComponent, IconButtonTypes } from '@iqser/common-ui';
import { IqserEventTarget } from '@iqser/common-ui/lib/utils';
import { Dictionary, DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
@ -17,7 +17,17 @@ import { TranslateModule } from '@ngx-translate/core';
templateUrl: './entity-info.component.html',
styleUrls: ['./entity-info.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [HasScrollbarDirective, MatIcon, NgIf, TranslateModule, AsyncPipe, IconButtonComponent, AddEditEntityComponent],
standalone: true,
imports: [
HasScrollbarDirective,
MatIcon,
NgIf,
TranslateModule,
AsyncPipe,
IconButtonComponent,
AddEditEntityComponent,
HelpButtonComponent,
],
})
export class EntityInfoComponent {
@ViewChild(AddEditEntityComponent) private readonly _addEditEntityComponent: AddEditEntityComponent;

View File

@ -22,6 +22,7 @@ export interface AddEditFileAttributeDialogData {
@Component({
templateUrl: './add-edit-file-attribute-dialog.component.html',
styleUrls: ['./add-edit-file-attribute-dialog.component.scss'],
standalone: true,
imports: [
ReactiveFormsModule,
TranslateModule,

View File

@ -13,6 +13,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
@Component({
templateUrl: './file-attributes-configurations-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
ReactiveFormsModule,
MatSlideToggleModule,
@ -57,7 +58,6 @@ export class FileAttributesConfigurationsDialogComponent extends BaseDialogCompo
if (supportCsvMapping) {
return {
...this.#configuration,
keyColumn: this.form.get('keyColumn').value,
filenameMappingColumnHeaderName: this.form.get('keyColumn').value,
delimiter: this.form.get('delimiter').value,
encoding: this.form.get('encodingType').value,
@ -67,14 +67,13 @@ export class FileAttributesConfigurationsDialogComponent extends BaseDialogCompo
return {
...this.#configuration,
filenameMappingColumnHeaderName: '',
keyColumn: this.form.get('keyColumn').value,
};
}
#getForm() {
return this._formBuilder.group({
supportCsvMapping: [!!this.#configuration.filenameMappingColumnHeaderName],
keyColumn: [this.#configuration.filenameMappingColumnHeaderName || this.#configuration.keyColumn || '', [Validators.required]],
keyColumn: [this.#configuration.filenameMappingColumnHeaderName || '', [Validators.required]],
delimiter: [this.#configuration.delimiter || '', [Validators.required]],
encodingType: [this.#configuration.encoding || FileAttributeEncodingTypes['UTF-8'], [Validators.required]],
});

View File

@ -2,8 +2,8 @@ import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatFormField } from '@angular/material/form-field';
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
import { MatOption, MatSelect } from '@angular/material/select';
import { MatMenu, MatMenuTrigger } from '@angular/material/menu';
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { MatSlideToggle } from '@angular/material/slide-toggle';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ChevronButtonComponent } from '@common-ui/buttons/chevron-button';
@ -19,6 +19,7 @@ import { fileAttributeTypesTranslations } from '@translations/file-attribute-typ
templateUrl: './active-fields-listing.component.html',
styleUrls: ['./active-fields-listing.component.scss'],
providers: listingProvidersFactory(ActiveFieldsListingComponent),
standalone: true,
imports: [
IqserListingModule,
CircleButtonComponent,
@ -29,6 +30,7 @@ import { fileAttributeTypesTranslations } from '@translations/file-attribute-typ
MatMenu,
EditableInputComponent,
MatFormField,
MatSelectTrigger,
MatSelect,
MatOption,
FormsModule,
@ -36,7 +38,6 @@ import { fileAttributeTypesTranslations } from '@translations/file-attribute-typ
RoundCheckboxComponent,
NgForOf,
NgIf,
MatMenuItem,
],
})
export class ActiveFieldsListingComponent extends ListingComponent<IField> implements OnChanges {

View File

@ -4,7 +4,7 @@ import { AbstractControl, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGr
import { MatAutocomplete, MatAutocompleteTrigger, MatOption } from '@angular/material/autocomplete';
import { MAT_DIALOG_DATA, MatDialogClose, MatDialogRef } from '@angular/material/dialog';
import { MatFormField } from '@angular/material/form-field';
import { MatSelect } from '@angular/material/select';
import { MatSelect, MatSelectTrigger } from '@angular/material/select';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
import {
@ -38,6 +38,7 @@ export interface IFileAttributesCSVImportData {
styleUrls: ['./file-attributes-csv-import-dialog.component.scss'],
providers: listingProvidersFactory(),
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
ReactiveFormsModule,
MatFormField,
@ -45,6 +46,7 @@ export interface IFileAttributesCSVImportData {
MatAutocomplete,
AsyncPipe,
MatOption,
MatSelectTrigger,
MatSelect,
CircleButtonComponent,
NgIf,

View File

@ -46,6 +46,7 @@ import {
templateUrl: './file-attributes-listing-screen.component.html',
styleUrls: ['./file-attributes-listing-screen.component.scss'],
providers: listingProvidersFactory(FileAttributesListingScreenComponent),
standalone: true,
imports: [
IqserListingModule,
NgForOf,
@ -85,7 +86,6 @@ export default class FileAttributesListingScreenComponent extends ListingCompone
},
];
readonly roles = Roles;
keyColumnValue: string = '';
constructor(
readonly permissionsService: PermissionsService,
@ -172,13 +172,13 @@ export default class FileAttributesListingScreenComponent extends ListingCompone
FileAttributesConfigurationsDialogComponent,
{
...defaultDialogConfig,
data: { ...this.#existingConfiguration, keyColumn: this.keyColumnValue },
data: this.#existingConfiguration,
},
);
const configuration = await firstValueFrom(ref.afterClosed());
if (configuration) {
this.keyColumnValue = configuration.keyColumn;
await this.#setConfigAndLoadData(configuration);
}
}

View File

@ -1,5 +1,6 @@
<div class="dialog-header">
<div class="heading-l" translate="general-config-screen.general.title"></div>
<div translate="general-config-screen.general.subtitle"></div>
</div>
<form (submit)="save()" *ngIf="form" [formGroup]="form">
<div class="dialog-content">

View File

@ -13,6 +13,7 @@ import { MatSlideToggle } from '@angular/material/slide-toggle';
@Component({
selector: 'redaction-general-config-form',
templateUrl: './general-config-form.component.html',
standalone: true,
imports: [ReactiveFormsModule, TranslateModule, NgIf, MatSlideToggle, IconButtonComponent],
})
export class GeneralConfigFormComponent extends BaseFormComponent implements OnInit {

View File

@ -11,12 +11,7 @@
<div class="dialog mt-24 mb-0">
<redaction-system-preferences-form></redaction-system-preferences-form>
</div>
@if (smtpLicenseFeatureEnabled) {
<div class="dialog mt-24">
<redaction-smtp-form></redaction-smtp-form>
</div>
} @else {
<div style="visibility: hidden" class="dialog mt-24"></div>
}
<div class="dialog mt-24">
<redaction-smtp-form></redaction-smtp-form>
</div>
</div>

View File

@ -6,29 +6,26 @@ import { BaseFormComponent, IqserListingModule } from '@iqser/common-ui';
import { SystemPreferencesFormComponent } from './system-preferences-form/system-preferences-form.component';
import { RouterHistoryService } from '@services/router-history.service';
import { TranslateModule } from '@ngx-translate/core';
import { LicenseService } from '@services/license.service';
import { ILicenseFeature } from '@red/domain';
@Component({
selector: 'redaction-general-config-screen',
templateUrl: './general-config-screen.component.html',
styleUrls: ['./general-config-screen.component.scss'],
standalone: true,
imports: [IqserListingModule, GeneralConfigFormComponent, SystemPreferencesFormComponent, SmtpFormComponent, TranslateModule],
})
export class GeneralConfigScreenComponent extends BaseFormComponent implements AfterViewInit {
readonly currentUser = inject(UserService).currentUser;
readonly routerHistoryService = inject(RouterHistoryService);
readonly licenseService = inject(LicenseService);
@ViewChild(GeneralConfigFormComponent) generalConfigFormComponent: GeneralConfigFormComponent;
@ViewChild(SystemPreferencesFormComponent) systemPreferencesFormComponent: SystemPreferencesFormComponent;
@ViewChild(SmtpFormComponent) smtpFormComponent: SmtpFormComponent;
children: BaseFormComponent[];
smtpLicenseFeatureEnabled: boolean;
get changed(): boolean {
for (const child of this.children) {
if (child?.changed) {
if (child.changed) {
return true;
}
}
@ -46,8 +43,6 @@ export class GeneralConfigScreenComponent extends BaseFormComponent implements A
ngAfterViewInit() {
this.children = [this.generalConfigFormComponent, this.systemPreferencesFormComponent, this.smtpFormComponent];
let licenseFeature: ILicenseFeature = this.licenseService.getFeature('configurableSMTPServer');
this.smtpLicenseFeatureEnabled = licenseFeature != null && licenseFeature.value === true;
}
async save(): Promise<void> {

View File

@ -21,6 +21,7 @@ import { NgIf } from '@angular/common';
@Component({
selector: 'redaction-smtp-form',
templateUrl: './smtp-form.component.html',
standalone: true,
imports: [ReactiveFormsModule, TranslateModule, MatSlideToggle, IconButtonComponent, NgIf],
})
export class SmtpFormComponent extends BaseFormComponent implements OnInit {

View File

@ -15,6 +15,7 @@ export type ValueType = 'number' | 'string' | 'boolean';
@Component({
selector: 'redaction-system-preferences-form',
templateUrl: './system-preferences-form.component.html',
standalone: true,
imports: [NgIf, ReactiveFormsModule, NgForOf, TranslateModule, MatSlideToggle, IconButtonComponent],
})
export class SystemPreferencesFormComponent extends BaseFormComponent {

View File

@ -34,20 +34,6 @@
<span [innerHTML]="'dossier-template-info-screen.created-on' | translate: { date: createdOn }"></span>
</div>
<div *ngIf="areRulesLocked()">
<mat-icon
(click)="resetRules()"
[matTooltip]="
currentUser.isAdmin
? ('dossier-template-info-screen.rules-reset.tooltip' | translate)
: ('dossier-template-info-screen.rules-reset.disabled-action' | translate)
"
[class.action-icon]="currentUser.isAdmin"
svgIcon="iqser:alert-circle"
></mat-icon>
<span class="error">{{ 'dossier-template-info-screen.rules-reset.label' | translate }}</span>
</div>
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
{{ 'dossier-template-info-screen.entries' | translate: { count: ctx.stats.numberOfEntries } }}

View File

@ -18,11 +18,3 @@
padding-right: 24px;
margin-right: 24px;
}
.error {
color: var(--iqser-primary);
}
.action-icon {
cursor: pointer;
}

View File

@ -1,4 +1,4 @@
import { Component, computed, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { ContextComponent } from '@iqser/common-ui/lib/utils';
import { type DossierTemplate, type DossierTemplateStats } from '@red/domain';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
@ -9,12 +9,6 @@ import { MatIcon } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
import { InitialsAvatarComponent } from '@common-ui/users';
import { DatePipe } from '@shared/pipes/date.pipe';
import { RulesService } from '../../../services/rules.service';
import { Toaster } from '@iqser/common-ui';
import { MatTooltip } from '@angular/material/tooltip';
import { firstValueFrom } from 'rxjs';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { getCurrentUser } from '@users/user.service';
interface Context {
readonly dossierTemplate: DossierTemplate;
@ -25,21 +19,17 @@ interface Context {
selector: 'redaction-dossier-template-details',
templateUrl: './dossier-template-details.component.html',
styleUrls: ['./dossier-template-details.component.scss'],
imports: [NgIf, AsyncPipe, MatIcon, TranslateModule, DatePipe, InitialsAvatarComponent, MatTooltip],
standalone: true,
imports: [NgIf, AsyncPipe, MatIcon, TranslateModule, DatePipe, InitialsAvatarComponent],
})
export class DossierTemplateDetailsComponent extends ContextComponent<Context> implements OnInit {
readonly translations = dossierTemplateStatusTranslations;
@Input({ required: true }) dossierTemplateId: string;
readonly areRulesLocked = computed(() => {
return this._rulesService.currentTemplateRules().timeoutDetected;
});
readonly currentUser = getCurrentUser();
constructor(
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
private readonly _rulesService: RulesService,
private readonly _toaster: Toaster,
) {
super();
}
@ -50,15 +40,4 @@ export class DossierTemplateDetailsComponent extends ContextComponent<Context> i
stats: this._dossierTemplateStatsService.watch$(this.dossierTemplateId),
});
}
async resetRules() {
if (!this.currentUser.isAdmin) return;
try {
await firstValueFrom(this._rulesService.reset(this.dossierTemplateId));
this._toaster.success(_('dossier-template-info-screen.rules-reset.success'));
await firstValueFrom(this._rulesService.getFor(this.dossierTemplateId));
} catch (error) {
this._toaster.rawError(error.error.message);
}
}
}

View File

@ -33,7 +33,7 @@
</mat-checkbox>
<div class="iqser-input-group datepicker-wrapper">
@if (hasValidFrom()) {
<ng-container *ngIf="hasValidFrom()">
<input
(dateChange)="applyValidityIntervalConstraints()"
[matDatepicker]="fromPicker"
@ -44,7 +44,7 @@
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
}
</ng-container>
</div>
</div>
@ -53,7 +53,7 @@
{{ 'add-edit-clone-dossier-template.form.valid-to' | translate }}
</mat-checkbox>
<div class="iqser-input-group datepicker-wrapper">
@if (hasValidTo()) {
<ng-container *ngIf="hasValidTo()">
<input
(dateChange)="applyValidityIntervalConstraints()"
[matDatepicker]="toPicker"
@ -64,79 +64,77 @@
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
}
</ng-container>
</div>
</div>
</div>
@if (!isDocumine) {
<div class="mt-24">
<div class="heading">
{{ 'add-edit-clone-dossier-template.form.apply-updates-default.heading' | translate }}
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="applyDictionaryUpdatesToAllDossiersByDefault">
{{ 'add-edit-clone-dossier-template.form.apply-updates-default.description' | translate }}
</mat-checkbox>
<div *ngIf="!isDocumine" class="mt-24">
<div class="heading">
{{ 'add-edit-clone-dossier-template.form.apply-updates-default.heading' | translate }}
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="applyDictionaryUpdatesToAllDossiersByDefault">
{{ 'add-edit-clone-dossier-template.form.apply-updates-default.description' | translate }}
</mat-checkbox>
</div>
</div>
<div class="mt-24">
<div class="heading mb-14">{{ 'download-includes' | translate }}</div>
<redaction-select
[label]="
'download-type.label'
| translate
: {
length: form.controls['downloadFileTypes'].value.length
}
"
[options]="downloadTypes"
formControlName="downloadFileTypes"
></redaction-select>
</div>
<div *ngIf="!isDocumine" class="mt-24">
<div class="heading">
{{ 'add-edit-clone-dossier-template.form.upload-settings.heading' | translate }}
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="ocrByDefault">
{{ 'add-edit-clone-dossier-template.form.upload-settings.ocr-by-default' | translate }}
</mat-checkbox>
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="removeWatermark">
{{ 'add-edit-clone-dossier-template.form.upload-settings.remove-watermark' | translate }}
</mat-checkbox>
</div>
</div>
<div *ngIf="!isDocumine" class="mt-24 hidden-elements">
<div class="heading">{{ 'add-edit-clone-dossier-template.form.hidden-text.heading' | translate }}</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="keepHiddenText">
{{ 'add-edit-clone-dossier-template.form.hidden-text.title' | translate }}
</mat-checkbox>
<div class="info">{{ 'add-edit-clone-dossier-template.form.hidden-text.description' | translate }}</div>
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="keepImageMetadata">
{{ 'add-edit-clone-dossier-template.form.image-metadata.title' | translate }}
</mat-checkbox>
<div class="info">{{ 'add-edit-clone-dossier-template.form.image-metadata.description' | translate }}</div>
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="keepOverlappingObjects">
{{ 'add-edit-clone-dossier-template.form.overlapping-elements.title' | translate }}
</mat-checkbox>
<div class="info">
{{ 'add-edit-clone-dossier-template.form.overlapping-elements.description' | translate }}
</div>
</div>
<div class="mt-24">
<div class="heading mb-14">{{ 'download-includes' | translate }}</div>
<redaction-select
[label]="
'download-type.label'
| translate
: {
length: form.controls['downloadFileTypes'].value.length,
}
"
[options]="downloadTypes"
formControlName="downloadFileTypes"
></redaction-select>
</div>
<div class="mt-24">
<div class="heading">
{{ 'add-edit-clone-dossier-template.form.upload-settings.heading' | translate }}
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="ocrByDefault">
{{ 'add-edit-clone-dossier-template.form.upload-settings.ocr-by-default' | translate }}
</mat-checkbox>
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="removeWatermark">
{{ 'add-edit-clone-dossier-template.form.upload-settings.remove-watermark' | translate }}
</mat-checkbox>
</div>
</div>
<div class="mt-24 hidden-elements">
<div class="heading">{{ 'add-edit-clone-dossier-template.form.hidden-text.heading' | translate }}</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="keepHiddenText">
{{ 'add-edit-clone-dossier-template.form.hidden-text.title' | translate }}
</mat-checkbox>
<div class="info">{{ 'add-edit-clone-dossier-template.form.hidden-text.description' | translate }}</div>
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="keepImageMetadata">
{{ 'add-edit-clone-dossier-template.form.image-metadata.title' | translate }}
</mat-checkbox>
<div class="info">{{ 'add-edit-clone-dossier-template.form.image-metadata.description' | translate }}</div>
</div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="keepOverlappingObjects">
{{ 'add-edit-clone-dossier-template.form.overlapping-elements.title' | translate }}
</mat-checkbox>
<div class="info">
{{ 'add-edit-clone-dossier-template.form.overlapping-elements.description' | translate }}
</div>
</div>
</div>
}
</div>
</div>
<div class="dialog-actions">

View File

@ -22,10 +22,10 @@ import { Observable } from 'rxjs';
import { DossierTemplateDetailsComponent } from '../dossier-template-details/dossier-template-details.component';
import { TranslateModule } from '@ngx-translate/core';
import { MatCheckbox } from '@angular/material/checkbox';
import { NgIf } from '@angular/common';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatIcon } from '@angular/material/icon';
import { SelectComponent } from '@shared/components/select/select.component';
import { MatSuffix } from '@angular/material/form-field';
const downloadTypes = ['ORIGINAL', 'PREVIEW', 'OPTIMIZED_PREVIEW', 'DELTA_PREVIEW', 'REDACTED'].map(type => ({
key: type,
@ -35,17 +35,18 @@ const downloadTypes = ['ORIGINAL', 'PREVIEW', 'OPTIMIZED_PREVIEW', 'DELTA_PREVIE
@Component({
templateUrl: './dossier-template-info-screen.component.html',
styleUrls: ['./dossier-template-info-screen.component.scss'],
standalone: true,
imports: [
HasScrollbarDirective,
ReactiveFormsModule,
DossierTemplateDetailsComponent,
TranslateModule,
MatCheckbox,
NgIf,
MatDatepickerModule,
SelectComponent,
IconButtonComponent,
MatIcon,
MatSuffix,
],
})
export default class DossierTemplateInfoScreenComponent extends BaseFormComponent implements OnInit {

View File

@ -17,17 +17,8 @@
type="text"
/>
</div>
<div class="iqser-input-group">
<label translate="add-edit-entity.form.technical-name"></label>
<div class="technical-name">{{ this.technicalName() || '-' }}</div>
<span
[translateParams]="{ type: data.justification ? 'edit' : 'create' }"
[translate]="'add-edit-entity.form.technical-name-hint'"
class="hint"
></span>
</div>
<div class="iqser-input-group w-400">
<div class="iqser-input-group required w-400">
<label translate="add-edit-justification.form.reason"></label>
<input
[placeholder]="'add-edit-justification.form.reason-placeholder' | translate"
@ -37,7 +28,7 @@
/>
</div>
<div class="iqser-input-group w-400">
<div class="iqser-input-group required w-400">
<label translate="add-edit-justification.form.description"></label>
<textarea
[placeholder]="'add-edit-justification.form.description-placeholder' | translate"

Some files were not shown because too many files have changed in this diff Show More