Merge branch 'master' into VM/RED-6240
This commit is contained in:
commit
31177e29cb
@ -6,6 +6,7 @@ import {
|
||||
IqserAuthGuard,
|
||||
IqserPermissionsGuard,
|
||||
IqserRoutes,
|
||||
TenantResolveComponent,
|
||||
} from '@iqser/common-ui';
|
||||
import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
||||
@ -96,11 +97,14 @@ const dossierTemplateIdRoutes: IqserRoutes = [
|
||||
const routes: IqserRoutes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'main',
|
||||
pathMatch: 'full',
|
||||
component: TenantResolveComponent,
|
||||
},
|
||||
{
|
||||
path: 'main',
|
||||
path: ':tenant',
|
||||
component: TenantResolveComponent,
|
||||
},
|
||||
{
|
||||
path: ':tenant/main',
|
||||
component: BaseScreenComponent,
|
||||
children: [
|
||||
{
|
||||
@ -212,7 +216,7 @@ const routes: IqserRoutes = [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'auth-error',
|
||||
path: ':tenant/auth-error',
|
||||
component: AuthErrorComponent,
|
||||
canActivate: [IqserAuthGuard],
|
||||
},
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
CommonUiModule,
|
||||
EmptyStateComponent,
|
||||
HiddenActionDirective,
|
||||
IconButtonComponent,
|
||||
InputWithActionComponent,
|
||||
IqserAllowDirective,
|
||||
IqserDenyDirective,
|
||||
@ -21,6 +22,7 @@ import {
|
||||
IqserPermissionsService,
|
||||
IqserTranslateModule,
|
||||
IqserUsersModule,
|
||||
KeycloakStatusService,
|
||||
LanguageService,
|
||||
LogoComponent,
|
||||
MAX_RETRIES_ON_SERVER_ERROR,
|
||||
@ -29,6 +31,8 @@ import {
|
||||
ServerErrorInterceptor,
|
||||
SkeletonComponent,
|
||||
StopPropagationDirective,
|
||||
TenantPipe,
|
||||
TenantsModule,
|
||||
ToastComponent,
|
||||
} from '@iqser/common-ui';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
@ -66,13 +70,11 @@ import { AppConfig, ILoggerConfig } from '@red/domain';
|
||||
import { SystemPreferencesService } from '@services/system-preferences.service';
|
||||
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { TenantIdInterceptor } from '@utils/tenant-id-interceptor';
|
||||
import { UI_CACHES } from '@utils/constants';
|
||||
import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { SkeletonTopBarComponent } from '@components/skeleton/skeleton-top-bar/skeleton-top-bar.component';
|
||||
import { DossierSkeletonComponent } from '@components/skeleton/dossier-skeleton/dossier-skeleton.component';
|
||||
import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skeleton-stats.component';
|
||||
import { TenantIdResponseInterceptor } from '@utils/tenant-id-response-interceptor';
|
||||
|
||||
const screens = [BaseScreenComponent, DownloadsListScreenComponent];
|
||||
|
||||
@ -119,6 +121,7 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
preventDuplicates: true,
|
||||
resetTimeoutOnDuplicate: true,
|
||||
}),
|
||||
TenantsModule.forRoot(),
|
||||
IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY || '/assets/i18n/redact/' }),
|
||||
IqserLoadingModule.forRoot(),
|
||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||
@ -170,6 +173,8 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
IqserAllowDirective,
|
||||
IqserDenyDirective,
|
||||
IqserListingModule,
|
||||
IconButtonComponent,
|
||||
TenantPipe,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@ -181,22 +186,13 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
provide: ErrorHandler,
|
||||
useClass: GlobalErrorHandler,
|
||||
},
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
multi: true,
|
||||
useClass: TenantIdInterceptor,
|
||||
},
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
multi: true,
|
||||
useClass: TenantIdResponseInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
useFactory: configurationInitializer,
|
||||
deps: [
|
||||
BASE_HREF,
|
||||
KeycloakStatusService,
|
||||
KeycloakService,
|
||||
ConfigService,
|
||||
SystemPreferencesService,
|
||||
|
||||
@ -38,11 +38,15 @@
|
||||
<mat-menu #userMenu="matMenu" xPosition="before">
|
||||
<div id="user-menu-items">
|
||||
<ng-container *ngFor="let item of userMenuItems; trackBy: trackByName">
|
||||
<a (click)="item.action?.()" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink" mat-menu-item>
|
||||
<a (click)="item.action?.()" *ngIf="item.show" [id]="item.id" [routerLink]="item.routerLink | tenant" mat-menu-item>
|
||||
{{ item.name | translate }}
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<button (click)="selectTenant()" *ngIf="tenantContext.hasMultipleTenants" id="select-tenant" mat-menu-item>
|
||||
<span translate="top-bar.navigation-items.my-account.children.select-tenant"> </span>
|
||||
</button>
|
||||
|
||||
<button (click)="userService.logout()" id="logout" mat-menu-item>
|
||||
<mat-icon svgIcon="iqser:logout"></mat-icon>
|
||||
<span translate="top-bar.navigation-items.my-account.children.logout"> </span>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
@ -7,7 +7,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { filter, map, startWith } from 'rxjs/operators';
|
||||
import { IqserPermissionsService, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { BASE_HREF, IqserPermissionsService, shareDistinctLast, TenantContext, TenantContextHolder } from '@iqser/common-ui';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
|
||||
@ -31,6 +31,8 @@ const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
|
||||
})
|
||||
export class BaseScreenComponent {
|
||||
readonly roles = ROLES;
|
||||
private readonly _baseHref = inject(BASE_HREF);
|
||||
readonly tenantContext = inject(TenantContext);
|
||||
readonly currentUser = this.userService.currentUser;
|
||||
readonly userMenuItems: readonly MenuItem[] = [
|
||||
{
|
||||
@ -89,6 +91,7 @@ export class BaseScreenComponent {
|
||||
private readonly _router: Router,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _featuresService: FeaturesService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
readonly permissionsService: IqserPermissionsService,
|
||||
readonly userService: UserService,
|
||||
readonly userPreferenceService: UserPreferenceService,
|
||||
@ -112,7 +115,7 @@ export class BaseScreenComponent {
|
||||
|
||||
private _search(query: string, dossierIds: string[], onlyActive = false) {
|
||||
const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive };
|
||||
this._router.navigate(['main/search'], { queryParams }).then();
|
||||
this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/search`], { queryParams }).then();
|
||||
}
|
||||
|
||||
private _searchThisDossier(query: string) {
|
||||
@ -123,4 +126,8 @@ export class BaseScreenComponent {
|
||||
const dossierId = routerLink[2];
|
||||
return this._search(query, [dossierId]);
|
||||
}
|
||||
|
||||
selectTenant() {
|
||||
window.open(window.location.origin + this._baseHref, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
[id]="first ? 'navigateToActiveDossiers' : ''"
|
||||
[matTooltip]="breadcrumb.options.clamp && (breadcrumb.name$ | async)"
|
||||
[routerLinkActiveOptions]="breadcrumb.options.routerLinkActiveOptions || { exact: false }"
|
||||
[routerLink]="breadcrumb.options.routerLink"
|
||||
[routerLink]="breadcrumb.options.routerLink | tenant"
|
||||
class="breadcrumb"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
@ -30,7 +30,7 @@
|
||||
<mat-menu #dropdownMenu="matMenu" class="padding-bottom-8">
|
||||
<a
|
||||
*ngFor="let option of breadcrumb.options.options"
|
||||
[routerLink]="option.options.routerLink"
|
||||
[routerLink]="option.options.routerLink | tenant"
|
||||
mat-menu-item
|
||||
routerLinkActive="active"
|
||||
>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<iqser-circle-button
|
||||
[matMenuTriggerFor]="menu"
|
||||
[showDot]="hasUnreadNotifications$ | async"
|
||||
[tooltip]="'notifications.button-text' | translate"
|
||||
[tooltipPosition]="'below'"
|
||||
buttonId="notification-button"
|
||||
icon="red:notification"
|
||||
></iqser-circle-button>
|
||||
|
||||
@ -7,11 +7,13 @@ import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierFilesGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _injector: Injector,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _filesMapService: FilesMapService,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
@ -32,7 +34,7 @@ export class DossierFilesGuard implements CanActivate {
|
||||
}
|
||||
|
||||
if (!dossiersService.has(dossierId)) {
|
||||
await this._router.navigate(['/main', dossierTemplateId]);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main`, dossierTemplateId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -3,11 +3,13 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossierTemplateExistsGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _dashboardStatsService: DashboardStatsService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
@ -19,13 +21,13 @@ export class DossierTemplateExistsGuard implements CanActivate {
|
||||
if (dossiersListView) {
|
||||
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
|
||||
if (!dossierTemplateStats || dossierTemplateStats.isEmpty) {
|
||||
await this._router.navigate(['']);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main`]);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
||||
if (!dossierTemplate) {
|
||||
await this._router.navigate(['main', 'admin', 'dossier-templates']);
|
||||
await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', 'admin', 'dossier-templates']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,12 +7,14 @@ import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.se
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossiersGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly _injector: Injector,
|
||||
private readonly _router: Router,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _dashboardStatsService: DashboardStatsService,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
@ -33,7 +35,7 @@ export class DossiersGuard implements CanActivate {
|
||||
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
|
||||
|
||||
if (isArchive && dossierTemplateStats?.numberOfArchivedDossiers === 0) {
|
||||
await this._router.navigate(['main', dossierTemplateId, 'dossiers']);
|
||||
await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', dossierTemplateId, 'dossiers']);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class EntityExistsGuard implements CanActivate {
|
||||
@ -10,6 +11,7 @@ export class EntityExistsGuard implements CanActivate {
|
||||
private readonly _dictionariesMapService: DictionariesMapService,
|
||||
private readonly _router: Router,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
@ -18,7 +20,7 @@ export class EntityExistsGuard implements CanActivate {
|
||||
|
||||
if (!this._dictionariesMapService.get(dossierTemplateId, type)) {
|
||||
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
|
||||
await this._router.navigate([`${dossierTemplate.routerLink}/entities`]);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/${dossierTemplate.routerLink}/entities`]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -2,10 +2,15 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
|
||||
import { WatermarksMapService } from '@services/entity-services/watermarks-map.service';
|
||||
import { DOSSIER_TEMPLATE_ID, WATERMARK_ID } from '@red/domain';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class WatermarkExistsGuard implements CanActivate {
|
||||
constructor(private readonly _watermarksMapService: WatermarksMapService, private readonly _router: Router) {}
|
||||
constructor(
|
||||
private readonly _watermarksMapService: WatermarksMapService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _router: Router,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const dossierTemplateId = route.parent.paramMap.get(DOSSIER_TEMPLATE_ID);
|
||||
@ -15,7 +20,14 @@ export class WatermarkExistsGuard implements CanActivate {
|
||||
return true;
|
||||
}
|
||||
|
||||
await this._router.navigate(['main', 'admin', 'dossier-templates', dossierTemplateId, 'watermarks']);
|
||||
await this._router.navigate([
|
||||
this._tenantContextHolder.currentTenant,
|
||||
'main',
|
||||
'admin',
|
||||
'dossier-templates',
|
||||
dossierTemplateId,
|
||||
'watermarks',
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
5
apps/red-ui/src/app/models/file/list-item.ts
Normal file
5
apps/red-ui/src/app/models/file/list-item.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface ListItem<T> {
|
||||
item: T;
|
||||
isSelected: boolean;
|
||||
multiSelectActive: boolean;
|
||||
}
|
||||
@ -51,6 +51,7 @@ import {
|
||||
IqserUploadFileModule,
|
||||
IqserUsersModule,
|
||||
RoundCheckboxComponent,
|
||||
TenantPipe,
|
||||
} from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AuditInfoDialogComponent } from './dialogs/audit-info-dialog/audit-info-dialog.component';
|
||||
@ -121,6 +122,7 @@ const components = [
|
||||
DetailsRadioComponent,
|
||||
IqserAllowDirective,
|
||||
IqserDenyDirective,
|
||||
TenantPipe,
|
||||
],
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@ -4,7 +4,7 @@ import { Router } from '@angular/router';
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { AdminDialogService } from '../services/admin-dialog.service';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { getParam, LoadingService } from '@iqser/common-ui';
|
||||
import { getParam, LoadingService, TenantContextHolder } from '@iqser/common-ui';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
@ -27,6 +27,7 @@ export class BaseEntityScreenComponent {
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
) {
|
||||
const entity$ = dictionaryMapService.watch$(this.#dossierTemplateId, this.#entityType);
|
||||
@ -41,7 +42,7 @@ export class BaseEntityScreenComponent {
|
||||
this._loadingService.start();
|
||||
const dossierTemplate = this._dossierTemplatesService.find(this.#dossierTemplateId);
|
||||
await firstValueFrom(this._dictionaryService.deleteDictionaries([this.#entityType], this.#dossierTemplateId));
|
||||
await this._router.navigate([`${dossierTemplate.routerLink}/entities`]);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/${dossierTemplate.routerLink}/entities`]);
|
||||
this._loadingService.stop();
|
||||
});
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@
|
||||
|
||||
<iqser-circle-button
|
||||
*ngIf="permissionsService.canEditEntities()"
|
||||
[routerLink]="dict.routerLink"
|
||||
[routerLink]="dict.routerLink | tenant"
|
||||
[tooltip]="'entities-listing.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
IconButtonTypes,
|
||||
IqserPermissionsService,
|
||||
LoadingService,
|
||||
TenantContextHolder,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { DOSSIER_TEMPLATE_ID, type IWatermark, type User, WATERMARK_ID, WatermarkOrientation, WatermarkOrientations } from '@red/domain';
|
||||
@ -75,6 +76,7 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
readonly permissionsService: IqserPermissionsService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _licenseService: LicenseService,
|
||||
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
|
||||
private readonly _watermarkService: WatermarkService,
|
||||
@ -146,7 +148,11 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
watermark.id ? _('watermark-screen.action.change-success') : _('watermark-screen.action.created-success'),
|
||||
);
|
||||
if (!watermark.id) {
|
||||
await this._router.navigate([`/main/admin/dossier-templates/${this.#dossierTemplateId}/watermarks/${updatedWatermark.id}`]);
|
||||
await this._router.navigate([
|
||||
`/${this._tenantContextHolder.currentTenant}/main/admin/dossier-templates/${this.#dossierTemplateId}/watermarks/${
|
||||
updatedWatermark.id
|
||||
}`,
|
||||
]);
|
||||
}
|
||||
} catch (error) {
|
||||
this._toaster.error(_('watermark-screen.action.error'));
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
IqserListingModule,
|
||||
IqserRoutes,
|
||||
IqserUsersModule,
|
||||
TenantPipe,
|
||||
} from '@iqser/common-ui';
|
||||
import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { WATERMARK_ID } from '@red/domain';
|
||||
@ -71,6 +72,7 @@ const routes: IqserRoutes = [
|
||||
CircleButtonComponent,
|
||||
HasScrollbarDirective,
|
||||
IqserAllowDirective,
|
||||
TenantPipe,
|
||||
],
|
||||
})
|
||||
export class WatermarkModule {}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
[iqserHelpMode]="'create_new_watermark'"
|
||||
[label]="'watermarks-listing.add-new' | translate"
|
||||
[overlappingElements]="['USER_MENU']"
|
||||
[routerLink]="getRouterLink()"
|
||||
[routerLink]="getRouterLink() | tenant"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:plus"
|
||||
></iqser-icon-button>
|
||||
@ -59,7 +59,7 @@
|
||||
<div class="action-buttons">
|
||||
<div [iqserHelpMode]="'edit_delete_watermark'" [overlappingElements]="['USER_MENU']">
|
||||
<iqser-circle-button
|
||||
[routerLink]="getRouterLink(entity)"
|
||||
[routerLink]="getRouterLink(entity) | tenant"
|
||||
[tooltip]="'watermarks-listing.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
[class.disabled]="isDisabled(item.screen)"
|
||||
[iqserHelpMode]="item.helpModeKey"
|
||||
[routerLinkActiveOptions]="{ exact: false }"
|
||||
[routerLink]="prefix + item.screen"
|
||||
[routerLink]="prefix + item.screen | tenant"
|
||||
class="item"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
|
||||
@ -5,7 +5,7 @@ import { adminSideNavTranslations } from '@translations/admin-side-nav-translati
|
||||
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
|
||||
import { AdminSideNavType, AdminSideNavTypes, ENTITY_TYPE, User } from '@red/domain';
|
||||
import { ROLES } from '@users/roles';
|
||||
import { getCurrentUser, IqserHelpModeModule, IqserPermissionsService, SideNavComponent } from '@iqser/common-ui';
|
||||
import { getCurrentUser, IqserHelpModeModule, IqserPermissionsService, SideNavComponent, TenantPipe } from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NgForOf, NgIf } from '@angular/common';
|
||||
|
||||
@ -21,7 +21,7 @@ interface NavItem {
|
||||
templateUrl: './admin-side-nav.component.html',
|
||||
styleUrls: ['./admin-side-nav.component.scss'],
|
||||
standalone: true,
|
||||
imports: [TranslateModule, NgIf, IqserHelpModeModule, RouterLink, RouterLinkActive, NgForOf, SideNavComponent],
|
||||
imports: [TranslateModule, NgIf, IqserHelpModeModule, RouterLink, RouterLinkActive, NgForOf, SideNavComponent, TenantPipe],
|
||||
})
|
||||
export class AdminSideNavComponent implements OnInit {
|
||||
@Input() type: AdminSideNavType;
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { CircleButtonComponent, CircleButtonTypes, getCurrentUser, IqserHelpModeModule, LoadingService } from '@iqser/common-ui';
|
||||
import {
|
||||
CircleButtonComponent,
|
||||
CircleButtonTypes,
|
||||
getCurrentUser,
|
||||
IqserHelpModeModule,
|
||||
LoadingService,
|
||||
TenantContextHolder,
|
||||
} from '@iqser/common-ui';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DOSSIER_TEMPLATE_ID, type User } from '@red/domain';
|
||||
@ -26,6 +33,7 @@ export class DossierTemplateActionsComponent implements OnInit {
|
||||
private readonly _route: ActivatedRoute,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
) {}
|
||||
|
||||
@ -43,7 +51,7 @@ export class DossierTemplateActionsComponent implements OnInit {
|
||||
|
||||
const success = await firstValueFrom(this._dossierTemplatesService.delete([this.dossierTemplateId]));
|
||||
if (success) {
|
||||
await this._router.navigate(['main', 'admin']);
|
||||
await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', 'admin']);
|
||||
}
|
||||
|
||||
this._loadingService.stop();
|
||||
|
||||
@ -2,25 +2,29 @@
|
||||
<ng-container *ngIf="dossierTemplate$ | async as dossierTemplate">
|
||||
<a
|
||||
*ngIf="root || dossierTemplate"
|
||||
[routerLink]="'/main/admin/dossier-templates'"
|
||||
[routerLink]="'/main/admin/dossier-templates' | tenant"
|
||||
class="breadcrumb"
|
||||
translate="dossier-templates.label"
|
||||
></a>
|
||||
|
||||
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
|
||||
|
||||
<a [class.active]="(activeDictionary$ | async) === undefined" [routerLink]="dossierTemplate.routerLink" class="breadcrumb ml-0">
|
||||
<a
|
||||
[class.active]="(activeDictionary$ | async) === undefined"
|
||||
[routerLink]="dossierTemplate.routerLink | tenant"
|
||||
class="breadcrumb ml-0"
|
||||
>
|
||||
{{ dossierTemplate.name }}
|
||||
</a>
|
||||
|
||||
<ng-container *ngIf="activeDictionary$ | async as activeDictionary">
|
||||
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
|
||||
<a [routerLink]="dossierTemplate.routerLink + '/entities'" class="breadcrumb ml-0">
|
||||
<a [routerLink]="dossierTemplate.routerLink + '/entities' | tenant" class="breadcrumb ml-0">
|
||||
{{ 'admin-side-nav.entities' | translate }}
|
||||
</a>
|
||||
|
||||
<mat-icon svgIcon="iqser:arrow-right"></mat-icon>
|
||||
<a [routerLink]="activeDictionary.routerLink" class="breadcrumb ml-0" routerLinkActive="active">
|
||||
<a [routerLink]="activeDictionary.routerLink | tenant" class="breadcrumb ml-0" routerLinkActive="active">
|
||||
{{ activeDictionary.label }}
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
@ -8,13 +8,14 @@ import { DictionariesMapService } from '@services/entity-services/dictionaries-m
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TenantPipe } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-template-breadcrumbs',
|
||||
templateUrl: './dossier-template-breadcrumbs.component.html',
|
||||
styleUrls: ['./dossier-template-breadcrumbs.component.scss'],
|
||||
standalone: true,
|
||||
imports: [NgIf, AsyncPipe, RouterLink, MatIconModule, TranslateModule, RouterLinkActive],
|
||||
imports: [NgIf, AsyncPipe, RouterLink, MatIconModule, TranslateModule, RouterLinkActive, TenantPipe],
|
||||
})
|
||||
export class DossierTemplateBreadcrumbsComponent {
|
||||
@Input() root = false;
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
[icon]="'iqser:edit'"
|
||||
[iqserHelpMode]="'dashboard_in_dossier'"
|
||||
[overlappingElements]="['USER_MENU']"
|
||||
[tooltipPosition]="'below'"
|
||||
[tooltip]="'dossier-details.edit-owner' | translate"
|
||||
class="ml-14"
|
||||
></iqser-circle-button>
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
[files]="entitiesService.all$ | async"
|
||||
[iqserHelpMode]="'edit_dossier_in_dossier'"
|
||||
[overlappingElements]="['USER_MENU']"
|
||||
[tooltipPosition]="'below'"
|
||||
></redaction-file-download-btn>
|
||||
|
||||
<iqser-circle-button
|
||||
@ -23,7 +22,6 @@
|
||||
[icon]="'iqser:csv'"
|
||||
[iqserHelpMode]="'edit_dossier_in_dossier'"
|
||||
[overlappingElements]="['USER_MENU']"
|
||||
[tooltipPosition]="'below'"
|
||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||
></iqser-circle-button>
|
||||
|
||||
@ -33,7 +31,6 @@
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[icon]="'iqser:refresh'"
|
||||
[tooltipClass]="'small warn'"
|
||||
[tooltipPosition]="'below'"
|
||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||
[type]="circleButtonTypes.warn"
|
||||
></iqser-circle-button>
|
||||
@ -45,7 +42,6 @@
|
||||
[icon]="'iqser:upload'"
|
||||
[iqserHelpMode]="'edit_dossier_in_dossier'"
|
||||
[overlappingElements]="['USER_MENU']"
|
||||
[tooltipPosition]="'below'"
|
||||
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-14"
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<span class="clamp-3"> {{ fileAttributeValue ? (fileAttributeValue | date : 'd MMM yyyy') : '-' }}</span>
|
||||
</ng-template>
|
||||
|
||||
<ng-container *ngIf="(fileAttributesService.isEditingFileAttribute$ | async) === false || isInEditMode">
|
||||
<ng-container *ngIf="((fileAttributesService.isEditingFileAttribute$ | async) === false || isInEditMode) && !file.isProcessing">
|
||||
<div *ngIf="!isInEditMode; else input" class="edit-button">
|
||||
<iqser-circle-button
|
||||
(action)="editFileAttribute()"
|
||||
|
||||
@ -16,7 +16,7 @@ import { filter, map, tap } from 'rxjs/operators';
|
||||
templateUrl: './file-attribute.component.html',
|
||||
styleUrls: ['./file-attribute.component.scss'],
|
||||
})
|
||||
export class FileAttributeComponent extends BaseFormComponent implements OnInit, OnDestroy {
|
||||
export class FileAttributeComponent extends BaseFormComponent implements OnDestroy {
|
||||
@Input() fileAttribute!: IFileAttributeConfig;
|
||||
@Input() file!: File;
|
||||
@Input() dossier!: Dossier;
|
||||
@ -53,23 +53,10 @@ export class FileAttributeComponent extends BaseFormComponent implements OnInit,
|
||||
return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
|
||||
}
|
||||
|
||||
get #noFileAttributes(): boolean {
|
||||
return JSON.stringify(this.file.fileAttributes.attributeIdToValue) === '{}';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.#subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.#noFileAttributes) {
|
||||
this.#initFileAttributes();
|
||||
}
|
||||
|
||||
this.form = this.#getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
async editFileAttribute(): Promise<void> {
|
||||
this.#toggleEdit();
|
||||
}
|
||||
@ -78,7 +65,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnInit,
|
||||
const rawFormValue = this.form.getRawValue();
|
||||
const fileAttrValue = rawFormValue[this.fileAttribute.id];
|
||||
const attributeIdToValue = {
|
||||
...rawFormValue,
|
||||
...this.#getForm().getRawValue(),
|
||||
[this.fileAttribute.id]: this.#formatAttributeValue(fileAttrValue),
|
||||
};
|
||||
try {
|
||||
@ -111,7 +98,11 @@ export class FileAttributeComponent extends BaseFormComponent implements OnInit,
|
||||
|
||||
#initFileAttributes() {
|
||||
const configs = this.fileAttributesService.getFileAttributeConfig(this.file.dossierTemplateId).fileAttributeConfigs;
|
||||
configs.forEach(config => (this.file.fileAttributes.attributeIdToValue[config.id] = null));
|
||||
configs.forEach(config => {
|
||||
if (!this.file.fileAttributes.attributeIdToValue[config.id]) {
|
||||
this.file.fileAttributes.attributeIdToValue[config.id] = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#getForm(): UntypedFormGroup {
|
||||
@ -129,6 +120,12 @@ export class FileAttributeComponent extends BaseFormComponent implements OnInit,
|
||||
}
|
||||
|
||||
#toggleEdit(): void {
|
||||
if (!this.isInEditMode) {
|
||||
this.#initFileAttributes();
|
||||
this.form = this.#getForm();
|
||||
this.initialFormValue = this.form.getRawValue();
|
||||
}
|
||||
|
||||
this.isInEditMode = !this.isInEditMode;
|
||||
this.fileAttributesService.isEditingFileAttribute$.next(this.isInEditMode);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div class="workflow-item">
|
||||
<div class="details-wrapper">
|
||||
<div class="details">
|
||||
<div [matTooltip]="file.filename" [routerLink]="file.routerLink" class="filename pointer" matTooltipPosition="above">
|
||||
<div [matTooltip]="file.filename" [routerLink]="file.routerLink | tenant" class="filename pointer" matTooltipPosition="above">
|
||||
{{ file.filename }}
|
||||
</div>
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
IqserUsersModule,
|
||||
StatusBarComponent,
|
||||
StopPropagationDirective,
|
||||
TenantPipe,
|
||||
} from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DossierOverviewScreenComponent } from './screen/dossier-overview-screen.component';
|
||||
@ -68,6 +69,7 @@ const routes: IqserRoutes = [
|
||||
HasScrollbarDirective,
|
||||
DynamicInputComponent,
|
||||
IqserAllowDirective,
|
||||
TenantPipe,
|
||||
],
|
||||
})
|
||||
export class DossierOverviewModule {}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<div
|
||||
*ngIf="(noSelection$ | async) && changesTooltip$ | async as changesTooltip"
|
||||
*ngIf="noSelection && changesTooltip"
|
||||
[matTooltip]="changesTooltip"
|
||||
class="chip"
|
||||
matTooltipClass="multiline"
|
||||
@ -8,7 +8,7 @@
|
||||
<mat-icon [svgIcon]="'red:redaction-changes'"></mat-icon>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="(noSelection$ | async) && engines$ | async as engines">
|
||||
<ng-container *ngIf="noSelection && engines">
|
||||
<div #trigger="cdkOverlayOrigin" (mouseout)="isPopoverOpen = false" (mouseover)="isPopoverOpen = true" cdkOverlayOrigin class="chip">
|
||||
<mat-icon *ngFor="let engine of engines" [svgIcon]="engine.icon"></mat-icon>
|
||||
</div>
|
||||
|
||||
@ -3,10 +3,9 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { annotationChangesTranslations } from '@translations/annotation-changes-translations';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { KeysOf, shareDistinctLast } from '@iqser/common-ui';
|
||||
import { BehaviorSubject, combineLatest, filter, map, Observable, switchMap } from 'rxjs';
|
||||
import { KeysOf } from '@iqser/common-ui';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
import { ListItem } from '@models/file/list-item';
|
||||
|
||||
interface Engine {
|
||||
readonly icon: string;
|
||||
@ -42,52 +41,31 @@ const changesProperties: KeysOf<AnnotationWrapper>[] = [
|
||||
styleUrls: ['./annotation-details.component.scss'],
|
||||
})
|
||||
export class AnnotationDetailsComponent implements OnChanges {
|
||||
@Input() annotation: AnnotationWrapper;
|
||||
readonly noSelection$: Observable<boolean>;
|
||||
@Input() annotation: ListItem<AnnotationWrapper>;
|
||||
isPopoverOpen = false;
|
||||
|
||||
readonly engines$: Observable<Engine[]>;
|
||||
readonly changesTooltip$: Observable<string>;
|
||||
readonly #annotationChanged$ = new BehaviorSubject<AnnotationWrapper>(undefined);
|
||||
engines: Engine[];
|
||||
changesTooltip: string;
|
||||
noSelection: boolean;
|
||||
|
||||
constructor(
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _listingService: AnnotationsListingService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
) {
|
||||
const isSelected$ = this.#annotationChanged$.pipe(switchMap(annotation => this._listingService.isSelected$(annotation)));
|
||||
this.noSelection$ = combineLatest([isSelected$, multiSelectService.inactive$]).pipe(
|
||||
map(([isSelected, inactive]) => !isSelected || inactive),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
this.engines$ = this.#engines$;
|
||||
this.changesTooltip$ = this.#changesTooltip;
|
||||
}
|
||||
constructor(private readonly _translateService: TranslateService, private readonly _listingService: AnnotationsListingService) {}
|
||||
|
||||
get #engines$(): Observable<Engine[]> {
|
||||
return this.#annotationChanged$.pipe(
|
||||
filter(annotation => !!annotation),
|
||||
map(annotation => this.#extractEngines(annotation).filter(engine => engine.show)),
|
||||
);
|
||||
}
|
||||
getChangesTooltip(): string | undefined {
|
||||
const changes = changesProperties.filter(key => this.annotation.item[key]);
|
||||
|
||||
get #changesTooltip(): Observable<string | undefined> {
|
||||
return this.#annotationChanged$.pipe(
|
||||
filter(annotation => !!annotation),
|
||||
map(annotation => changesProperties.filter(key => annotation[key])),
|
||||
map(changes => {
|
||||
if (!changes.length) {
|
||||
return;
|
||||
}
|
||||
const header = this._translateService.instant(_('annotation-changes.header'));
|
||||
const details = changes.map(change => this._translateService.instant(annotationChangesTranslations[change]));
|
||||
return [header, ...details.map(change => `• ${change}`)].join('\n');
|
||||
}),
|
||||
);
|
||||
if (!changes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const header = this._translateService.instant(_('annotation-changes.header'));
|
||||
const details = changes.map(change => this._translateService.instant(annotationChangesTranslations[change]));
|
||||
return [header, ...details.map(change => `• ${change}`)].join('\n');
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.#annotationChanged$.next(this.annotation);
|
||||
this.engines = this.#extractEngines(this.annotation.item).filter(engine => engine.show);
|
||||
this.changesTooltip = this.getChangesTooltip();
|
||||
this.noSelection = !this.annotation.isSelected || !this.annotation.multiSelectActive;
|
||||
}
|
||||
|
||||
#extractEngines(annotation: AnnotationWrapper): Engine[] {
|
||||
|
||||
@ -1,37 +1,37 @@
|
||||
<div class="active-bar-marker"></div>
|
||||
|
||||
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
|
||||
<div [class.removed]="annotation.item.isChangeLogRemoved" class="annotation">
|
||||
<redaction-annotation-card
|
||||
[annotation]="annotation"
|
||||
[isSelected]="isSelected$ | async"
|
||||
[matTooltip]="annotation.content"
|
||||
[annotation]="annotation.item"
|
||||
[isSelected]="annotation.isSelected"
|
||||
[matTooltip]="annotation.item.content"
|
||||
matTooltipPosition="above"
|
||||
></redaction-annotation-card>
|
||||
|
||||
<div *ngIf="!annotation.isEarmark" class="actions-wrapper">
|
||||
<div *ngIf="!annotation.item.isEarmark" class="actions-wrapper">
|
||||
<div
|
||||
(click)="comments.toggleExpandComments()"
|
||||
[matTooltip]="'comments.comments' | translate : { count: annotation.comments?.length }"
|
||||
[matTooltip]="'comments.comments' | translate : { count: annotation.item.comments?.length }"
|
||||
class="comments-counter"
|
||||
matTooltipPosition="above"
|
||||
stopPropagation
|
||||
>
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
{{ annotation.comments.length }}
|
||||
{{ annotation.item.comments.length }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="multiSelectService.inactive$ | async" class="actions">
|
||||
<div *ngIf="!annotation.multiSelectActive" class="actions">
|
||||
<redaction-annotation-actions
|
||||
[annotations]="[annotation]"
|
||||
[annotations]="[annotation.item]"
|
||||
[canPerformAnnotationActions]="pdfProxyService.canPerformAnnotationActions$ | async"
|
||||
[iqserHelpMode]="getActionsHelpModeKey(annotation)"
|
||||
[iqserHelpMode]="getActionsHelpModeKey(annotation.item)"
|
||||
[overlappingElements]="['USER_MENU', 'WORKLOAD_FILTER', 'DOCUMENT_INFO']"
|
||||
[scrollableParentView]="scrollableParentView"
|
||||
></redaction-annotation-actions>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<redaction-comments #comments [annotation]="annotation"></redaction-comments>
|
||||
<redaction-comments #comments [annotation]="annotation.item"></redaction-comments>
|
||||
</div>
|
||||
|
||||
<redaction-annotation-details [annotation]="annotation"></redaction-annotation-details>
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { Component, HostBinding, Input, OnChanges } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, switchMap, tap } from 'rxjs/operators';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { MultiSelectService } from '../../services/multi-select.service';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
import { PdfProxyService } from '../../services/pdf-proxy.service';
|
||||
import { ScrollableParentViews } from '@iqser/common-ui';
|
||||
import { ActionsHelpModeKeys } from '../../utils/constants';
|
||||
import { ListItem } from '@models/file/list-item';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotation-wrapper [annotation]',
|
||||
@ -14,29 +12,17 @@ import { ActionsHelpModeKeys } from '../../utils/constants';
|
||||
styleUrls: ['./annotation-wrapper.component.scss'],
|
||||
})
|
||||
export class AnnotationWrapperComponent implements OnChanges {
|
||||
@Input() annotation!: AnnotationWrapper;
|
||||
@Input() annotation!: ListItem<AnnotationWrapper>;
|
||||
|
||||
readonly isSelected$!: Observable<boolean>;
|
||||
@HostBinding('attr.annotation-id') annotationId: string;
|
||||
@HostBinding('class.active') active = false;
|
||||
readonly scrollableParentView = ScrollableParentViews.ANNOTATIONS_LIST;
|
||||
readonly #annotationChanged$ = new BehaviorSubject<AnnotationWrapper>(undefined);
|
||||
|
||||
constructor(
|
||||
readonly listingService: AnnotationsListingService,
|
||||
readonly multiSelectService: MultiSelectService,
|
||||
readonly pdfProxyService: PdfProxyService,
|
||||
) {
|
||||
this.isSelected$ = this.#annotationChanged$.pipe(
|
||||
switchMap(entity => this.listingService.isSelected$(entity)),
|
||||
distinctUntilChanged(),
|
||||
tap(isSelected => (this.active = isSelected)),
|
||||
);
|
||||
}
|
||||
constructor(readonly listingService: AnnotationsListingService, readonly pdfProxyService: PdfProxyService) {}
|
||||
|
||||
ngOnChanges() {
|
||||
this.#annotationChanged$.next(this.annotation);
|
||||
this.annotationId = this.annotation.id;
|
||||
this.annotationId = this.annotation.item.id;
|
||||
this.active = this.annotation.isSelected;
|
||||
}
|
||||
|
||||
getActionsHelpModeKey(annotation: AnnotationWrapper): string {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
<ng-container *ngFor="let annotation of annotations; let idx = index">
|
||||
<ng-container *ngFor="let annotation of annotations; let idx = index; trackBy: _trackBy">
|
||||
<div *ngIf="showHighlightGroup(idx) as highlightGroup" class="workload-separator">
|
||||
<redaction-highlights-separator [annotation]="annotation" [highlightGroup]="highlightGroup"></redaction-highlights-separator>
|
||||
<redaction-highlights-separator [annotation]="annotation.item" [highlightGroup]="highlightGroup"></redaction-highlights-separator>
|
||||
</div>
|
||||
|
||||
<redaction-annotation-wrapper (click)="annotationClicked(annotation, $event)" [annotation]="annotation"></redaction-annotation-wrapper>
|
||||
<redaction-annotation-wrapper
|
||||
(click)="annotationClicked(annotation.item, $event)"
|
||||
[annotation]="annotation"
|
||||
></redaction-annotation-wrapper>
|
||||
</ng-container>
|
||||
|
||||
<redaction-annotation-references-list
|
||||
|
||||
@ -9,6 +9,7 @@ import { BehaviorSubject } from 'rxjs';
|
||||
import { EarmarkGroup } from '@red/domain';
|
||||
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
import { ListItem } from '@models/file/list-item';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotations-list',
|
||||
@ -16,11 +17,12 @@ import { AnnotationsListingService } from '../../services/annotations-listing.se
|
||||
styleUrls: ['./annotations-list.component.scss'],
|
||||
})
|
||||
export class AnnotationsListComponent extends HasScrollbarDirective implements OnChanges {
|
||||
@Input() annotations: AnnotationWrapper[];
|
||||
@Input() annotations: ListItem<AnnotationWrapper>[];
|
||||
|
||||
@Output() readonly pagesPanelActive = new EventEmitter<boolean>();
|
||||
|
||||
readonly earmarkGroups$ = new BehaviorSubject<EarmarkGroup[]>([]);
|
||||
protected readonly _trackBy = (index: number, listItem: ListItem<AnnotationWrapper>) => listItem.item.id;
|
||||
|
||||
constructor(
|
||||
protected readonly _elementRef: ElementRef,
|
||||
@ -87,11 +89,11 @@ export class AnnotationsListComponent extends HasScrollbarDirective implements O
|
||||
const earmarksGroups: EarmarkGroup[] = [];
|
||||
let lastGroup: EarmarkGroup;
|
||||
for (let idx = 0; idx < this.annotations.length; ++idx) {
|
||||
if (idx === 0 || this.annotations[idx].color !== this.annotations[idx - 1].color) {
|
||||
if (idx === 0 || this.annotations[idx].item.color !== this.annotations[idx - 1].item.color) {
|
||||
if (lastGroup) {
|
||||
earmarksGroups.push(lastGroup);
|
||||
}
|
||||
lastGroup = { startIdx: idx, length: 1, color: this.annotations[idx].color };
|
||||
lastGroup = { startIdx: idx, length: 1, color: this.annotations[idx].item.color };
|
||||
} else {
|
||||
lastGroup.length += 1;
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-ma
|
||||
import { AnnotationsListingService } from '../../services/annotations-listing.service';
|
||||
import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service';
|
||||
import { SuggestionsService } from '../../services/suggestions.service';
|
||||
import { ListItem } from '@models/file/list-item';
|
||||
|
||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||
@ -49,7 +50,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
|
||||
@Input() file!: File;
|
||||
displayedPages: number[] = [];
|
||||
pagesPanelActive = true;
|
||||
readonly displayedAnnotations$: Observable<Map<number, AnnotationWrapper[]>>;
|
||||
readonly displayedAnnotations$: Observable<Map<number, ListItem<AnnotationWrapper>[]>>;
|
||||
readonly multiSelectInactive$: Observable<boolean>;
|
||||
readonly showExcludedPages$: Observable<boolean>;
|
||||
readonly title$: Observable<string>;
|
||||
@ -95,8 +96,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
|
||||
this.handleKeyEvent($event);
|
||||
});
|
||||
|
||||
this.displayedAnnotations$ = this._displayedAnnotations$;
|
||||
this.multiSelectInactive$ = this._multiSelectInactive$;
|
||||
this.displayedAnnotations$ = this._displayedAnnotations$;
|
||||
this.showExcludedPages$ = this._showExcludedPages$;
|
||||
this.isEarmarks$ = this._isEarmarks$;
|
||||
this.title$ = this._title$;
|
||||
@ -149,13 +150,21 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
|
||||
return this.listingService.selected.length ? this.listingService.selected[0] : null;
|
||||
}
|
||||
|
||||
private get _displayedAnnotations$(): Observable<Map<number, AnnotationWrapper[]>> {
|
||||
private get _displayedAnnotations$(): Observable<Map<number, ListItem<AnnotationWrapper>[]>> {
|
||||
const primary$ = this.filterService.getFilterModels$('primaryFilters');
|
||||
const secondary$ = this.filterService.getFilterModels$('secondaryFilters');
|
||||
|
||||
return combineLatest([this.fileDataService.all$, primary$, secondary$]).pipe(
|
||||
return combineLatest([
|
||||
this.fileDataService.all$,
|
||||
primary$,
|
||||
secondary$,
|
||||
this.listingService.selected$,
|
||||
this.multiSelectService.active$,
|
||||
]).pipe(
|
||||
delay(0),
|
||||
map(([annotations, primary, secondary]) => this._filterAnnotations(annotations, primary, secondary)),
|
||||
map(annotations => this._mapListItemsFromAnnotationWrapperArray(annotations)),
|
||||
tap(() => setTimeout(() => this._changeDetectorRef.detectChanges())),
|
||||
);
|
||||
}
|
||||
|
||||
@ -457,4 +466,20 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
|
||||
FileWorkloadComponent._scrollToFirstElement(elements);
|
||||
}
|
||||
}
|
||||
|
||||
private _mapListItemsFromAnnotationWrapperArray(annotations: Map<number, AnnotationWrapper[]>) {
|
||||
const listItemsMap = new Map<number, ListItem<AnnotationWrapper>[]>();
|
||||
if (!annotations) {
|
||||
return listItemsMap;
|
||||
}
|
||||
[...annotations.keys()].forEach(key => {
|
||||
const newValue = annotations.get(key).map(annotation => ({
|
||||
item: annotation,
|
||||
isSelected: this.listingService.isSelected(annotation),
|
||||
multiSelectActive: this.multiSelectService.isActive,
|
||||
}));
|
||||
listItemsMap.set(key, newValue);
|
||||
});
|
||||
return listItemsMap;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
<iqser-initials-avatar
|
||||
*ngIf="!editingReviewer"
|
||||
[id]="'assignee'"
|
||||
[tooltipPosition]="'below'"
|
||||
[user]="file.assignee"
|
||||
[withName]="!!file.assignee"
|
||||
></iqser-initials-avatar>
|
||||
@ -27,7 +26,7 @@
|
||||
(save)="assignReviewer(file, $event)"
|
||||
*ngIf="editingReviewer"
|
||||
[options]="usersOptions$ | async"
|
||||
[value]="file.assignee === null ? undefined : file.assignee"
|
||||
[value]="file.assignee"
|
||||
></redaction-assign-user-dropdown>
|
||||
|
||||
<div *ngIf="!editingReviewer && canAssign$ | async" class="assign-actions-wrapper">
|
||||
@ -38,7 +37,6 @@
|
||||
[icon]="'iqser:edit'"
|
||||
[iqserHelpMode]="'document_features_in_editor'"
|
||||
[overlappingElements]="['USER_MENU']"
|
||||
[tooltipPosition]="'below'"
|
||||
[tooltip]="assignTooltip$ | async"
|
||||
></iqser-circle-button>
|
||||
|
||||
@ -46,7 +44,6 @@
|
||||
(action)="fileAssignService.assignToMe([file])"
|
||||
*ngIf="canAssignToSelf$ | async"
|
||||
[icon]="'red:assign-me'"
|
||||
[tooltipPosition]="'below'"
|
||||
[tooltip]="'file-preview.assign-me' | translate"
|
||||
buttonId="assign-me"
|
||||
></iqser-circle-button>
|
||||
|
||||
@ -91,7 +91,6 @@ export class UserManagementComponent {
|
||||
this.usersOptions$ = combineLatest([this._canUnassignUser$, this.stateService.file$, this._dossier$]).pipe(
|
||||
map(([canUnassignUser, file, dossier]) => {
|
||||
const unassignUser = canUnassignUser && file.assignee ? [undefined] : [];
|
||||
console.log(unassignUser);
|
||||
return file.isUnderApproval
|
||||
? this.#customSort([...dossier.approverIds, ...unassignUser])
|
||||
: this.#customSort([...dossier.memberIds, ...unassignUser]);
|
||||
|
||||
@ -4,12 +4,13 @@
|
||||
[class.active]="viewMode === viewModes.STANDARD"
|
||||
[iqserHelpMode]="'views'"
|
||||
[matTooltip]="'file-preview.standard-tooltip' | translate"
|
||||
[matTooltipPosition]="'above'"
|
||||
class="red-tab"
|
||||
>
|
||||
{{ 'file-preview.standard' | translate }}
|
||||
</button>
|
||||
|
||||
<div [matTooltip]="'file-preview.delta-tooltip' | translate">
|
||||
<div [matTooltip]="'file-preview.delta-tooltip' | translate" [matTooltipPosition]="'above'">
|
||||
<button
|
||||
(click)="switchView(viewModes.DELTA)"
|
||||
*deny="roles.getRss"
|
||||
@ -22,7 +23,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div [matTooltip]="'file-preview.redacted-tooltip' | translate">
|
||||
<div [matTooltip]="'file-preview.redacted-tooltip' | translate" [matTooltipPosition]="'above'">
|
||||
<button
|
||||
(click)="switchView(viewModes.REDACTED)"
|
||||
*deny="roles.getRss"
|
||||
@ -35,7 +36,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div [matTooltip]="'file-preview.text-highlights-tooltip' | translate">
|
||||
<div [matTooltip]="'file-preview.text-highlights-tooltip' | translate" [matTooltipPosition]="'above'">
|
||||
<button
|
||||
(click)="switchView(viewModes.TEXT_HIGHLIGHTS)"
|
||||
*deny="roles.getRss"
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
<redaction-view-switch></redaction-view-switch>
|
||||
</div>
|
||||
|
||||
<!-- TODO: mode this file preview header to a separate component-->
|
||||
<div #actionsWrapper class="flex-2 actions-container">
|
||||
<redaction-processing-indicator [file]="file" class="mr-16"></redaction-processing-indicator>
|
||||
|
||||
@ -25,6 +26,7 @@
|
||||
|
||||
<div class="vertical-line"></div>
|
||||
|
||||
<!-- TODO: mode these actions to a separate component -->
|
||||
<iqser-circle-button
|
||||
(action)="openRSSView(file)"
|
||||
*allow="roles.getRss"
|
||||
@ -36,9 +38,9 @@
|
||||
|
||||
<redaction-file-actions
|
||||
[dossier]="dossier"
|
||||
[fileActionsHelpModeKey]="'editor_document_features'"
|
||||
[file]="file"
|
||||
[minWidth]="width"
|
||||
[fileActionsHelpModeKey]="'editor_document_features'"
|
||||
type="file-preview"
|
||||
></redaction-file-actions>
|
||||
|
||||
@ -47,7 +49,6 @@
|
||||
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
|
||||
[tooltip]="'file-preview.fullscreen' | translate"
|
||||
class="ml-2"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
|
||||
<!-- Dev Mode Features-->
|
||||
@ -58,19 +59,16 @@
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-8"
|
||||
icon="iqser:download"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
|
||||
<!-- End Dev Mode Features-->
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="closeFullScreen()"
|
||||
*ngIf="!fullScreen"
|
||||
[routerLink]="dossier.routerLink"
|
||||
[routerLink]="dossier.routerLink | tenant"
|
||||
[tooltip]="'common.close' | translate"
|
||||
class="ml-8"
|
||||
icon="iqser:close"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -31,6 +31,7 @@ import {
|
||||
OnDetach,
|
||||
processFilters,
|
||||
shareDistinctLast,
|
||||
TenantContextHolder,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
@ -125,6 +126,7 @@ export class FilePreviewScreenComponent
|
||||
private readonly _viewModeService: ViewModeService,
|
||||
private readonly _documentViewer: REDDocumentViewer,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _dialogService: FilePreviewDialogService,
|
||||
private readonly _pageRotationService: PageRotationService,
|
||||
private readonly _viewerHeaderService: ViewerHeaderService,
|
||||
@ -772,7 +774,7 @@ export class FilePreviewScreenComponent
|
||||
|
||||
private _navigateToDossier() {
|
||||
this._logger.info('Navigating to ', this.state.dossier.dossierName);
|
||||
return this._router.navigate([this.state.dossier.routerLink]);
|
||||
return this._router.navigate([`/${this._tenantContextHolder.currentTenant}${this.state.dossier.routerLink}`]);
|
||||
}
|
||||
|
||||
#highlightSelectedAnnotations(newAnnotations: AnnotationWrapper[]) {
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
RoundCheckboxComponent,
|
||||
StatusBarComponent,
|
||||
StopPropagationDirective,
|
||||
TenantPipe,
|
||||
} from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
@ -139,6 +140,7 @@ const components = [
|
||||
RoundCheckboxComponent,
|
||||
IqserAllowDirective,
|
||||
IqserDenyDirective,
|
||||
TenantPipe,
|
||||
],
|
||||
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, SuggestionsService],
|
||||
})
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
<div class="cell small-label full-opacity stats-subtitle">
|
||||
<div>
|
||||
<mat-icon *ngIf="item.archived" svgIcon="red:archive"></mat-icon>
|
||||
<a [routerLink]="routerLink" stopPropagation> {{ item.dossierName }}</a>
|
||||
<a [routerLink]="routerLink | tenant" stopPropagation> {{ item.dossierName }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SearchScreenComponent } from './search-screen/search-screen.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { IqserListingModule, IqserUsersModule, StatusBarComponent, StopPropagationDirective } from '@iqser/common-ui';
|
||||
import { IqserListingModule, IqserUsersModule, StatusBarComponent, StopPropagationDirective, TenantPipe } from '@iqser/common-ui';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchItemTemplateComponent } from './search-item-template/search-item-template.component';
|
||||
@ -20,6 +20,7 @@ const routes = [{ path: '', component: SearchScreenComponent }];
|
||||
IqserListingModule,
|
||||
StatusBarComponent,
|
||||
StopPropagationDirective,
|
||||
TenantPipe,
|
||||
],
|
||||
})
|
||||
export class SearchModule {}
|
||||
|
||||
@ -8,11 +8,12 @@ import {
|
||||
getCurrentUser,
|
||||
IConfirmationDialogData,
|
||||
IqserPermissionsService,
|
||||
IqserTooltipPosition,
|
||||
IqserTooltipPositions,
|
||||
LoadingService,
|
||||
OverlappingElements,
|
||||
ScrollableParentView,
|
||||
ScrollableParentViews,
|
||||
TenantContextHolder,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
@ -76,7 +77,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
isDossierOverviewList = false;
|
||||
isDossierOverviewWorkflow = false;
|
||||
isFilePreview = false;
|
||||
tooltipPosition: IqserTooltipPosition;
|
||||
tooltipPosition = IqserTooltipPositions.above;
|
||||
buttons: Action[];
|
||||
|
||||
scrollableParentView: ScrollableParentView;
|
||||
@ -90,6 +91,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _fileAssignService: FileAssignService,
|
||||
private readonly _reanalysisService: ReanalysisService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
@ -343,7 +345,7 @@ export class FileActionsComponent implements OnChanges {
|
||||
try {
|
||||
const dossier = this._activeDossiersService.find(this.file.dossierId);
|
||||
await firstValueFrom(this._fileManagementService.delete([this.file], this.file.dossierId));
|
||||
await this._injector.get(Router).navigate([dossier.routerLink]);
|
||||
await this._injector.get(Router).navigate([`/${this._tenantContextHolder.currentTenant}${dossier.routerLink}`]);
|
||||
} catch (error) {
|
||||
this._injector.get(Toaster).error(_('error.http.generic'), { params: error });
|
||||
}
|
||||
@ -416,8 +418,6 @@ export class FileActionsComponent implements OnChanges {
|
||||
this.isDossierOverview = this.type.startsWith('dossier-overview');
|
||||
this.isFilePreview = this.type === 'file-preview';
|
||||
|
||||
this.tooltipPosition = this.isFilePreview ? 'below' : 'above';
|
||||
|
||||
this.assignTooltip = this.file.isUnderApproval ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer');
|
||||
this.buttonType = this.isFilePreview ? CircleButtonTypes.default : CircleButtonTypes.dark;
|
||||
this.showAssign =
|
||||
|
||||
@ -7,7 +7,15 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { EditDossierDialogComponent } from '../edit-dossier-dialog.component';
|
||||
import { ConfirmOptions, IconButtonTypes, IConfirmationDialogData, LoadingService, TitleColors, Toaster } from '@iqser/common-ui';
|
||||
import {
|
||||
ConfirmOptions,
|
||||
IconButtonTypes,
|
||||
IConfirmationDialogData,
|
||||
LoadingService,
|
||||
TenantContextHolder,
|
||||
TitleColors,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
|
||||
@ -47,6 +55,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
private readonly _formBuilder: UntypedFormBuilder,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _router: Router,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _editDossierDialogRef: MatDialogRef<EditDossierDialogComponent>,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _loadingService: LoadingService,
|
||||
@ -144,7 +153,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._trashService.deleteDossier(this.dossier));
|
||||
this._editDossierDialogRef.close();
|
||||
await this._router.navigate([this.dossier.dossiersListRouterLink]);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}${this.dossier.dossiersListRouterLink}`]);
|
||||
this._loadingService.stop();
|
||||
this._toaster.success(_('edit-dossier-dialog.delete-successful'), {
|
||||
params: {
|
||||
|
||||
@ -3,7 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DOSSIER_TEMPLATE_ID, DownloadFileType, IDossierRequest, IDossierTemplate, IReportTemplate } from '@red/domain';
|
||||
import { UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { downloadTypesTranslations } from '@translations/download-types-translations';
|
||||
import { BaseDialogComponent, IconButtonTypes, IqserPermissionsService, SaveOptions } from '@iqser/common-ui';
|
||||
import { BaseDialogComponent, IconButtonTypes, IqserPermissionsService, SaveOptions, TenantContextHolder } from '@iqser/common-ui';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
|
||||
import { ReportTemplateService } from '@services/report-template.service';
|
||||
@ -42,6 +42,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent implements On
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: IqserPermissionsService,
|
||||
private readonly _tenantContextHolder: TenantContextHolder,
|
||||
private readonly _activeDossiersService: ActiveDossiersService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _reportTemplateController: ReportTemplateService,
|
||||
@ -82,7 +83,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent implements On
|
||||
this._loadingService.start();
|
||||
const savedDossier = await firstValueFrom(this._activeDossiersService.createOrUpdate(this._formToObject()));
|
||||
if (savedDossier) {
|
||||
await this._router.navigate([savedDossier.routerLink]);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}${savedDossier.routerLink}`]);
|
||||
if (options?.addMembers) {
|
||||
this._dialogService.openDialog('editDossier', {
|
||||
dossierId: savedDossier.id,
|
||||
|
||||
@ -20,7 +20,11 @@
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<a *ngIf="item.isFile && fileDossier$ | async as fileDossier" [routerLink]="fileDossier.routerLink" class="small-label link-action">
|
||||
<a
|
||||
*ngIf="item.isFile && fileDossier$ | async as fileDossier"
|
||||
[routerLink]="fileDossier.routerLink | tenant"
|
||||
class="small-label link-action"
|
||||
>
|
||||
{{ fileDossier.dossierName }}
|
||||
</a>
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TrashScreenComponent } from './trash-screen/trash-screen.component';
|
||||
import { CircleButtonComponent, IqserListingModule, IqserUsersModule } from '@iqser/common-ui';
|
||||
import { CircleButtonComponent, IqserListingModule, IqserUsersModule, TenantPipe } from '@iqser/common-ui';
|
||||
import { TrashTableItemComponent } from './trash-screen/trash-table-item/trash-table-item.component';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { TrashDialogService } from './services/trash-dialog.service';
|
||||
@ -20,6 +20,7 @@ const routes = [{ path: '', component: TrashScreenComponent }];
|
||||
TranslateModule,
|
||||
IqserListingModule,
|
||||
CircleButtonComponent,
|
||||
TenantPipe,
|
||||
],
|
||||
providers: [TrashDialogService],
|
||||
})
|
||||
|
||||
@ -3,15 +3,14 @@ import { DownloadStatus, IDownloadStatus, IDownloadStatusResponse, IPrepareDownl
|
||||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { EntitiesService, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { EntitiesService, mapEach, RequiredParam, TenantContextHolder, Validate } from '@iqser/common-ui';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { TenantContext } from '@utils/tenant-context';
|
||||
|
||||
@Injectable()
|
||||
export class FileDownloadService extends EntitiesService<IDownloadStatus, DownloadStatus> {
|
||||
protected readonly _defaultModelPath = 'async/download';
|
||||
protected readonly _entityClass = DownloadStatus;
|
||||
protected readonly _tenantContext = inject(TenantContext);
|
||||
protected readonly _tenantContext = inject(TenantContextHolder);
|
||||
|
||||
constructor(private readonly _configService: ConfigService, private readonly _logger: NGXLogger) {
|
||||
super();
|
||||
@ -37,7 +36,7 @@ export class FileDownloadService extends EntitiesService<IDownloadStatus, Downlo
|
||||
const token = await this.generateToken(status.storageId);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = `${this._configService.values.API_URL}/async/download/with-ott/${token.value}?tenantId=${encodeURIComponent(
|
||||
this._tenantContext.getTenant(),
|
||||
this._tenantContext.currentTenant,
|
||||
)}`;
|
||||
anchor.download = status.filename;
|
||||
anchor.target = '_blank';
|
||||
|
||||
@ -8,6 +8,7 @@ import { DossiersService } from './dossiers.service';
|
||||
import { FilesMapService } from '../files/files-map.service';
|
||||
import { FeaturesService } from '../features.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ArchivedDossiersService extends DossiersService {
|
||||
@ -17,6 +18,7 @@ export class ArchivedDossiersService extends DossiersService {
|
||||
readonly #activeDossiersService = inject(ActiveDossiersService);
|
||||
readonly #filesMapService = inject(FilesMapService);
|
||||
readonly #featuresService = inject(FeaturesService);
|
||||
readonly #tenantContextHolder = inject(TenantContextHolder);
|
||||
readonly #router = inject(Router);
|
||||
|
||||
archive(dossiers: Dossier[]): Observable<unknown> {
|
||||
@ -40,7 +42,7 @@ export class ArchivedDossiersService extends DossiersService {
|
||||
if (!this.#activeDossiersService.all.find(d => d.dossierTemplateId === dossierTemplateId)) {
|
||||
route = route.replace(DOSSIERS_ROUTE, ARCHIVE_ROUTE);
|
||||
}
|
||||
await this.#router.navigate([route]);
|
||||
await this.#router.navigate([`/${this.#tenantContextHolder.currentTenant}${route}`]);
|
||||
}),
|
||||
catchError(showArchiveFailedToast),
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { TenantContextHolder } from '@iqser/common-ui';
|
||||
|
||||
const LAST_DOSSIERS_SCREEN = 'routerHistory_lastDossiersScreen';
|
||||
|
||||
@ -10,7 +11,8 @@ const LAST_DOSSIERS_SCREEN = 'routerHistory_lastDossiersScreen';
|
||||
export class RouterHistoryService {
|
||||
private _lastDossiersScreen = localStorage.getItem(LAST_DOSSIERS_SCREEN);
|
||||
|
||||
constructor(private readonly _router: Router) {
|
||||
constructor(private readonly _router: Router, private readonly _tenantContextHolder: TenantContextHolder) {
|
||||
// eslint-disable-next-line rxjs/no-ignored-subscription
|
||||
this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
|
||||
if (event.url.includes('/dossiers') || event.url.includes('/archive')) {
|
||||
this._lastDossiersScreen = event.url;
|
||||
@ -21,9 +23,10 @@ export class RouterHistoryService {
|
||||
|
||||
navigateToLastDossiersScreen(): void {
|
||||
if (this._router.url === decodeURI(this._lastDossiersScreen)) {
|
||||
this._router.navigate(['/']);
|
||||
this._router.navigate(['/' + this._tenantContextHolder.currentTenant]);
|
||||
} else {
|
||||
const url = decodeURI(this._lastDossiersScreen).split('?')[0];
|
||||
// todo links
|
||||
this._router.navigate([url]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { UserService } from './user.service';
|
||||
import { IqserPermissionsService, IqserRoleGuard } from '@iqser/common-ui';
|
||||
import { IqserPermissionsService, IqserRoleGuard, TenantContextHolder } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -9,19 +9,20 @@ import { IqserPermissionsService, IqserRoleGuard } from '@iqser/common-ui';
|
||||
export class RedRoleGuard extends IqserRoleGuard {
|
||||
protected readonly _userService = inject(UserService);
|
||||
protected readonly _permissionsService = inject(IqserPermissionsService);
|
||||
protected readonly _tenantContextHolder = inject(TenantContextHolder);
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
const currentUser = this._userService.currentUser;
|
||||
|
||||
if (!currentUser?.hasAnyRole) {
|
||||
await this._router.navigate(['/auth-error']);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/auth-error`]);
|
||||
this._loadingService.stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
// we have at least 1 RED Role -> if it's not user he must be an admin
|
||||
if (currentUser.isUserAdmin && !currentUser.isAdmin && state.url.includes('admin') && !state.url.includes('users')) {
|
||||
await this._router.navigate(['/main/admin/users']);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin/users`]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -29,9 +30,9 @@ export class RedRoleGuard extends IqserRoleGuard {
|
||||
currentUser.isUserAdmin &&
|
||||
!currentUser.isAdmin &&
|
||||
!currentUser.isUser &&
|
||||
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/account'))
|
||||
!(state.url.includes('/main/admin/users') || state.url.includes('/main/account'))
|
||||
) {
|
||||
await this._router.navigate(['/main/admin/users']);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin/users`]);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -40,9 +41,9 @@ export class RedRoleGuard extends IqserRoleGuard {
|
||||
return true;
|
||||
}
|
||||
if (!currentUser.isUser) {
|
||||
await this._router.navigate(['/main/admin']);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin`]);
|
||||
} else {
|
||||
await this._router.navigate(['/']);
|
||||
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}`]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { catchError, filter, switchMap, tap } from 'rxjs/operators';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { firstValueFrom, map, of, throwError } from 'rxjs';
|
||||
import { KeycloakEventType, KeycloakService } from 'keycloak-angular';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { IqserPermissionsService, LanguageService } from '@iqser/common-ui';
|
||||
import { IqserPermissionsService, KeycloakStatus, KeycloakStatusService, LanguageService } from '@iqser/common-ui';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
@ -21,6 +21,7 @@ function lastDossierTemplateRedirect(baseHref: string, userPreferenceService: Us
|
||||
|
||||
export function configurationInitializer(
|
||||
baseHref: string,
|
||||
keycloakStatusService: KeycloakStatusService,
|
||||
keycloakService: KeycloakService,
|
||||
configService: ConfigService,
|
||||
systemPreferencesService: SystemPreferencesService,
|
||||
@ -32,8 +33,9 @@ export function configurationInitializer(
|
||||
licenseService: LicenseService,
|
||||
permissionsService: IqserPermissionsService,
|
||||
) {
|
||||
const setup = keycloakService.keycloakEvents$.pipe(
|
||||
filter(event => event.type === KeycloakEventType.OnReady),
|
||||
console.log('BASE HREF: ', baseHref);
|
||||
const setup = keycloakStatusService.keycloakStatus$.pipe(
|
||||
filter(event => event === KeycloakStatus.READY || event === KeycloakStatus.NOT_ACTIVE),
|
||||
map(() => featuresService.loadConfig()),
|
||||
switchMap(() => keycloakService.isLoggedIn()),
|
||||
switchMap(loggedIn => (!loggedIn ? throwError(() => 'Not Logged In') : of({}))),
|
||||
@ -43,8 +45,12 @@ export function configurationInitializer(
|
||||
tap(configuration => configService.updateDisplayName(configuration.displayName)),
|
||||
switchMap(() => systemPreferencesService.loadPreferences()),
|
||||
switchMap(() => userPreferenceService.reload()),
|
||||
catchError(e => {
|
||||
catchError((e: unknown) => {
|
||||
console.log('[Redaction] Initialization error:', e);
|
||||
if (keycloakStatusService.keycloakStatus$.value === KeycloakStatus.READY) {
|
||||
return of(userService.redirectToLogin());
|
||||
}
|
||||
|
||||
return of({});
|
||||
}),
|
||||
tap(() => {
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TenantContext {
|
||||
getTenant(): string {
|
||||
return 'redaction';
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TenantContext } from '@utils/tenant-context';
|
||||
|
||||
@Injectable()
|
||||
export class TenantIdInterceptor implements HttpInterceptor {
|
||||
protected readonly _tenantContext = inject(TenantContext);
|
||||
|
||||
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
const updatedRequest = req.clone({
|
||||
setHeaders: { 'X-TENANT-ID': this._tenantContext.getTenant() },
|
||||
});
|
||||
return next.handle(updatedRequest);
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
const VALID_TENANT_IDS = ['redaction'];
|
||||
|
||||
@Injectable()
|
||||
export class TenantIdResponseInterceptor implements HttpInterceptor {
|
||||
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
return next.handle(req).pipe(
|
||||
tap(event => {
|
||||
if (event instanceof HttpResponse) {
|
||||
const xTenantId = event.headers.get('X-TENANT-ID');
|
||||
if (VALID_TENANT_IDS.includes(xTenantId)) {
|
||||
//TODO add logic to deny the response when backend will send the header
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
|
||||
"API_URL": "https://dom1.iqser.cloud/redaction-gateway-v1",
|
||||
"APP_NAME": "RedactManager",
|
||||
"AUTO_READ_TIME": 3,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
@ -11,7 +11,7 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction",
|
||||
"OAUTH_URL": "https://dom1.iqser.cloud/auth",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural",
|
||||
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
|
||||
|
||||
@ -1764,6 +1764,7 @@
|
||||
"title": "Benachrichtigungseinstellungen"
|
||||
},
|
||||
"notifications": {
|
||||
"button-text": "",
|
||||
"deleted-dossier": "",
|
||||
"label": "Benachrichtigungen",
|
||||
"mark-all-as-read": "Alle als gelesen markieren",
|
||||
@ -1815,11 +1816,6 @@
|
||||
"previous": "Vorherige"
|
||||
},
|
||||
"pdf-viewer": {
|
||||
"text-popup": {
|
||||
"actions": {
|
||||
"search": "Nach Auswahl suchen"
|
||||
}
|
||||
},
|
||||
"toggle-readable-redactions": "",
|
||||
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} Kurzinfos für Anmerkungen"
|
||||
},
|
||||
@ -2051,6 +2047,15 @@
|
||||
},
|
||||
"title": "Authentifizierung aktivieren"
|
||||
},
|
||||
"tenant-resolve": {
|
||||
"actions": {
|
||||
"save": "Proceed"
|
||||
},
|
||||
"form": {
|
||||
"tenant-placeholder": "Select a tenant ..."
|
||||
},
|
||||
"header": "Select your Tenant"
|
||||
},
|
||||
"time": {
|
||||
"days": "{days} {days, plural, one{Tag} other{Tage}}",
|
||||
"hours": "{hours} {hours, plural, one{Stunde} other{Stunden}}",
|
||||
@ -2078,6 +2083,7 @@
|
||||
"label": "Sprache"
|
||||
},
|
||||
"logout": "Abmelden",
|
||||
"select-tenant": "Select Tenant",
|
||||
"trash": "Papierkorb"
|
||||
}
|
||||
}
|
||||
@ -2180,6 +2186,9 @@
|
||||
}
|
||||
},
|
||||
"user-management": "Benutzerverwaltung",
|
||||
"user-menu": {
|
||||
"button-text": ""
|
||||
},
|
||||
"user-profile": "Mein Profil",
|
||||
"user-profile-screen": {
|
||||
"actions": {
|
||||
|
||||
@ -1764,6 +1764,7 @@
|
||||
"title": "Notifications Preferences"
|
||||
},
|
||||
"notifications": {
|
||||
"button-text": "Notifications",
|
||||
"deleted-dossier": "Deleted Dossier",
|
||||
"label": "Notifications",
|
||||
"mark-all-as-read": "Mark all as read",
|
||||
@ -1815,11 +1816,6 @@
|
||||
"previous": "Prev"
|
||||
},
|
||||
"pdf-viewer": {
|
||||
"text-popup": {
|
||||
"actions": {
|
||||
"search": "Search for selection"
|
||||
}
|
||||
},
|
||||
"toggle-readable-redactions": "Show redactions {active, select, true{as in final document} false{in preview color} other{}}",
|
||||
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} annotation tooltips"
|
||||
},
|
||||
@ -2051,6 +2047,15 @@
|
||||
},
|
||||
"title": "Enable Authentication"
|
||||
},
|
||||
"tenant-resolve": {
|
||||
"actions": {
|
||||
"save": "Proceed"
|
||||
},
|
||||
"form": {
|
||||
"tenant-placeholder": "Select a tenant ..."
|
||||
},
|
||||
"header": "Select your Tenant"
|
||||
},
|
||||
"time": {
|
||||
"days": "{days} {days, plural, one{day} other{days}}",
|
||||
"hours": "{hours} {hours, plural, one{hour} other{hours}}",
|
||||
@ -2078,6 +2083,7 @@
|
||||
"label": "Language"
|
||||
},
|
||||
"logout": "Logout",
|
||||
"select-tenant": "Select Tenant",
|
||||
"trash": "Trash"
|
||||
}
|
||||
}
|
||||
@ -2180,6 +2186,9 @@
|
||||
}
|
||||
},
|
||||
"user-management": "User Management",
|
||||
"user-menu": {
|
||||
"button-text": "User menu"
|
||||
},
|
||||
"user-profile": "My Profile",
|
||||
"user-profile-screen": {
|
||||
"actions": {
|
||||
|
||||
@ -1764,6 +1764,7 @@
|
||||
"title": "Benachrichtigungseinstellungen"
|
||||
},
|
||||
"notifications": {
|
||||
"button-text": "",
|
||||
"deleted-dossier": "",
|
||||
"label": "Benachrichtigungen",
|
||||
"mark-all-as-read": "Alle als gelesen markieren",
|
||||
@ -1815,11 +1816,6 @@
|
||||
"previous": "Vorherige"
|
||||
},
|
||||
"pdf-viewer": {
|
||||
"text-popup": {
|
||||
"actions": {
|
||||
"search": "Nach Auswahl suchen"
|
||||
}
|
||||
},
|
||||
"toggle-readable-redactions": "",
|
||||
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} Kurzinfos für Anmerkungen"
|
||||
},
|
||||
@ -2051,6 +2047,15 @@
|
||||
},
|
||||
"title": "Authentifizierung aktivieren"
|
||||
},
|
||||
"tenant-resolve": {
|
||||
"actions": {
|
||||
"save": "Proceed"
|
||||
},
|
||||
"form": {
|
||||
"tenant-placeholder": "Select a tenant ..."
|
||||
},
|
||||
"header": "Select your Tenant"
|
||||
},
|
||||
"time": {
|
||||
"days": "{days} {days, plural, one{Tag} other{Tage}}",
|
||||
"hours": "{hours} {hours, plural, one{Stunde} other{Stunden}}",
|
||||
@ -2078,6 +2083,7 @@
|
||||
"label": "Sprache"
|
||||
},
|
||||
"logout": "Abmelden",
|
||||
"select-tenant": "Select Tenant",
|
||||
"trash": "Papierkorb"
|
||||
}
|
||||
}
|
||||
@ -2180,6 +2186,9 @@
|
||||
}
|
||||
},
|
||||
"user-management": "Benutzerverwaltung",
|
||||
"user-menu": {
|
||||
"button-text": ""
|
||||
},
|
||||
"user-profile": "Mein Profil",
|
||||
"user-profile-screen": {
|
||||
"actions": {
|
||||
|
||||
@ -1764,6 +1764,7 @@
|
||||
"title": "Notifications Preferences"
|
||||
},
|
||||
"notifications": {
|
||||
"button-text": "",
|
||||
"deleted-dossier": "Deleted Dossier",
|
||||
"label": "Notifications",
|
||||
"mark-all-as-read": "Mark all as read",
|
||||
@ -1815,11 +1816,6 @@
|
||||
"previous": "Prev"
|
||||
},
|
||||
"pdf-viewer": {
|
||||
"text-popup": {
|
||||
"actions": {
|
||||
"search": "Search for selection"
|
||||
}
|
||||
},
|
||||
"toggle-readable-redactions": "Show components {active, select, true{as in final document} false{in preview color} other{}}",
|
||||
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} annotation tooltips"
|
||||
},
|
||||
@ -2051,6 +2047,15 @@
|
||||
},
|
||||
"title": "Enable Authentication"
|
||||
},
|
||||
"tenant-resolve": {
|
||||
"actions": {
|
||||
"save": "Proceed"
|
||||
},
|
||||
"form": {
|
||||
"tenant-placeholder": "Select a tenant ..."
|
||||
},
|
||||
"header": "Select your Tenant"
|
||||
},
|
||||
"time": {
|
||||
"days": "{days} {days, plural, one{day} other{days}}",
|
||||
"hours": "{hours} {hours, plural, one{hour} other{hours}}",
|
||||
@ -2078,6 +2083,7 @@
|
||||
"label": "Language"
|
||||
},
|
||||
"logout": "Logout",
|
||||
"select-tenant": "Select Tenant",
|
||||
"trash": "Trash"
|
||||
}
|
||||
}
|
||||
@ -2180,6 +2186,9 @@
|
||||
}
|
||||
},
|
||||
"user-management": "User Management",
|
||||
"user-menu": {
|
||||
"button-text": ""
|
||||
},
|
||||
"user-profile": "My Profile",
|
||||
"user-profile-screen": {
|
||||
"actions": {
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit ba21886e27187490c526816c4692c541384e1f5f
|
||||
Subproject commit 9bb7619907b3965da28a3477addcb3a3082d4b3b
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "redaction",
|
||||
"version": "4.31.0",
|
||||
"version": "4.39.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user