Merge branch 'master' into VM/RED-6801

This commit is contained in:
Valentin Mihai 2023-07-10 11:28:57 +03:00
commit cf655c0c5c
236 changed files with 1274 additions and 1165 deletions

View File

@ -1,22 +1,12 @@
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
import {
CompositeRouteGuard,
CustomRouteReuseStrategy,
DEFAULT_REDIRECT_KEY,
ifLoggedIn,
ifNotLoggedIn,
IqserAuthGuard,
IqserPermissionsGuard,
IqserRoutes,
TenantSelectComponent,
} from '@iqser/common-ui';
import { CompositeRouteGuard, DEFAULT_REDIRECT_KEY, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
import { RedRoleGuard } from '@users/red-role.guard';
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
import { RouteReuseStrategy, RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
import { DossiersGuard } from '@guards/dossiers.guard';
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
import { loadActiveDossiersGuard, loadAllDossiersGuard, loadArchivedDossiersGuard } from '@guards/dossiers.guard';
import { ACTIVE_DOSSIERS_SERVICE } from './tokens';
import { FeaturesGuard } from '@guards/features-guard.service';
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
import { templateExistsWhenEnteringDossierList } from '@guards/dossier-template-exists.guard';
@ -24,17 +14,20 @@ import { DashboardGuard } from '@guards/dashboard-guard.service';
import { TrashGuard } from '@guards/trash.guard';
import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE, FILE_ID } from '@red/domain';
import { DossierFilesGuard } from '@guards/dossier-files-guard';
import { WebViewerLoadedGuard } from './modules/pdf-viewer/services/webviewer-loaded.guard';
import { webViewerLoadedGuard } from './modules/pdf-viewer/services/webviewer-loaded.guard';
import { Roles } from '@users/roles';
import { mainResolver } from '@utils/main.resolver';
import { hasAnyRoleGuard, IqserAuthGuard } from '@iqser/common-ui/lib/users';
import { CustomRouteReuseStrategy } from '@iqser/common-ui/lib/utils';
import { ifLoggedIn } from '@guards/if-logged-in.guard';
import { ifNotLoggedIn } from '@guards/if-not-logged-in.guard';
import { TenantSelectComponent } from '@iqser/common-ui/lib/tenants';
const dossierTemplateIdRoutes: IqserRoutes = [
{
path: `${DOSSIERS_ROUTE}`,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canActivate: [loadActiveDossiersGuard(), IqserPermissionsGuard],
data: {
routeGuards: [DossiersGuard],
dossiersService: ACTIVE_DOSSIERS_SERVICE,
permissions: {
allow: [Roles.files.readStatus],
redirectTo: '/auth-error',
@ -58,9 +51,9 @@ const dossierTemplateIdRoutes: IqserRoutes = [
},
{
path: `:${DOSSIER_ID}/file/:${FILE_ID}`,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, webViewerLoadedGuard()],
data: {
routeGuards: [DossierFilesGuard, WebViewerLoadedGuard],
routeGuards: [DossierFilesGuard],
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier, BreadcrumbTypes.file],
dossiersService: ACTIVE_DOSSIERS_SERVICE,
permissions: {
@ -83,10 +76,9 @@ const dossierTemplateIdRoutes: IqserRoutes = [
{
path: `${ARCHIVE_ROUTE}`,
loadChildren: () => import('./modules/archive/archive.module').then(m => m.ArchiveModule),
canActivate: [CompositeRouteGuard, WebViewerLoadedGuard],
canActivate: [CompositeRouteGuard, webViewerLoadedGuard(), loadArchivedDossiersGuard()],
data: {
routeGuards: [FeaturesGuard, DossiersGuard],
dossiersService: ARCHIVED_DOSSIERS_SERVICE,
routeGuards: [FeaturesGuard],
features: [DOSSIERS_ARCHIVE],
},
},
@ -155,9 +147,9 @@ const mainRoutes: IqserRoutes = [
{
path: 'search',
loadChildren: () => import('./modules/search/search.module').then(m => m.SearchModule),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, loadAllDossiersGuard()],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, DossiersGuard],
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.search],
redirectTo: '/auth-error',
@ -167,10 +159,9 @@ const mainRoutes: IqserRoutes = [
{
path: 'trash',
loadChildren: () => import('./modules/trash/trash.module').then(m => m.TrashModule),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, loadActiveDossiersGuard()],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, DossiersGuard, TrashGuard],
dossiersService: ACTIVE_DOSSIERS_SERVICE,
routeGuards: [IqserAuthGuard, RedRoleGuard, TrashGuard],
permissions: {
allow: [Roles.dossiers.read, Roles.files.readStatus],
redirectTo: '/auth-error',
@ -182,7 +173,7 @@ const mainRoutes: IqserRoutes = [
children: dossierTemplateIdRoutes,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, templateExistsWhenEnteringDossierList()],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard],
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [
Roles.any,
@ -210,7 +201,7 @@ const routes: IqserRoutes = [
{
path: '',
pathMatch: 'full',
canActivate: [ifNotLoggedIn],
canActivate: [ifNotLoggedIn()],
component: TenantSelectComponent,
},
{
@ -220,7 +211,7 @@ const routes: IqserRoutes = [
},
{
path: ':tenant/main',
canActivate: [ifLoggedIn],
canActivate: [ifLoggedIn()],
component: BaseScreenComponent,
resolve: {
whateverThisMainRouteNeeds: mainResolver,
@ -230,7 +221,7 @@ const routes: IqserRoutes = [
{
path: ':tenant/auth-error',
component: AuthErrorComponent,
canActivate: [IqserAuthGuard],
canActivate: [hasAnyRoleGuard()],
},
{
path: '**',

View File

@ -9,7 +9,6 @@ import {
CachingModule,
ChevronButtonComponent,
CircleButtonComponent,
CommonUiModule,
EmptyStateComponent,
HelpModeKey,
HiddenActionDirective,
@ -21,18 +20,12 @@ import {
IqserListingModule,
IqserLoadingModule,
IqserTranslateModule,
IqserUsersModule,
LanguageService,
LogoComponent,
MAX_RETRIES_ON_SERVER_ERROR,
RoundCheckboxComponent,
SERVER_ERROR_SKIP_PATHS,
ServerErrorInterceptor,
SkeletonComponent,
StopPropagationDirective,
TenantPipe,
TenantsModule,
ToastComponent,
} from '@iqser/common-ui';
import { ToastrModule } from 'ngx-toastr';
import { ServiceWorkerModule } from '@angular/service-worker';
@ -71,6 +64,10 @@ import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skel
import { UserMenuComponent } from '@components/user-menu/user-menu.component';
import { TenantsMenuComponent } from '@components/tenants-menu/tenants-menu.component';
import { MatDividerModule } from '@angular/material/divider';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { CommonUiModule } from '@iqser/common-ui/lib/common-ui.module';
import { LogoComponent, SkeletonComponent, ToastComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe, TenantsModule } from '@iqser/common-ui/lib/tenants';
export const appModuleFactory = (config: AppConfig) => {
@NgModule({
@ -144,10 +141,10 @@ export const appModuleFactory = (config: AppConfig) => {
enabled: true,
},
FILE: {
enabled: false,
enabled: true,
},
CHANGES: {
enabled: false,
enabled: true,
},
STATS: {
enabled: false,

View File

@ -6,15 +6,17 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
import { filter, map, startWith } from 'rxjs/operators';
import { IqserPermissionsService, List, shareDistinctLast, TenantsService } from '@iqser/common-ui';
import { IqserPermissionsService } 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';
import { Roles } from '@users/roles';
import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { List, shareDistinctLast } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
const isNavigationStart = event => event instanceof NavigationStart;
const isNavigationStart = (event: unknown): event is NavigationStart => event instanceof NavigationStart;
const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
@Component({
@ -22,6 +24,12 @@ const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
styleUrls: ['./base-screen.component.scss'],
})
export class BaseScreenComponent {
readonly #navigationStart$ = this._router.events.pipe(
filter(isNavigationStart),
map(event => event.url),
startWith(this._router.url),
shareDistinctLast(),
);
readonly roles = Roles;
readonly documentViewer = inject(REDDocumentViewer);
readonly currentUser = this.userService.currentUser;
@ -44,12 +52,6 @@ export class BaseScreenComponent {
action: (query): void => this.#search(query, []),
},
];
readonly #navigationStart$ = this._router.events.pipe(
filter(isNavigationStart),
map((event: NavigationStart) => event.url),
startWith(this._router.url),
shareDistinctLast(),
);
readonly isSearchScreen$ = this.#navigationStart$.pipe(map(isSearchScreen));
constructor(

View File

@ -1,8 +1,8 @@
<iqser-circle-button
[matMenuTriggerFor]="menu"
[showDot]="hasUnreadNotifications$ | async"
[tooltip]="'notifications.button-text' | translate"
[tooltipPosition]="'below'"
[tooltip]="'notifications.button-text' | translate"
buttonId="notification-button"
icon="red:notification"
></iqser-circle-button>
@ -25,7 +25,7 @@
*ngIf="(hasUnreadNotifications$ | async) && first"
class="view-all"
id="notifications-mark-all-as-read-btn"
stopPropagation
iqserStopPropagation
>
{{ 'notifications.mark-all-as-read' | translate }}
</div>
@ -37,8 +37,8 @@
[class.unread]="!notification.readDate"
[id]="'notifications-mark-as-read-' + notification.id + '-btn'"
class="notification"
iqserStopPropagation
mat-menu-item
stopPropagation
>
<iqser-initials-avatar [user]="notification.userId"></iqser-initials-avatar>
@ -51,9 +51,9 @@
(click)="markRead([notification], !notification.readDate)"
[id]="'notifications-mark-' + notification.id"
class="dot"
iqserStopPropagation
matTooltip="{{ 'notifications.mark-as' | translate : { type: notification.readDate ? 'unread' : 'read' } }}"
matTooltipPosition="before"
stopPropagation
></div>
</div>
</div>

View File

@ -4,7 +4,7 @@ import { NotificationsService } from '@services/notifications.service';
import { Notification } from '@red/domain';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { isToday, shareLast, trackByFactory } from '@iqser/common-ui';
import { isToday, shareLast, trackByFactory } from '@iqser/common-ui/lib/utils';
import dayjs, { Dayjs } from 'dayjs';
import { TranslateService } from '@ngx-translate/core';

View File

@ -1,11 +1,11 @@
<div id="tenants-menu-items">
<ng-container *ngFor="let item of storedTenants | keyvalue; trackBy: trackBy">
<ng-container *ngFor="let item of storedTenants; trackBy: trackBy">
<div class="label">{{ item.key }}</div>
<a
(click)="selectTenant(stored.tenant.tenantId, stored.email)"
(click)="selectTenant(stored.tenantId, stored.email)"
*ngFor="let stored of item.value"
[id]="stored.tenant.tenantId"
[id]="stored.tenantId + stored.email"
mat-menu-item
>
{{ stored.email }}

View File

@ -1,8 +1,10 @@
import { Component, inject } from '@angular/core';
import { BASE_HREF, getConfig, getCurrentUser, IStoredTenant, KeycloakStatusService, TenantsService } from '@iqser/common-ui';
import { getConfig } from '@iqser/common-ui';
import { User } from '@red/domain';
import { KeyValue } from '@angular/common';
import { KeycloakService } from 'keycloak-angular';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { BASE_HREF } from '@iqser/common-ui/lib/utils';
import { getKeycloakOptions, IStoredTenantId, KeycloakStatusService, TenantsService } from '@iqser/common-ui/lib/tenants';
@Component({
selector: 'app-tenants-menu',
@ -10,48 +12,49 @@ import { KeycloakService } from 'keycloak-angular';
styleUrls: ['./tenants-menu.component.scss'],
})
export class TenantsMenuComponent {
readonly tenantsService = inject(TenantsService);
readonly storedTenants: Map<string, IStoredTenant[]>;
readonly #baseHref = inject(BASE_HREF);
readonly #keycloakService = inject(KeycloakService);
readonly #keycloakStatusService = inject(KeycloakStatusService);
readonly #currentUser = getCurrentUser<User>();
readonly #config = getConfig();
readonly tenantsService = inject(TenantsService);
readonly storedTenants: { key: string; value: IStoredTenantId[] }[];
constructor() {
this.storedTenants = this.#getStoredTenants();
}
trackBy(_index: number, item: KeyValue<string, IStoredTenant[]>) {
trackBy(_index: number, item: { key: string; value: IStoredTenantId[] }) {
return item.key;
}
// TODO: this should be moved to the keycloak status service
async selectTenant(tenantId?: string, email?: string) {
let tenantUrl = tenantId ? '/' + tenantId : '/';
if (email) {
tenantUrl += '?username=' + email;
if (tenantId && tenantId !== this.tenantsService.activeTenantId) {
await this.#keycloakService.init(getKeycloakOptions(this.#baseHref, this.#config, tenantId));
}
if (tenantId === this.tenantsService.activeTenantId) {
if (tenantId) {
const url = this.#keycloakService.getKeycloakInstance().createLoginUrl({
redirectUri: this.#keycloakStatusService.createLoginUrl(),
redirectUri: this.#keycloakStatusService.createLoginUrl(tenantId),
idpHint: this.#config.OAUTH_IDP_HINT,
loginHint: email ?? undefined,
});
return this.#keycloakService.logout(url);
}
window.open(window.location.origin + this.#baseHref + tenantUrl, '_blank');
return this.#keycloakService.logout(window.location.origin + this.#baseHref);
}
#getStoredTenants(): Map<string, IStoredTenant[]> {
#getStoredTenants() {
const storedTenants = this.tenantsService.getStoredTenants();
const otherTenant = storedTenants.filter(t => {
const isCurrentTenant = t.tenant.tenantId === this.tenantsService.activeTenantId;
const isCurrentTenant = t.tenantId === this.tenantsService.activeTenantId;
const isCurrentUser = t.email === this.#currentUser.email || t.email === this.#currentUser.username;
return !(isCurrentTenant && isCurrentUser);
});
return otherTenant.groupBy(t => t.tenant.displayName);
const grouped = otherTenant.groupBy(t => t.tenantId);
return [...grouped.keys()].sort((a, b) => a.localeCompare(b)).map(key => ({ key, value: grouped.get(key) }));
}
}

View File

@ -5,7 +5,7 @@
</a>
</ng-container>
<a *ngIf="tenantsService.hasMultiple()" [id]="'switch-accounts'" [matMenuTriggerFor]="tenantsMenu" mat-menu-item>
<a *ngIf="tenantsService.getStoredTenants().length > 1" [id]="'switch-accounts'" [matMenuTriggerFor]="tenantsMenu" mat-menu-item>
{{ 'top-bar.navigation-items.my-account.children.select-tenant' | translate }}
</a>

View File

@ -1,9 +1,12 @@
import { Component, inject } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { Roles } from '@users/roles';
import { getCurrentUser, IqserPermissionsService, List, TenantsService } from '@iqser/common-ui';
import { IqserPermissionsService } from '@iqser/common-ui';
import { User } from '@red/domain';
import { UserService } from '@users/user.service';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { List } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
interface MenuItem {
readonly id: string;
@ -20,10 +23,10 @@ interface MenuItem {
styleUrls: ['./user-menu.component.scss'],
})
export class UserMenuComponent {
readonly #permissionsService = inject(IqserPermissionsService);
readonly currentUser = getCurrentUser<User>();
readonly tenantsService = inject(TenantsService);
readonly userService = inject(UserService);
readonly #permissionsService = inject(IqserPermissionsService);
readonly userMenuItems: List<MenuItem> = [
{
id: 'account',

View File

@ -7,7 +7,7 @@ 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 { TenantsService } from '@iqser/common-ui';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
@Injectable({ providedIn: 'root' })
export class DossierFilesGuard implements CanActivate {

View File

@ -2,7 +2,10 @@ import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
import { TenantsService } from '@iqser/common-ui';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
import { NGXLogger } from 'ngx-logger';
import { firstValueFrom } from 'rxjs';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
export function templateExistsWhenEnteringAdmin(): CanActivateFn {
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
@ -20,10 +23,18 @@ export function templateExistsWhenEnteringAdmin(): CanActivateFn {
export function templateExistsWhenEnteringDossierList(): CanActivateFn {
return async function (route: ActivatedRouteSnapshot) {
const dossierTemplateId: string = route.paramMap.get(DOSSIER_TEMPLATE_ID);
const dashboardStatsService = inject(DashboardStatsService);
const dossierTemplatesService = inject(DossierTemplatesService);
const logger = inject(NGXLogger);
const router = inject(Router);
const tenantsService = inject(TenantsService);
const dossierTemplateStats = inject(DashboardStatsService).find(dossierTemplateId);
await firstValueFrom(dashboardStatsService.loadAll());
await firstValueFrom(dossierTemplatesService.loadAll());
const dossierTemplateStats = dashboardStatsService.find(dossierTemplateId);
if (!dossierTemplateStats || dossierTemplateStats.isEmpty) {
await inject(Router).navigate([inject(TenantsService).activeTenantId, 'main']);
logger.warn('[ROUTES] Dossier template not found, redirecting to main');
await router.navigate([tenantsService.activeTenantId, 'main']);
return false;
}
return true;

View File

@ -1,45 +1,57 @@
import { Injectable, Injector, ProviderToken } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { firstValueFrom, forkJoin } from 'rxjs';
import { take } from 'rxjs/operators';
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
import { TenantsService } from '@iqser/common-ui';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
import { NGXLogger } from 'ngx-logger';
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from '../tokens';
@Injectable({ providedIn: 'root' })
export class DossiersGuard implements CanActivate {
constructor(
private readonly _injector: Injector,
private readonly _router: Router,
private readonly _tenantsService: TenantsService,
private readonly _dashboardStatsService: DashboardStatsService,
private readonly _activeDossiersService: ActiveDossiersService,
private readonly _archivedDossiersService: ArchivedDossiersService,
) {}
export function loadAllDossiersGuard(): CanActivateFn {
return async () => {
const logger = inject(NGXLogger);
logger.info('[GUARDS] loadAllDossiersGuard start');
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
const token: ProviderToken<DossiersService> = route.data.dossiersService;
if (!token) {
const services = [this._archivedDossiersService, this._activeDossiersService];
const loading$ = forkJoin(services.map(service => service.loadAll().pipe(take(1))));
await firstValueFrom(loading$);
return true;
}
const services = [inject(ArchivedDossiersService), inject(ActiveDossiersService)];
const requests = services.map(service => firstValueFrom(service.loadAll()));
await Promise.all(requests);
const dossiersService: DossiersService = this._injector.get<DossiersService>(token);
const isArchive = dossiersService.routerPath === ARCHIVE_ROUTE;
logger.info('[GUARDS] loadAllDossiersGuard end');
return true;
};
}
export function loadActiveDossiersGuard(): CanActivateFn {
return async () => {
const logger = inject(NGXLogger);
logger.info('[GUARDS] loadDossiersGuard start');
await firstValueFrom(inject<ActiveDossiersService>(ACTIVE_DOSSIERS_SERVICE).loadAll());
logger.info('[GUARDS] loadDossiersGuard end');
return true;
};
}
export function loadArchivedDossiersGuard(): CanActivateFn {
return async (route: ActivatedRouteSnapshot) => {
const logger = inject(NGXLogger);
logger.info('[GUARDS] loadArchivedDossiersGuard start');
const dossiersService = inject<ArchivedDossiersService>(ARCHIVED_DOSSIERS_SERVICE);
const dossierTemplateId = route.paramMap.get(DOSSIER_TEMPLATE_ID);
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
const dossierTemplateStats = inject(DashboardStatsService).find(dossierTemplateId);
if (isArchive && dossierTemplateStats?.numberOfArchivedDossiers === 0) {
await this._router.navigate([this._tenantsService.activeTenantId, 'main', dossierTemplateId, 'dossiers']);
if (dossierTemplateStats?.numberOfArchivedDossiers === 0) {
logger.info('[GUARDS] loadArchivedDossiersGuard no archived dossiers, redirect to active dossiers page');
await inject(Router).navigate([inject(TenantsService).activeTenantId, 'main', dossierTemplateId, 'dossiers']);
return false;
}
await firstValueFrom(dossiersService.loadAll());
logger.info('[GUARDS] loadArchivedDossiersGuard end');
return true;
}
};
}

View File

@ -3,7 +3,7 @@ import { ActivatedRouteSnapshot, CanActivateFn, 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 { TenantsService } from '@iqser/common-ui';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
export function entityExistsGuard(): CanActivateFn {
return async function (route: ActivatedRouteSnapshot): Promise<boolean> {

View File

@ -0,0 +1,50 @@
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { NGXLogger } from 'ngx-logger';
import { keycloakInitializer, KeycloakStatusService, TenantsService } from '@iqser/common-ui/lib/tenants';
import { KeycloakService } from 'keycloak-angular';
import { UserService } from '@users/user.service';
import { LicenseService } from '@services/license.service';
export function ifLoggedIn(): CanActivateFn {
return async (route: ActivatedRouteSnapshot) => {
const logger = inject(NGXLogger);
logger.info('[ROUTES] Check if can activate route', route);
const tenantsService = inject(TenantsService);
const keycloakService = inject(KeycloakService);
const usersService = inject(UserService);
const licenseService = inject(LicenseService);
const keycloakStatusService = inject(KeycloakStatusService);
const keycloakInstance = keycloakService.getKeycloakInstance();
const tenant = route.paramMap.get('tenant');
const queryParams = new URLSearchParams(window.location.search);
const username = queryParams.get('username');
if (!keycloakInstance) {
if (!tenant) {
logger.error('[ROUTES] No tenant found, something is wrong...');
return inject(Router).navigate(['/']);
}
logger.info('[KEYCLOAK] Keycloak init...');
await keycloakInitializer(tenant);
logger.info('[KEYCLOAK] Keycloak init done!');
await tenantsService.selectTenant(tenant);
await usersService.initialize();
await licenseService.loadLicenses();
}
const isLoggedIn = await keycloakService.isLoggedIn();
if (isLoggedIn) {
logger.info('[ROUTES] Is logged in, continuing');
return true;
}
logger.warn('[ROUTES] Redirect to login');
keycloakStatusService.createLoginUrlAndExecute(username);
return false;
};
}

View File

@ -0,0 +1,29 @@
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { NGXLogger } from 'ngx-logger';
import { KeycloakService } from 'keycloak-angular';
export function ifNotLoggedIn(): CanActivateFn {
return async (route: ActivatedRouteSnapshot) => {
const logger = inject(NGXLogger);
const router = inject(Router);
const keycloakService = inject(KeycloakService);
const isLoggedIn = await keycloakService.isLoggedIn();
if (!isLoggedIn) {
logger.info('[ROUTES] Not logged in, continuing to selected route');
return true;
}
const tenant = route.paramMap.get('tenant') || keycloakService.getKeycloakInstance().realm;
if (!tenant) {
logger.error('[ROUTES] Tenant not found in route or keycloak realm');
return false;
}
logger.warn('[ROUTES] Is logged in for ' + tenant + ', redirecting to /' + tenant);
await router.navigate([tenant]);
return false;
};
}

View File

@ -2,7 +2,7 @@ import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
import { WatermarksMapService } from '@services/entity-services/watermarks-map.service';
import { DOSSIER_TEMPLATE_ID, WATERMARK_ID } from '@red/domain';
import { TenantsService } from '@iqser/common-ui';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
export function watermarkExistsGuard(): CanActivateFn {
return async function (route: ActivatedRouteSnapshot) {

View File

@ -1,10 +1,11 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CompositeRouteGuard, IqserAuthGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
import { RedRoleGuard } from '@users/red-role.guard';
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
import { PreferencesComponent } from './screens/preferences/preferences.component';
import { Roles } from '@users/roles';
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
const routes: IqserRoutes = [
{ path: '', redirectTo: 'user-profile', pathMatch: 'full' },

View File

@ -1,8 +1,9 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { getCurrentUser, IqserPermissionsService } from '@iqser/common-ui';
import { IqserPermissionsService } from '@iqser/common-ui';
import { Roles } from '@users/roles';
import { User } from '@red/domain';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
interface NavItem {
readonly label: string;

View File

@ -6,8 +6,9 @@ import { AccountSideNavComponent } from './account-side-nav/account-side-nav.com
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
import { NotificationPreferencesService } from './services/notification-preferences.service';
import { TranslateModule } from '@ngx-translate/core';
import { IconButtonComponent, IqserHelpModeModule, SideNavComponent } from '@iqser/common-ui';
import { IconButtonComponent, IqserHelpModeModule } from '@iqser/common-ui';
import { PreferencesComponent } from './screens/preferences/preferences.component';
import { SideNavComponent } from '@iqser/common-ui/lib/shared';
@NgModule({
declarations: [AccountSideNavComponent, BaseAccountScreenComponent, PreferencesComponent],

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
import { BaseFormComponent, getCurrentUser, LoadingService, Toaster } from '@iqser/common-ui';
import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import {
NotificationCategoriesValues,
@ -12,6 +12,7 @@ import {
} from '@red/domain';
import { firstValueFrom } from 'rxjs';
import { notificationsSettingsTranslations } from '@translations/notifications-settings-translations';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
@Component({
templateUrl: './notifications-screen.component.html',
@ -24,6 +25,7 @@ export class NotificationsScreenComponent extends BaseFormComponent implements O
readonly notificationGroupsValues = NotificationGroupsValues;
readonly translations = notificationsSettingsTranslations;
readonly currentUser = getCurrentUser<User>();
constructor(
private readonly _toaster: Toaster,
private readonly _formBuilder: UntypedFormBuilder,

View File

@ -2,8 +2,9 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { AsControl, BaseFormComponent, IqserPermissionsService } from '@iqser/common-ui';
import { BaseFormComponent, IqserPermissionsService } from '@iqser/common-ui';
import { Roles } from '@users/roles';
import { AsControl } from '@iqser/common-ui/lib/utils';
interface PreferencesForm {
// preferences

View File

@ -2,15 +2,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit }
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { DomSanitizer } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import {
BaseFormComponent,
getConfig,
IqserPermissionsService,
LanguageService,
LoadingService,
TenantsService,
Toaster,
} from '@iqser/common-ui';
import { BaseFormComponent, getConfig, IqserPermissionsService, LanguageService, LoadingService, Toaster } from '@iqser/common-ui';
import { AppConfig, IProfile } from '@red/domain';
import { languagesTranslations } from '@translations/languages-translations';
import { UserService } from '@users/user.service';
@ -19,6 +11,7 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { Roles } from '@users/roles';
import { UserProfileDialogService } from '../services/user-profile-dialog.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
@Component({
templateUrl: './user-profile-screen.component.html',
@ -26,12 +19,11 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
#profileModel: IProfile;
readonly translations = languagesTranslations;
readonly devMode = this._userPreferenceService.areDevFeaturesEnabled;
readonly changePasswordUrl: string;
#profileModel: IProfile;
constructor(
domSanitizer: DomSanitizer,
private readonly _userService: UserService,

View File

@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { CompositeRouteGuard, IqserAuthGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
import { RedRoleGuard } from '@users/red-role.guard';
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
@ -18,6 +18,7 @@ import { entityExistsGuard } from '@guards/entity-exists-guard.service';
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
import { PermissionsGuard } from '@guards/permissions-guard';
import { Roles } from '@users/roles';
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
const dossierTemplateIdRoutes: IqserRoutes = [
{

View File

@ -49,13 +49,13 @@ import {
IqserHelpModeModule,
IqserListingModule,
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';
import { DossierTemplateActionsComponent } from './shared/components/dossier-template-actions/dossier-template-actions.component';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
const dialogs = [
AddEditCloneDossierTemplateDialogComponent,

View File

@ -4,21 +4,23 @@ 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, TenantsService } from '@iqser/common-ui';
import { LoadingService } 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';
import { PermissionsService } from '@services/permissions.service';
import { getParam } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
@Component({
templateUrl: './base-entity-screen.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BaseEntityScreenComponent {
readonly disabledItems$: Observable<string[]>;
readonly canDeleteEntity$: Observable<boolean>;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly #entityType = getParam(ENTITY_TYPE);
readonly disabledItems$: Observable<string[]>;
readonly canDeleteEntity$: Observable<boolean>;
constructor(
private readonly _router: Router,

View File

@ -1,13 +1,14 @@
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { BaseFormComponent, IProfileUpdateRequest, LoadingService, Toaster } from '@iqser/common-ui';
import { BaseFormComponent, 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';
@Component({
selector: 'redaction-user-details',
@ -15,17 +16,15 @@ import { firstValueFrom } from 'rxjs';
styleUrls: ['./user-details.component.scss'],
})
export class UserDetailsComponent extends BaseFormComponent implements OnChanges {
/** e.g. a RED_ADMIN is automatically a RED_USER_ADMIN => can't disable RED_USER_ADMIN as long as RED_ADMIN is checked */
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
@Input() user: User;
@Output() readonly toggleResetPassword = new EventEmitter();
@Output() readonly closeDialog = new EventEmitter();
@Output() readonly cancel = new EventEmitter();
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
readonly translations = rolesTranslations;
/** e.g. a RED_ADMIN is automatically a RED_USER_ADMIN => can't disable RED_USER_ADMIN as long as RED_ADMIN is checked */
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
constructor(
private readonly _formBuilder: UntypedFormBuilder,
private readonly _toaster: Toaster,

View File

@ -3,7 +3,6 @@ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
import {
CircleButtonTypes,
getCurrentUser,
IqserPermissionsService,
ListingComponent,
listingProvidersFactory,
@ -19,6 +18,7 @@ import { Dayjs } from 'dayjs';
import { RouterHistoryService } from '@services/router-history.service';
import { Roles } from '@users/roles';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
const PAGE_SIZE = 50;
@ -28,6 +28,8 @@ const PAGE_SIZE = 50;
providers: listingProvidersFactory(AuditScreenComponent),
})
export class AuditScreenComponent extends ListingComponent<Audit> implements OnInit, OnDestroy {
private _previousFrom: Dayjs;
private _previousTo: Dayjs;
readonly circleButtonTypes = CircleButtonTypes;
readonly ALL_CATEGORIES = 'allCategories';
readonly ALL_USERS = _('audit-screen.all-users');
@ -37,11 +39,9 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
readonly permissionsService = inject(IqserPermissionsService);
readonly roles = Roles;
readonly currentUser = getCurrentUser<User>();
categories: string[] = [];
userIds: Set<string>;
logs: IAuditResponse;
readonly tableColumnConfigs: TableColumnConfig<Audit>[] = [
{ label: _('audit-screen.table-col-names.message') },
{ label: _('audit-screen.table-col-names.date') },
@ -49,8 +49,6 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
{ label: _('audit-screen.table-col-names.category') },
];
readonly tableHeaderLabel = _('audit-screen.table-header.title');
private _previousFrom: Dayjs;
private _previousTo: Dayjs;
constructor(
private readonly _formBuilder: UntypedFormBuilder,

View File

@ -1,21 +1,15 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { DefaultColorTypes, DOSSIER_TEMPLATE_ID, User } from '@red/domain';
import { AdminDialogService } from '../../services/admin-dialog.service';
import {
CircleButtonTypes,
getCurrentUser,
getParam,
IListable,
ListingComponent,
listingProvidersFactory,
TableColumnConfig,
} from '@iqser/common-ui';
import { CircleButtonTypes, IListable, ListingComponent, listingProvidersFactory, TableColumnConfig } from '@iqser/common-ui';
import { defaultColorsTranslations } from '@translations/default-colors-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { combineLatest } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { getParam } from '@iqser/common-ui/lib/utils';
interface ListItem extends IListable {
readonly key: string;
@ -29,6 +23,7 @@ interface ListItem extends IListable {
providers: listingProvidersFactory(DefaultColorsScreenComponent),
})
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = getCurrentUser<User>();
readonly roles = Roles;
@ -39,7 +34,6 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> {
{ label: _('default-colors-screen.table-col-names.color'), class: 'flex-center' },
];
readonly context$;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(private readonly _dialogService: AdminDialogService, private readonly _defaultColorsService: DefaultColorsService) {
super();

View File

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { getCurrentUser, IconButtonTypes, IqserPermissionsService, LoadingService, Toaster } from '@iqser/common-ui';
import { IconButtonTypes, IqserPermissionsService, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { RouterHistoryService } from '@services/router-history.service';
import { DigitalSignatureService } from '../../services/digital-signature.service';
@ -9,6 +9,7 @@ import { PkcsSignatureConfigurationComponent } from '../../dialogs/configure-dig
import { KmsSignatureConfigurationComponent } from '../../dialogs/configure-digital-signature-dialog/form/kms-signature-configuration/kms-signature-configuration.component';
import { DigitalSignatureOptions, IKmsDigitalSignatureRequest, IPkcsDigitalSignatureRequest, User } from '@red/domain';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
@Component({
selector: 'redaction-digital-signature-screen',

View File

@ -2,8 +2,6 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import {
CircleButtonTypes,
defaultDialogConfig,
getCurrentUser,
getParam,
IconButtonTypes,
ListingComponent,
listingProvidersFactory,
@ -21,6 +19,8 @@ import {
AddEditDossierAttributeDialogComponent,
AddEditDossierAttributeDialogData,
} from './add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({
templateUrl: './dossier-attributes-listing-screen.component.html',
@ -31,6 +31,7 @@ import {
}),
})
export class DossierAttributesListingScreenComponent extends ListingComponent<DossierAttributeConfig> implements OnInit {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = getCurrentUser<User>();
@ -42,7 +43,6 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
];
@ViewChild('impactedTemplates') impactedTemplatesRef: TemplateRef<unknown>;
readonly canEditDossierAttributes = this.permissionsService.canEditGlobalDossierAttributes();
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
readonly permissionsService: PermissionsService,

View File

@ -1,13 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import {
defaultDialogConfig,
getParam,
IconButtonTypes,
ListingComponent,
listingProvidersFactory,
SortingOrders,
TableColumnConfig,
} from '@iqser/common-ui';
import { defaultDialogConfig, IconButtonTypes, ListingComponent, listingProvidersFactory, TableColumnConfig } from '@iqser/common-ui';
import { type DonutChartConfig, DOSSIER_TEMPLATE_ID, type DossierState } from '@red/domain';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { firstValueFrom, Observable } from 'rxjs';
@ -20,6 +12,8 @@ import {
AddEditDossierStateDialogComponent,
AddEditDossierStateDialogData,
} from '../add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component';
import { SortingOrders } from '@iqser/common-ui/lib/sorting';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({
templateUrl: './dossier-states-listing-screen.component.html',
@ -27,6 +21,7 @@ import {
providers: listingProvidersFactory(DossierStatesListingScreenComponent),
})
export class DossierStatesListingScreenComponent extends ListingComponent<DossierState> implements OnInit, OnDestroy {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes;
readonly tableHeaderLabel = _('dossier-states-listing.table-header.title');
readonly tableColumnConfigs: TableColumnConfig<DossierState>[] = [
@ -35,7 +30,6 @@ export class DossierStatesListingScreenComponent extends ListingComponent<Dossie
{ label: _('dossier-states-listing.table-col-names.dossiers-count'), class: 'flex-center' },
];
readonly chartConfig$: Observable<DonutChartConfig[]>;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
private readonly _dialog: MatDialog,

View File

@ -4,7 +4,6 @@ import { AdminDialogService } from '../../../services/admin-dialog.service';
import { DossierTemplate, User } from '@red/domain';
import {
CircleButtonTypes,
getCurrentUser,
IconButtonTypes,
IqserPermissionsService,
ListingComponent,
@ -17,6 +16,7 @@ import { RouterHistoryService } from '@services/router-history.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { firstValueFrom } from 'rxjs';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
@Component({
templateUrl: './dossier-templates-listing-screen.component.html',

View File

@ -12,10 +12,10 @@ import {
IqserHelpModeModule,
IqserListingModule,
IqserRoutes,
IqserUsersModule,
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { DossierTemplateActionsComponent } from '../../shared/components/dossier-template-actions/dossier-template-actions.component';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
const routes: IqserRoutes = [{ path: '', component: DossierTemplatesListingScreenComponent }];

View File

@ -1,7 +1,6 @@
import { Component } from '@angular/core';
import {
CircleButtonTypes,
getParam,
IconButtonTypes,
ListingComponent,
listingProvidersFactory,
@ -17,6 +16,7 @@ import { DossierTemplateStatsService } from '@services/entity-services/dossier-t
import { tap } from 'rxjs/operators';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { PermissionsService } from '@services/permissions.service';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({
templateUrl: './entities-listing-screen.component.html',
@ -24,6 +24,7 @@ import { PermissionsService } from '@services/permissions.service';
providers: listingProvidersFactory(EntitiesListingScreenComponent),
})
export class EntitiesListingScreenComponent extends ListingComponent<Dictionary> {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly tableHeaderLabel = _('entities-listing.table-header.title');
@ -34,7 +35,6 @@ export class EntitiesListingScreenComponent extends ListingComponent<Dictionary>
{ label: _('entities-listing.table-col-names.dictionary-entries') },
];
readonly templateStats$: Observable<DossierTemplateStats>;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
private readonly _loadingService: LoadingService,

View File

@ -2,12 +2,14 @@ import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/
import { ActivatedRoute } from '@angular/router';
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { getCurrentUser, getParam, IqserPermissionsService, List, LoadingService } from '@iqser/common-ui';
import { IqserPermissionsService, LoadingService } from '@iqser/common-ui';
import { BehaviorSubject } from 'rxjs';
import { DICTIONARY_TO_ENTRY_TYPE_MAP, DICTIONARY_TYPE_KEY_MAP, DictionaryType, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { Roles } from '@users/roles';
import { NGXLogger } from 'ngx-logger';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { getParam, List } from '@iqser/common-ui/lib/utils';
@Component({
templateUrl: './dictionary-screen.component.html',
@ -15,15 +17,15 @@ import { NGXLogger } from 'ngx-logger';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DictionaryScreenComponent implements OnInit {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
@ViewChild('dictionaryManager', { static: false })
private readonly _dictionaryManager: DictionaryManagerComponent;
readonly currentUser = getCurrentUser<User>();
readonly roles = Roles;
readonly initialEntries$ = new BehaviorSubject<string[]>([]);
isLeavingPage = false;
readonly type: DictionaryType;
readonly entityType = getParam(ENTITY_TYPE);
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
@ViewChild('dictionaryManager', { static: false })
private readonly _dictionaryManager: DictionaryManagerComponent;
constructor(
route: ActivatedRoute,

View File

@ -5,9 +5,10 @@ import { ActivatedRoute } from '@angular/router';
import { getCurrentUser } from '@users/user.service';
import { PermissionsService } from '@services/permissions.service';
import { AddEditEntityComponent } from '@shared/components/add-edit-entity/add-edit-entity.component';
import { IconButtonTypes, IqserEventTarget } from '@iqser/common-ui';
import { IconButtonTypes } from '@iqser/common-ui';
import { Observable } from 'rxjs';
import { Roles } from '@users/roles';
import { IqserEventTarget } from '@iqser/common-ui/lib/utils';
@Component({
selector: 'redaction-entity-info',
@ -16,14 +17,13 @@ import { Roles } from '@users/roles';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EntityInfoComponent {
@ViewChild(AddEditEntityComponent) private readonly _addEditEntityComponent: AddEditEntityComponent;
readonly currentUser = getCurrentUser();
readonly entity$: Observable<Dictionary>;
readonly dossierTemplateId: string;
readonly roles = Roles;
readonly iconButtonTypes = IconButtonTypes;
@ViewChild(AddEditEntityComponent) private readonly _addEditEntityComponent: AddEditEntityComponent;
constructor(route: ActivatedRoute, dictionariesMapService: DictionariesMapService, readonly permissionsService: PermissionsService) {
this.dossierTemplateId = route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
const entityType = route.parent.snapshot.paramMap.get(ENTITY_TYPE);

View File

@ -4,11 +4,12 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as Papa from 'papaparse';
import { firstValueFrom, Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { IconButtonTypes, ListingComponent, listingProvidersFactory, TableColumnConfig, Toaster, trackByFactory } from '@iqser/common-ui';
import { IconButtonTypes, ListingComponent, listingProvidersFactory, TableColumnConfig, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileAttributeConfig, FileAttributeConfigTypes, FileAttributeEncodingTypes, IField, IFileAttributesConfig } from '@red/domain';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { fileAttributeEncodingTypesTranslations } from '@translations/file-attribute-encoding-types-translations';
import { trackByFactory } from '@iqser/common-ui/lib/utils';
export interface IFileAttributesCSVImportData {
readonly csv: File;

View File

@ -3,8 +3,6 @@ import { AdminDialogService } from '../../services/admin-dialog.service';
import {
CircleButtonTypes,
defaultDialogConfig,
getCurrentUser,
getParam,
IconButtonTypes,
largeDialogConfig,
ListingComponent,
@ -32,6 +30,8 @@ import {
IFileAttributesCSVImportData,
} from './file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component';
import { FileAttributesConfigurationsDialogComponent } from './file-attributes-configurations-dialog/file-attributes-configurations-dialog.component';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({
templateUrl: './file-attributes-listing-screen.component.html',
@ -39,6 +39,10 @@ import { FileAttributesConfigurationsDialogComponent } from './file-attributes-c
providers: listingProvidersFactory(FileAttributesListingScreenComponent),
})
export class FileAttributesListingScreenComponent extends ListingComponent<FileAttributeConfig> implements OnInit, OnDestroy {
@ViewChild('impactedTemplates') private readonly _impactedTemplatesRef: TemplateRef<unknown>;
#existingConfiguration: IFileAttributesConfig;
@ViewChild('fileInput') private _fileInput: ElementRef;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = getCurrentUser<User>();
@ -59,18 +63,6 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
},
];
readonly roles = Roles;
@ViewChild('impactedTemplates') private readonly _impactedTemplatesRef: TemplateRef<unknown>;
#existingConfiguration: IFileAttributesConfig;
@ViewChild('fileInput') private _fileInput: ElementRef;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
private get _numberOfDisplayedAttrs(): number {
return this.entitiesService.all.filter(attr => attr.displayedInFileList).length;
}
private get _numberOfFilterableAttrs(): number {
return this.entitiesService.all.filter(attr => attr.filterable).length;
}
constructor(
readonly permissionsService: PermissionsService,
@ -84,6 +76,14 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
super();
}
private get _numberOfDisplayedAttrs(): number {
return this.entitiesService.all.filter(attr => attr.displayedInFileList).length;
}
private get _numberOfFilterableAttrs(): number {
return this.entitiesService.all.filter(attr => attr.filterable).length;
}
async ngOnInit() {
await this.#loadData();
}

View File

@ -1,10 +1,11 @@
import { Component } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { UntypedFormBuilder, Validators } from '@angular/forms';
import { SystemPreferences } from '@red/domain';
import { BaseFormComponent, IqserPermissionsService, KeysOf, LoadingService } from '@iqser/common-ui';
import { BaseFormComponent, IqserPermissionsService, LoadingService } from '@iqser/common-ui';
import { SystemPreferencesService } from '@services/system-preferences.service';
import { systemPreferencesTranslations } from '@translations/system-preferences-translations';
import { Roles } from '@users/roles';
import { KeysOf } from '@iqser/common-ui/lib/utils';
export type ValueType = 'number' | 'string' | 'boolean';
@ -13,24 +14,24 @@ export type ValueType = 'number' | 'string' | 'boolean';
templateUrl: './system-preferences-form.component.html',
})
export class SystemPreferencesFormComponent extends BaseFormComponent {
#initialConfiguration: SystemPreferences;
readonly translations = systemPreferencesTranslations;
readonly keys: { name: KeysOf<SystemPreferences>; type: ValueType }[] = [
{ name: 'softDeleteCleanupTime', type: 'number' },
{ name: 'downloadCleanupNotDownloadFilesHours', type: 'number' },
{ name: 'downloadCleanupDownloadFilesHours', type: 'number' },
];
private _initialConfiguration: SystemPreferences;
constructor(
private readonly _loadingService: LoadingService,
private readonly _systemPreferencesService: SystemPreferencesService,
private readonly _formBuilder: UntypedFormBuilder,
private readonly _permissionsService: IqserPermissionsService,
permissionsService: IqserPermissionsService,
) {
super();
this.form = this._getForm();
this._loadData();
if (!_permissionsService.has(Roles.appConfiguration.write)) {
this.form = this.#getForm();
this.#loadData();
if (!permissionsService.has(Roles.appConfiguration.write)) {
this.form.disable();
}
}
@ -38,19 +39,21 @@ export class SystemPreferencesFormComponent extends BaseFormComponent {
async save(): Promise<void> {
this._loadingService.start();
await this._systemPreferencesService.update(this.form.getRawValue());
this._loadData();
this.#loadData();
this._loadingService.stop();
}
private _getForm(): UntypedFormGroup {
#getForm() {
const controlsConfig = {};
this.keys.forEach(key => (controlsConfig[key.name] = [this._systemPreferencesService.values[key.name], Validators.required]));
this.keys.forEach(key => {
controlsConfig[key.name] = [this._systemPreferencesService.values[key.name], Validators.required];
});
return this._formBuilder.group(controlsConfig);
}
private _loadData() {
this._initialConfiguration = this._systemPreferencesService.values;
this.form.patchValue(this._initialConfiguration, { emitEvent: false });
#loadData() {
this.#initialConfiguration = this._systemPreferencesService.values;
this.form.patchValue(this.#initialConfiguration, { emitEvent: false });
this.initialFormValue = this.form.getRawValue();
}
}

View File

@ -3,8 +3,9 @@ import { CommonModule } from '@angular/common';
import { DossierTemplateInfoScreenComponent } from './info-screen/dossier-template-info-screen.component';
import { RouterModule } from '@angular/router';
import { SharedModule } from '@shared/shared.module';
import { HasScrollbarDirective, IqserHelpModeModule, IqserUsersModule } from '@iqser/common-ui';
import { HasScrollbarDirective, IqserHelpModeModule } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
const routes = [{ path: '', component: DossierTemplateInfoScreenComponent }];

View File

@ -3,7 +3,7 @@ import { DossierTemplatesService } from '@services/dossier-templates/dossier-tem
import { DOSSIER_TEMPLATE_ID, type DossierTemplate, type DossierTemplateStats } from '@red/domain';
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
import { dossierTemplateStatusTranslations } from '@translations/dossier-template-status-translations';
import { ContextComponent, getParam } from '@iqser/common-ui';
import { ContextComponent, getParam } from '@iqser/common-ui/lib/utils';
interface Context {
readonly dossierTemplate: DossierTemplate;

View File

@ -2,12 +2,10 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import {
CircleButtonTypes,
getParam,
IconButtonTypes,
ListingComponent,
listingProvidersFactory,
LoadingService,
SortingOrders,
TableColumnConfig,
} from '@iqser/common-ui';
import { DOSSIER_TEMPLATE_ID, Justification } from '@red/domain';
@ -16,6 +14,8 @@ import { JustificationsDialogService } from '../justifications-dialog.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { firstValueFrom } from 'rxjs';
import { PermissionsService } from '@services/permissions.service';
import { SortingOrders } from '@iqser/common-ui/lib/sorting';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({
selector: 'redaction-justifications-screen',
@ -28,6 +28,7 @@ import { PermissionsService } from '@services/permissions.service';
}),
})
export class JustificationsScreenComponent extends ListingComponent<Justification> implements OnInit {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly tableHeaderLabel = _('justifications-listing.table-header');
@ -37,7 +38,6 @@ export class JustificationsScreenComponent extends ListingComponent<Justificatio
{ label: _('justifications-listing.table-col-names.description'), width: '2fr' },
];
readonly canAddEditJustifications = this.permissionsService.canAddEditJustifications();
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
private readonly _justificationService: JustificationsService,

View File

@ -1,8 +1,9 @@
import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core';
import { DOSSIER_TEMPLATE_ID, Justification } from '@red/domain';
import { CircleButtonTypes, getParam } from '@iqser/common-ui';
import { CircleButtonTypes } from '@iqser/common-ui';
import { JustificationsDialogService } from '../justifications-dialog.service';
import { PermissionsService } from '@services/permissions.service';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({
selector: 'redaction-table-item',
@ -10,10 +11,10 @@ import { PermissionsService } from '@services/permissions.service';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TableItemComponent {
readonly #dossierTemplateId: string = getParam(DOSSIER_TEMPLATE_ID);
readonly circleButtonTypes = CircleButtonTypes;
@Input() justification: Justification;
readonly canAddEditJustifications = inject(PermissionsService).canAddEditJustifications();
readonly #dossierTemplateId: string = getParam(DOSSIER_TEMPLATE_ID);
constructor(private readonly _dialogService: JustificationsDialogService) {}

View File

@ -1,12 +1,12 @@
import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { size } from '@iqser/common-ui';
import { size } from '@iqser/common-ui/lib/utils';
import { LicenseService } from '@services/license.service';
import { map, tap } from 'rxjs/operators';
import type { DonutChartConfig, ILicenseReport } from '@red/domain';
import { ChartDataset } from 'chart.js';
import { ChartBlue, ChartGreen, ChartGrey, ChartRed } from '../../utils/constants';
import { getLabelsFromMonthlyData, getLineConfig } from '../../utils/functions';
import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@Component({
@ -26,7 +26,7 @@ export class LicenseStorageComponent {
}),
map(license => ({
datasets: this.#getDatasets(license),
labels: getLabelsFromMonthlyData(license.monthlyData),
labels: getLabelsFromLicense(license),
})),
);
readonly size = size;

View File

@ -5,7 +5,7 @@ import { map } from 'rxjs/operators';
import type { ILicenseReport } from '@red/domain';
import { ChartDataset } from 'chart.js';
import { ChartBlue, ChartGreen, ChartRed } from '../../utils/constants';
import { getLabelsFromMonthlyData, getLineConfig } from '../../utils/functions';
import { getLabelsFromLicense, getLineConfig } from '../../utils/functions';
@Component({
selector: 'red-license-usage',
@ -18,7 +18,7 @@ export class LicenseUsageComponent {
map(() => this.licenseService.currentLicenseReport),
map(license => ({
datasets: this.#getDatasets(license),
labels: getLabelsFromMonthlyData(license.monthlyData),
labels: getLabelsFromLicense(license),
})),
);
@ -32,12 +32,14 @@ export class LicenseUsageComponent {
#getDatasets(license: ILicenseReport): ChartDataset[] {
const monthlyData = license.monthlyData;
return [
{
data: monthlyData.flatMap(d => d.numberOfAnalyzedPages),
label: 'Pages per Month',
type: 'bar',
backgroundColor: ChartBlue,
borderColor: ChartBlue,
order: 2,
},
{

View File

@ -1,12 +1,13 @@
import { Component } from '@angular/core';
import { ConfigService } from '@services/config.service';
import { TranslateService } from '@ngx-translate/core';
import { ButtonConfig, getCurrentUser, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
import { ButtonConfig, IconButtonTypes, IqserPermissionsService } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { RouterHistoryService } from '@services/router-history.service';
import { LicenseService } from '@services/license.service';
import { Roles } from '@users/roles';
import type { User } from '@red/domain';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
@Component({
templateUrl: './license-screen.component.html',

View File

@ -1,7 +1,7 @@
import dayjs, { Dayjs } from 'dayjs';
import { FillTarget } from 'chart.js';
import { hexToRgba } from '@utils/functions';
import { ILicenseData } from '@red/domain';
import { ILicenseData, ILicenseReport } from '@red/domain';
import { ComplexFillTarget } from 'chart.js/dist/types';
const monthNames = dayjs.monthsShort();
@ -30,3 +30,17 @@ export const getLineConfig: (
});
export const getLabelsFromMonthlyData = (monthlyData: ILicenseData[]) => monthlyData.map(data => verboseDate(dayjs(data.startDate)));
export const getLabelsFromLicense = (license: ILicenseReport) => {
let startMonth = dayjs(license.startDate);
const endMonth = dayjs(license.endDate);
const result = [];
while (startMonth.isBefore(endMonth)) {
result.push(verboseDate(startMonth));
startMonth = startMonth.add(1, 'month');
}
return result;
};

View File

@ -1,13 +1,5 @@
import { Component, OnDestroy } from '@angular/core';
import {
getCurrentUser,
IqserPermissionsService,
ListingComponent,
listingProvidersFactory,
LoadingService,
SortingOrders,
TableColumnConfig,
} from '@iqser/common-ui';
import { IqserPermissionsService, ListingComponent, listingProvidersFactory, LoadingService, TableColumnConfig } from '@iqser/common-ui';
import { PermissionsMapping, User } from '@red/domain';
import { ConfigService } from '../config.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -20,6 +12,8 @@ import { switchMap, tap } from 'rxjs/operators';
import { permissionsTranslations } from '@translations/permissions-translations';
import { RouterHistoryService } from '@services/router-history.service';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { SortingOrders } from '@iqser/common-ui/lib/sorting';
@Component({
templateUrl: './permissions-screen.component.html',
@ -27,6 +21,7 @@ import { Roles } from '@users/roles';
providers: listingProvidersFactory(PermissionsScreenComponent),
})
export class PermissionsScreenComponent extends ListingComponent<PermissionsMapping> implements OnDestroy {
readonly #subscription: Subscription = new Subscription();
readonly roles = Roles;
readonly currentUser = getCurrentUser<User>();
readonly translations = permissionsTranslations;
@ -34,7 +29,6 @@ export class PermissionsScreenComponent extends ListingComponent<PermissionsMapp
readonly tableHeaderLabel = _('permissions-screen.table-header.title');
readonly targetObject: string;
readonly mappedPermissions: string[];
readonly #subscription: Subscription = new Subscription();
constructor(
route: ActivatedRoute,

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { DOSSIER_TEMPLATE_ID, IPlaceholdersResponse, IReportTemplate, User } from '@red/domain';
import { download } from '@utils/file-download-utils';
import { getCurrentUser, getParam, IConfirmationDialogData, LoadingService, Toaster } from '@iqser/common-ui';
import { IConfirmationDialogData, LoadingService, Toaster } from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import {
generalPlaceholdersDescriptionsTranslations,
@ -13,6 +13,8 @@ import { AdminDialogService } from '../../../services/admin-dialog.service';
import { ReportTemplateService } from '@services/report-template.service';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { getParam } from '@iqser/common-ui/lib/utils';
interface Placeholder {
placeholder: string;
@ -30,12 +32,12 @@ const placeholderTypes: PlaceholderType[] = ['generalPlaceholders', 'fileAttribu
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReportsScreenComponent implements OnInit {
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly placeholders$ = new BehaviorSubject<Placeholder[]>([]);
readonly availableTemplates$ = new BehaviorSubject<IReportTemplate[]>([]);
readonly currentUser = getCurrentUser<User>();
readonly roles = Roles;
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
private readonly _reportTemplateService: ReportTemplateService,

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { Debounce, getParam, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { saveAs } from 'file-saver';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { RulesService } from '../../../services/rules.service';
@ -8,6 +8,7 @@ import { firstValueFrom } from 'rxjs';
import { DOSSIER_TEMPLATE_ID } from '@red/domain';
import { EditorThemeService } from '@services/editor-theme.service';
import { ComponentCanDeactivate } from '@guards/can-deactivate.guard';
import { Debounce, getParam } from '@iqser/common-ui/lib/utils';
import ICodeEditor = monaco.editor.ICodeEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
@ -18,6 +19,11 @@ import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorCon
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
@ViewChild('fileInput')
private _fileInput: ElementRef;
private _codeEditor: ICodeEditor;
private _decorations: string[] = [];
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes;
readonly editorOptions: IStandaloneEditorConstructionOptions = {
theme: 'vs',
@ -25,19 +31,10 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
automaticLayout: true,
readOnly: !this.permissionsService.canEditRules(),
};
initialLines: string[] = [];
currentLines: string[] = [];
isLeaving = false;
@ViewChild('fileInput')
private _fileInput: ElementRef;
private _codeEditor: ICodeEditor;
private _decorations: string[] = [];
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
readonly permissionsService: PermissionsService,
private readonly _rulesService: RulesService,

View File

@ -11,7 +11,6 @@ import {
ListingComponent,
listingProvidersFactory,
LoadingService,
NestedFilter,
SearchPositions,
TableColumnConfig,
} from '@iqser/common-ui';
@ -22,6 +21,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { userTypeChecker, userTypeFilters } from '../../../../utils';
import { RouterHistoryService } from '@services/router-history.service';
import { Roles } from '@users/roles';
import { NestedFilter } from '@iqser/common-ui/lib/filtering';
function configToFilter({ key, label }: DonutChartConfig) {
return new NestedFilter({

View File

@ -1,5 +1,5 @@
<div class="content-container">
<div #viewer class="viewer" id="viewer"></div>
<div #viewer class="viewer"></div>
<redaction-paginator (changePage)="navigateTo($event)" *ngIf="loaded$ | async"></redaction-paginator>
@ -54,30 +54,30 @@
<div class="iqser-input-group">
<label [translate]="'watermark-screen.form.alignment'" class="all-caps-label mb-8"></label>
<div class="flex">
<div class="alignment-buttons" [class.disabled]="form.controls.horizontalTextAlignment.disabled">
<div [class.disabled]="form.controls.horizontalTextAlignment.disabled" class="alignment-buttons">
<div
(click)="alignHorizontally(alignment)"
*ngFor="let alignment of watermarkHorizontalAlignments"
[class.active]="currentAlignment.horizontal === alignment"
[class.disabled]="form.controls.horizontalTextAlignment.disabled"
[matTooltipPosition]="'above'"
[matTooltip]="translations.HORIZONTAL[alignment] | translate"
[ngClass]="'horizontal-' + alignment.toLowerCase()"
[class.disabled]="form.controls.horizontalTextAlignment.disabled"
class="alignment"
(click)="alignHorizontally(alignment)"
>
<mat-icon [svgIcon]="'red:align-horizontal-' + alignment.toLowerCase()"></mat-icon>
</div>
</div>
<div class="alignment-buttons" [class.disabled]="form.controls.verticalTextAlignment.disabled">
<div [class.disabled]="form.controls.verticalTextAlignment.disabled" class="alignment-buttons">
<div
(click)="alignVertically(alignment)"
*ngFor="let alignment of watermarkVerticalAlignments"
[class.active]="currentAlignment.vertical === alignment"
[class.disabled]="form.controls.verticalTextAlignment.disabled"
[matTooltipPosition]="'above'"
[matTooltip]="translations.VERTICAL[alignment] | translate"
[ngClass]="'vertical-' + alignment.toLowerCase()"
[class.disabled]="form.controls.verticalTextAlignment.disabled"
class="alignment"
(click)="alignVertically(alignment)"
>
<mat-icon [svgIcon]="'red:align-vertical-' + alignment.toLowerCase()"></mat-icon>
</div>

View File

@ -2,20 +2,7 @@ import { ChangeDetectorRef, Component, ElementRef, inject, OnInit, ViewChild } f
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
import { HttpClient } from '@angular/common/http';
import { FormBuilder, FormGroup } from '@angular/forms';
import {
AsControl,
BASE_HREF_FN,
Debounce,
getConfig,
getCurrentUser,
getParam,
IconButtonTypes,
IqserPermissionsService,
LoadingService,
TenantsService,
Toaster,
trackByFactory,
} from '@iqser/common-ui';
import { getConfig, IconButtonTypes, IqserPermissionsService, LoadingService, Toaster } from '@iqser/common-ui';
import {
AppConfig,
DOSSIER_TEMPLATE_ID,
@ -42,6 +29,9 @@ import { Roles } from '@users/roles';
import { environment } from '@environments/environment';
import { tap } from 'rxjs/operators';
import { watermarkTranslations } from '@translations/watermark-translations';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { AsControl, BASE_HREF_FN, Debounce, getParam, trackByFactory } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
export const DEFAULT_WATERMARK: Partial<IWatermark> = {
text: 'Watermark',
@ -72,6 +62,13 @@ interface WatermarkForm {
styleUrls: ['./watermark-screen.component.scss'],
})
export class WatermarkScreenComponent implements OnInit {
@ViewChild('viewer', { static: true }) private readonly _viewer: ElementRef<HTMLDivElement>;
private readonly _convertPath = inject(BASE_HREF_FN);
readonly #loaded$ = new BehaviorSubject(false);
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly #watermarkId = Number(getParam(WATERMARK_ID));
readonly #config = getConfig<AppConfig>();
#watermark: Partial<IWatermark> = {};
readonly iconButtonTypes = IconButtonTypes;
readonly translations = watermarkTranslations;
readonly trackBy = trackByFactory();
@ -89,13 +86,6 @@ export class WatermarkScreenComponent implements OnInit {
readonly watermarkHorizontalAlignments = Object.values(WATERMARK_HORIZONTAL_ALIGNMENTS);
readonly watermarkVerticalAlignments = Object.values(WATERMARK_VERTICAL_ALIGNMENTS);
currentAlignment: WatermarkAlignment;
@ViewChild('viewer', { static: true }) private readonly _viewer: ElementRef<HTMLDivElement>;
private readonly _convertPath = inject(BASE_HREF_FN);
readonly #loaded$ = new BehaviorSubject(false);
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly #watermarkId = Number(getParam(WATERMARK_ID));
readonly #config = getConfig<AppConfig>();
#watermark: Partial<IWatermark> = {};
constructor(
private readonly _http: HttpClient,

View File

@ -9,12 +9,9 @@ import {
HasScrollbarDirective,
IconButtonComponent,
IqserAllowDirective,
IqserAuthGuard,
IqserHelpModeModule,
IqserListingModule,
IqserRoutes,
IqserUsersModule,
TenantPipe,
} from '@iqser/common-ui';
import { RedRoleGuard } from '@users/red-role.guard';
import { WATERMARK_ID } from '@red/domain';
@ -27,6 +24,8 @@ import { MatSliderModule } from '@angular/material/slider';
import { ColorPickerModule } from 'ngx-color-picker';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { IqserAuthGuard, IqserUsersModule } from '@iqser/common-ui/lib/users';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
const routes: IqserRoutes = [
{

View File

@ -1,8 +1,6 @@
import { Component } from '@angular/core';
import {
CircleButtonTypes,
getCurrentUser,
getParam,
IconButtonTypes,
IConfirmationDialogData,
IqserPermissionsService,
@ -18,6 +16,8 @@ import { WatermarkService } from '@services/entity-services/watermark.service';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { WatermarksMapService } from '@services/entity-services/watermarks-map.service';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({
templateUrl: './watermarks-listing-screen.component.html',
@ -25,6 +25,7 @@ import { Roles } from '@users/roles';
providers: listingProvidersFactory(WatermarksListingScreenComponent),
})
export class WatermarksListingScreenComponent extends ListingComponent<Watermark> {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = getCurrentUser<User>();
@ -37,7 +38,6 @@ export class WatermarksListingScreenComponent extends ListingComponent<Watermark
{ label: _('watermarks-listing.table-col-names.modified-on'), sortByKey: 'dateModified' },
];
readonly tableHeaderLabel = _('watermarks-listing.table-header.title');
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
constructor(
private readonly _loadingService: LoadingService,

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { filterEach, GenericService } from '@iqser/common-ui';
import { GenericService } from '@iqser/common-ui';
import { forkJoin, Observable, of } from 'rxjs';
import {
IDigitalSignatureRequest,
@ -9,6 +9,7 @@ import {
IPkcsDigitalSignatureRequest,
} from '@red/domain';
import { catchError, map } from 'rxjs/operators';
import { filterEach } from '@iqser/common-ui/lib/utils';
@Injectable()
export class DigitalSignatureService extends GenericService<IDigitalSignatureRequest> {

View File

@ -5,9 +5,12 @@ import { adminSideNavTranslations } from '@translations/admin-side-nav-translati
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
import { AdminSideNavType, AdminSideNavTypes, DOSSIER_TEMPLATE_ID, ENTITY_TYPE, User, WATERMARK_ID } from '@red/domain';
import { Roles } from '@users/roles';
import { getCurrentUser, IqserHelpModeModule, IqserPermissionsService, SideNavComponent, TenantPipe } from '@iqser/common-ui';
import { IqserHelpModeModule, IqserPermissionsService } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { NgForOf, NgIf } from '@angular/common';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { SideNavComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
interface NavItem {
readonly label: string;

View File

@ -1,19 +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,
TenantsService,
} from '@iqser/common-ui';
import { CircleButtonComponent, CircleButtonTypes, IqserHelpModeModule, LoadingService } 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';
import { NgIf } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
@Component({
selector: 'redaction-dossier-template-actions',

View File

@ -8,7 +8,7 @@ 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';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
@Component({
selector: 'redaction-dossier-template-breadcrumbs',

View File

@ -4,10 +4,11 @@ import { ArchivedDossiersScreenComponent } from './screens/archived-dossiers-scr
import { ArchiveRoutingModule } from './archive-routing.module';
import { TableItemComponent } from './components/table-item/table-item.component';
import { ConfigService } from './services/config.service';
import { IqserHelpModeModule, IqserListingModule, IqserUsersModule } from '@iqser/common-ui';
import { IqserHelpModeModule, IqserListingModule } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { SharedModule } from '@shared/shared.module';
import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
@NgModule({
declarations: [TableItemComponent, ArchivedDossiersScreenComponent],

View File

@ -1,11 +1,12 @@
import { Injectable } from '@angular/core';
import { IFilterGroup, keyChecker, NestedFilter, TableColumnConfig } from '@iqser/common-ui';
import { TableColumnConfig } from '@iqser/common-ui';
import { Dossier, User } from '@red/domain';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { dossierMemberChecker, dossierMemberQuickChecker, dossierOwnerQuickChecker, dossierTemplateChecker } from '@utils/index';
import { UserService } from '@users/user.service';
import { TranslateService } from '@ngx-translate/core';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { IFilterGroup, keyChecker, NestedFilter } from '@iqser/common-ui/lib/filtering';
@Injectable()
export class ConfigService {

View File

@ -1,8 +1,9 @@
import { Component, inject, OnInit } from '@angular/core';
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { getCurrentUser, trackByFactory } from '@iqser/common-ui';
import { trackByFactory } from '@iqser/common-ui/lib/utils';
import { User } from '@red/domain';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
@Component({
templateUrl: './dashboard-screen.component.html',

View File

@ -13,16 +13,7 @@ import {
} from '@red/domain';
import { TranslateChartService } from '@services/translate-chart.service';
import { UserService } from '@users/user.service';
import {
ContextComponent,
FilterService,
getCurrentUser,
getParam,
INestedFilter,
ProgressBarConfigModel,
shareLast,
Toaster,
} from '@iqser/common-ui';
import { ProgressBarConfigModel, Toaster } from '@iqser/common-ui';
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { combineLatestWith, firstValueFrom } from 'rxjs';
@ -31,6 +22,9 @@ import { map, tap } from 'rxjs/operators';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { FilesMapService } from '@services/files/files-map.service';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { FilterService, INestedFilter } from '@iqser/common-ui/lib/filtering';
import { ContextComponent, getParam, shareLast } from '@iqser/common-ui/lib/utils';
interface DossierDetailsContext {
needsWorkFilters: INestedFilter[] | undefined;
@ -45,17 +39,16 @@ interface DossierDetailsContext {
styleUrls: ['./dossier-details.component.scss'],
})
export class DossierDetailsComponent extends ContextComponent<DossierDetailsContext> {
#currentChartSubtitleIndex = 0;
readonly #dossierId = getParam(DOSSIER_ID);
@Input() dossierAttributes: DossierAttributeWithValue[];
@Output() readonly toggleCollapse = new EventEmitter();
editingOwner = false;
readonly roles = Roles;
readonly currentUser = getCurrentUser<User>();
readonly collapseTooltip = _('dossier-details.collapse');
readonly expandTooltip = _('dossier-details.expand');
chartConfig: DonutChartConfig[] = [];
#currentChartSubtitleIndex = 0;
readonly #dossierId = getParam(DOSSIER_ID);
constructor(
private readonly _toaster: Toaster,

View File

@ -1,8 +1,12 @@
<ng-container *ngIf="configService.listingMode$ | async as mode">
<div class="file-attribute" [ngClass]="{ 'workflow-attribute': mode === 'workflow' }" (click)="editFileAttribute($event)">
<div class="value" [ngClass]="{ 'workflow-value': mode === 'workflow' }">
<div (click)="editFileAttribute($event)" [ngClass]="{ 'workflow-attribute': mode === 'workflow' }" class="file-attribute">
<div [ngClass]="{ 'workflow-value': mode === 'workflow' }" class="value">
<b *ngIf="mode === 'workflow' && !isInEditMode"> {{ fileAttribute.label }}: </b>
<span *ngIf="!isDate; else date" [ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }">
<span
*ngIf="!isDate; else date"
[ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }"
[matTooltip]="fileAttributeValue"
>
{{ fileAttributeValue || '-' }}</span
>
<ng-template #date>
@ -14,20 +18,20 @@
<ng-container
*ngIf="
((fileAttributesService.isEditingFileAttribute$ | async) === false || isInEditMode) &&
(fileAttributesService.isEditingFileAttribute() === false || isInEditMode) &&
!file.isInitialProcessing &&
permissionsService.canEditFileAttributes(file, dossier)
"
>
<div
*ngIf="!isInEditMode; else input"
class="action-buttons edit-button"
[ngClass]="{ 'workflow-edit-button': mode === 'workflow' }"
[class.help-mode-button]="helpModeService.isHelpModeActive$ | async"
(click)="editFileAttribute($event)"
*ngIf="!isInEditMode; else input"
[attr.help-mode-key]="'edit-file-attributes'"
[class.help-mode-button]="helpModeService.isHelpModeActive$ | async"
[ngClass]="{ 'workflow-edit-button': mode === 'workflow' }"
class="action-buttons edit-button"
>
<div class="edit-icon" [ngClass]="{ 'workflow-edit-icon': mode === 'workflow' }">
<div [ngClass]="{ 'workflow-edit-icon': mode === 'workflow' }" class="edit-icon">
<mat-icon [svgIcon]="'iqser:edit'"></mat-icon>
</div>
</div>
@ -35,15 +39,15 @@
</div>
<ng-template #input>
<div class="edit-input" [ngClass]="{ 'workflow-edit-input': mode === 'workflow' }" stopPropagation>
<form (submit)="save()" [formGroup]="form">
<div [ngClass]="{ 'workflow-edit-input': mode === 'workflow' }" class="edit-input" iqserStopPropagation>
<form [formGroup]="form">
<iqser-dynamic-input
(closedDatepicker)="closedDatepicker = $event"
(keydown.escape)="close()"
[formControlName]="fileAttribute.id"
[id]="fileAttribute.id"
[type]="fileAttribute.type"
[ngClass]="{ 'workflow-input': mode === 'workflow' }"
[type]="fileAttribute.type"
></iqser-dynamic-input>
<iqser-circle-button
@ -54,7 +58,7 @@
class="save"
></iqser-circle-button>
<iqser-circle-button (action)="close()" [size]="mode === 'workflow' ? 15 : 34" [icon]="'iqser:close'"></iqser-circle-button>
<iqser-circle-button (action)="close()" [icon]="'iqser:close'" [size]="mode === 'workflow' ? 15 : 34"></iqser-circle-button>
</form>
</div>
</ng-template>

View File

@ -1,8 +1,8 @@
import { Component, HostListener, Input, OnDestroy } from '@angular/core';
import { Component, computed, effect, HostListener, Input, OnDestroy } from '@angular/core';
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
import { BaseFormComponent, HelpModeService, ListingService, Toaster } from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import { FormBuilder, UntypedFormGroup } from '@angular/forms';
import { FormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { firstValueFrom, Subscription } from 'rxjs';
import { FilesService } from '@services/files/files.service';
@ -25,6 +25,12 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
isInEditMode = false;
closedDatepicker = true;
readonly #subscriptions = new Subscription();
readonly #shouldClose = computed(
() =>
this.fileAttributesService.isEditingFileAttribute() &&
(this.fileAttribute.id !== this.fileAttributesService.openAttributeEdit() ||
this.file.fileId !== this.fileAttributesService.fileEdit()),
);
constructor(
router: Router,
@ -46,6 +52,15 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
tap(() => this.close()),
);
this.#subscriptions.add(sub2.subscribe());
effect(
() => {
if (this.#shouldClose()) {
this.close();
}
},
{ allowSignalWrites: true },
);
}
get isDate(): boolean {
@ -56,6 +71,13 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
}
@HostListener('document:click')
clickOutside() {
if (this.isInEditMode && this.closedDatepicker) {
this.close();
}
}
ngOnDestroy() {
this.#subscriptions.unsubscribe();
}
@ -64,6 +86,8 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
if (!this.file.isInitialProcessing && this.permissionsService.canEditFileAttributes(this.file, this.dossier)) {
$event.stopPropagation();
this.#toggleEdit();
this.fileAttributesService.setFileEdit(this.file.fileId);
this.fileAttributesService.setOpenAttributeEdit(this.fileAttribute.id);
}
}
@ -95,13 +119,6 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
}
}
@HostListener('document:click')
clickOutside() {
if (this.isInEditMode && this.closedDatepicker) {
this.close();
}
}
#initFileAttributes() {
const configs = this.fileAttributesService.getFileAttributeConfig(this.file.dossierTemplateId).fileAttributeConfigs;
configs.forEach(config => {
@ -116,13 +133,16 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
const fileAttributes = this.file.fileAttributes.attributeIdToValue;
Object.keys(fileAttributes).forEach(key => {
const attrValue = fileAttributes[key];
config[key] = [dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue];
config[key] = [
dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue,
Validators.pattern(/^(\s+\S+\s*)*(?!\s).*$/),
];
});
return this._formBuilder.group(config);
}
#formatAttributeValue(attrValue) {
return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue;
return this.isDate ? attrValue && dayjs(attrValue).format('YYYY-MM-DD') : attrValue.replaceAll(/\s\s+/g, ' ');
}
#toggleEdit(): void {
@ -133,7 +153,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
}
this.isInEditMode = !this.isInEditMode;
this.fileAttributesService.isEditingFileAttribute$.next(this.isInEditMode);
this.fileAttributesService.isEditingFileAttribute.set(this.isInEditMode);
if (this.isInEditMode) {
this.#focusOnEditInput();

View File

@ -1,16 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import {
ActionConfig,
CircleButtonTypes,
EntitiesService,
List,
ListingService,
LoadingService,
some,
SortingService,
TenantsService,
Toaster,
} from '@iqser/common-ui';
import { ActionConfig, CircleButtonTypes, EntitiesService, ListingService, LoadingService, Toaster } from '@iqser/common-ui';
import { Dossier, File, IFile } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -22,6 +11,9 @@ import { ConfigService } from '../../config.service';
import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service';
import { Router } from '@angular/router';
import { Roles } from '@users/roles';
import { SortingService } from '@iqser/common-ui/lib/sorting';
import { List, some } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
@Component({
selector: 'redaction-dossier-overview-screen-header [dossier] [upload]',

View File

@ -1,10 +1,10 @@
<div class="workflow-item" [class.help-mode-active]="helpModeService?.isHelpModeActive$ | async">
<div [class.help-mode-active]="helpModeService?.isHelpModeActive$ | async" class="workflow-item">
<div class="details-wrapper">
<div class="details">
<div
[attr.help-mode-key]="'workflow_view'"
[matTooltip]="file.filename"
[routerLink]="file.routerLink | tenant"
[attr.help-mode-key]="'workflow_view'"
class="filename pointer"
matTooltipPosition="above"
>
@ -20,7 +20,7 @@
</div>
<div *ngFor="let config of displayedAttributes" class="small-label mt-8 attribute">
<redaction-file-attribute [file]="file" [dossier]="dossier" [fileAttribute]="config"></redaction-file-attribute>
<redaction-file-attribute [dossier]="dossier" [fileAttribute]="config" [file]="file"></redaction-file-attribute>
</div>
<redaction-file-workload [file]="file"></redaction-file-workload>
@ -31,10 +31,11 @@
<div #actionsWrapper class="actions-wrapper">
<redaction-file-actions
*ngIf="!file.isProcessing"
[attr.help-mode-key]="'workflow_view'"
[dossier]="dossier"
[file]="file"
[maxWidth]="width"
[attr.help-mode-key]="'workflow_view'"
iqserDisableStopPropagation
type="dossier-overview-workflow"
></redaction-file-actions>
</div>

View File

@ -1,6 +1,7 @@
import { ChangeDetectorRef, Component, ElementRef, Input, OnInit, Optional, ViewChild } from '@angular/core';
import { Dossier, File, IFileAttributeConfig } from '@red/domain';
import { Debounce, HelpModeService } from '@iqser/common-ui';
import { HelpModeService } from '@iqser/common-ui';
import { Debounce } from '@iqser/common-ui/lib/utils';
@Component({
selector: 'redaction-workflow-item [file] [dossier] [displayedAttributes]',
@ -8,13 +9,12 @@ import { Debounce, HelpModeService } from '@iqser/common-ui';
styleUrls: ['./workflow-item.component.scss'],
})
export class WorkflowItemComponent implements OnInit {
@ViewChild('actionsWrapper', { static: true }) private _actionsWrapper: ElementRef;
@Input() file: File;
@Input() dossier: Dossier;
@Input() displayedAttributes: IFileAttributeConfig[];
width: number;
@ViewChild('actionsWrapper', { static: true }) private _actionsWrapper: ElementRef;
constructor(private readonly _changeRef: ChangeDetectorRef, @Optional() readonly helpModeService: HelpModeService) {}
ngOnInit(): void {

View File

@ -2,16 +2,9 @@ import { Injectable, TemplateRef } from '@angular/core';
import {
ActionConfig,
getConfig,
getCurrentUser,
getParam,
IFilterGroup,
INestedFilter,
IqserPermissionsService,
keyChecker,
List,
ListingMode,
ListingModes,
NestedFilter,
TableColumnConfig,
WorkflowColumn,
WorkflowConfig,
@ -46,6 +39,9 @@ import { DictionariesMapService } from '@services/entity-services/dictionaries-m
import { UserPreferenceService } from '@users/user-preference.service';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { IFilterGroup, INestedFilter, keyChecker, NestedFilter } from '@iqser/common-ui/lib/filtering';
import { getParam, List } from '@iqser/common-ui/lib/utils';
@Injectable()
export class ConfigService {

View File

@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import {
CircleButtonComponent,
DisableStopPropagationDirective,
DynamicInputComponent,
HasScrollbarDirective,
IqserAllowDirective,
@ -10,10 +11,7 @@ import {
IqserListingModule,
IqserLoadingModule,
IqserRoutes,
IqserUsersModule,
StatusBarComponent,
StopPropagationDirective,
TenantPipe,
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { DossierOverviewScreenComponent } from './screen/dossier-overview-screen.component';
@ -28,6 +26,9 @@ import { DossierOverviewScreenHeaderComponent } from './components/screen-header
import { ViewModeSelectionComponent } from './components/view-mode-selection/view-mode-selection.component';
import { FileAttributeComponent } from './components/file-attribute/file-attribute.component';
import { SharedModule } from '@shared/shared.module';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { StatusBarComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
const routes: IqserRoutes = [
{
@ -70,6 +71,7 @@ const routes: IqserRoutes = [
DynamicInputComponent,
IqserAllowDirective,
TenantPipe,
DisableStopPropagationDirective,
],
})
export class DossierOverviewModule {}

View File

@ -19,16 +19,11 @@ import {
CircleButtonTypes,
CustomError,
ErrorService,
getParam,
IqserPermissionsService,
ListingComponent,
ListingModes,
listingProvidersFactory,
LoadingService,
NestedFilter,
OnAttach,
OnDetach,
shareLast,
TableColumnConfig,
TableComponent,
WorkflowConfig,
@ -46,6 +41,8 @@ import { BulkActionsService } from '../services/bulk-actions.service';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider';
import { Roles } from '@users/roles';
import { NestedFilter } from '@iqser/common-ui/lib/filtering';
import { getParam, OnAttach, OnDetach, shareLast } from '@iqser/common-ui/lib/utils';
@Component({
templateUrl: './dossier-overview-screen.component.html',

View File

@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { DossierStats, StatusSorter } from '@red/domain';
import { List, StatusBarConfig } from '@iqser/common-ui';
import { List } from '@iqser/common-ui/lib/utils';
import { StatusBarConfig } from '@iqser/common-ui/lib/shared';
@Component({
selector: 'redaction-dossier-documents-status',

View File

@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
import { TranslateChartService } from '@services/translate-chart.service';
import { map } from 'rxjs/operators';
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
import { getParam } from '@iqser/common-ui';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({
selector: 'redaction-dossiers-listing-details',

View File

@ -1,13 +1,5 @@
import { Injectable, TemplateRef } from '@angular/core';
import {
ButtonConfig,
IFilterGroup,
INestedFilter,
keyChecker,
NestedFilter,
OverlappingElements,
TableColumnConfig,
} from '@iqser/common-ui';
import { ButtonConfig, TableColumnConfig } from '@iqser/common-ui';
import {
annotationDefaultColorConfig,
AnnotationShapeMap,
@ -35,6 +27,7 @@ import { DossierStatesMapService } from '@services/entity-services/dossier-state
import { PermissionsService } from '@services/permissions.service';
import { SharedDialogService } from '@shared/services/dialog.service';
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import { IFilterGroup, INestedFilter, keyChecker, NestedFilter } from '@iqser/common-ui/lib/filtering';
@Injectable()
export class ConfigService {

View File

@ -1,14 +1,6 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
CompositeRouteGuard,
HasScrollbarDirective,
IqserHelpModeModule,
IqserListingModule,
IqserRoutes,
IqserUsersModule,
StatusBarComponent,
} from '@iqser/common-ui';
import { CompositeRouteGuard, HasScrollbarDirective, IqserHelpModeModule, IqserListingModule, IqserRoutes } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { DossiersListingScreenComponent } from './screen/dossiers-listing-screen.component';
import { RouterModule } from '@angular/router';
@ -21,6 +13,8 @@ import { DossierWorkloadColumnComponent } from './components/dossier-workload-co
import { DossierDocumentsStatusComponent } from './components/dossier-documents-status/dossier-documents-status.component';
import { DossierFilesGuard } from '@guards/dossier-files-guard';
import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { StatusBarComponent } from '@iqser/common-ui/lib/shared';
const routes: IqserRoutes = [
{

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { Dossier, DOSSIER_TEMPLATE_ID, DossierTemplate } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { ButtonConfig, ListingComponent, listingProvidersFactory, OnAttach, TableComponent } from '@iqser/common-ui';
import { ButtonConfig, ListingComponent, listingProvidersFactory, TableComponent } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ConfigService } from '../config.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
@ -10,6 +10,7 @@ import { Router } from '@angular/router';
import { UserPreferenceService } from '@users/user-preference.service';
import { SharedDialogService } from '@shared/services/dialog.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { OnAttach } from '@iqser/common-ui/lib/utils';
@Component({
templateUrl: './dossiers-listing-screen.component.html',
@ -18,17 +19,17 @@ import { DossierTemplatesService } from '@services/dossier-templates/dossier-tem
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DossiersListingScreenComponent extends ListingComponent<Dossier> implements OnInit, OnAttach {
readonly tableColumnConfigs = this._configService.tableConfig;
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
readonly buttonConfigs: ButtonConfig[];
readonly dossierTemplate: DossierTemplate;
readonly computeFilters$ = this._activeDossiersService.all$.pipe(tap(() => this._computeAllFilters()));
@ViewChild('needsWorkFilterTemplate', {
read: TemplateRef,
static: true,
})
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
readonly tableColumnConfigs = this._configService.tableConfig;
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
readonly buttonConfigs: ButtonConfig[];
readonly dossierTemplate: DossierTemplate;
readonly computeFilters$ = this._activeDossiersService.all$.pipe(tap(() => this._computeAllFilters()));
constructor(
router: Router,

View File

@ -3,9 +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 { KeysOf } from '@iqser/common-ui';
import { ListItem } from '@models/file/list-item';
import { MultiSelectService } from '../../services/multi-select.service';
import { KeysOf } from '@iqser/common-ui/lib/utils';
interface Engine {
readonly icon: string;
@ -41,16 +41,14 @@ const changesProperties: KeysOf<AnnotationWrapper>[] = [
styleUrls: ['./annotation-details.component.scss'],
})
export class AnnotationDetailsComponent implements OnChanges {
private readonly _translateService = inject(TranslateService);
private readonly _multiSelectService = inject(MultiSelectService);
@Input() annotation: ListItem<AnnotationWrapper>;
isPopoverOpen = false;
engines: Engine[];
changesTooltip: string;
noSelection: boolean;
private readonly _translateService = inject(TranslateService);
private readonly _multiSelectService = inject(MultiSelectService);
getChangesTooltip(): string | undefined {
const changes = changesProperties.filter(key => this.annotation.item[key]);

View File

@ -13,8 +13,8 @@
(click)="comments.toggleExpandComments()"
[matTooltip]="'comments.comments' | translate : { count: annotation.item.comments?.length }"
class="comments-counter"
iqserStopPropagation
matTooltipPosition="above"
stopPropagation
>
<mat-icon svgIcon="red:comment"></mat-icon>
{{ annotation.item.comments.length }}

View File

@ -1,6 +1,6 @@
import { ChangeDetectorRef, Component, computed, ElementRef, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { FilterService, HasScrollbarDirective, IqserEventTarget } from '@iqser/common-ui';
import { HasScrollbarDirective } from '@iqser/common-ui';
import { MultiSelectService } from '../../services/multi-select.service';
import { AnnotationReferencesService } from '../../services/annotation-references.service';
import { UserPreferenceService } from '@users/user-preference.service';
@ -9,6 +9,8 @@ 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';
import { FilterService } from '@iqser/common-ui/lib/filtering';
import { IqserEventTarget } from '@iqser/common-ui/lib/utils';
@Component({
selector: 'redaction-annotations-list',
@ -16,15 +18,14 @@ import { ListItem } from '@models/file/list-item';
styleUrls: ['./annotations-list.component.scss'],
})
export class AnnotationsListComponent extends HasScrollbarDirective implements OnChanges {
@Input() annotations: ListItem<AnnotationWrapper>[];
@Output() readonly pagesPanelActive = new EventEmitter<boolean>();
readonly #earmarkGroups = computed(() => {
if (this._viewModeService.isEarmarks()) {
return this.#getEarmarksGroups();
}
return [] as EarmarkGroup[];
});
@Input() annotations: ListItem<AnnotationWrapper>[];
@Output() readonly pagesPanelActive = new EventEmitter<boolean>();
constructor(
protected readonly _elementRef: ElementRef,

View File

@ -33,7 +33,7 @@
<div
(click)="toggleExpandComments()"
class="all-caps-label pointer hide-comments"
stopPropagation
iqserStopPropagation
translate="comments.hide-comments"
></div>
</ng-container>

View File

@ -2,12 +2,14 @@ import { ChangeDetectorRef, Component, HostBinding, Input, OnInit, ViewChild } f
import type { IComment, User } from '@red/domain';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { PermissionsService } from '@services/permissions.service';
import { ContextComponent, getCurrentUser, InputWithActionComponent, LoadingService, trackByFactory } from '@iqser/common-ui';
import { InputWithActionComponent, LoadingService } from '@iqser/common-ui';
import { Observable } from 'rxjs';
import { CommentingService } from '../../services/commenting.service';
import { tap } from 'rxjs/operators';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { ManualRedactionService } from '../../services/manual-redaction.service';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { ContextComponent, trackByFactory } from '@iqser/common-ui/lib/utils';
interface CommentsContext {
hiddenComments: boolean;
@ -19,12 +21,12 @@ interface CommentsContext {
styleUrls: ['./comments.component.scss'],
})
export class CommentsComponent extends ContextComponent<CommentsContext> implements OnInit {
@HostBinding('class.hidden') private _hidden = true;
@ViewChild(InputWithActionComponent) private readonly _input: InputWithActionComponent;
@Input() annotation: AnnotationWrapper;
readonly trackBy = trackByFactory();
readonly currentUser = getCurrentUser<User>();
hiddenComments$: Observable<boolean>;
@HostBinding('class.hidden') private _hidden = true;
@ViewChild(InputWithActionComponent) private readonly _input: InputWithActionComponent;
constructor(
readonly permissionsService: PermissionsService,

View File

@ -6,7 +6,7 @@ import { FilePreviewStateService } from '../../services/file-preview-state.servi
import { type FileAttributeConfigType, FileAttributeConfigTypes } from '@red/domain';
import { FilePreviewDialogService } from '../../services/file-preview-dialog.service';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { ContextComponent } from '@iqser/common-ui';
import { ContextComponent } from '@iqser/common-ui/lib/utils';
import { toSignal } from '@angular/core/rxjs-interop';
interface FileAttribute {

View File

@ -29,6 +29,7 @@
*ngIf="documentInfoService.hidden()"
[actionsTemplate]="annotationFilterActionTemplate"
[attr.help-mode-key]="'workload_in_editor'"
[fileId]="state.file()?.id"
[primaryFiltersSlug]="'primaryFilters'"
[secondaryFiltersSlug]="'secondaryFilters'"
></iqser-popup-filter>
@ -216,6 +217,6 @@
*ngIf="filter.id === 'skipped'"
[icon]="skippedService.hideSkipped() ? 'red:visibility-off' : 'red:visibility'"
[type]="circleButtonTypes.dark"
preventDefault
iqserPreventDefault
></iqser-circle-button>
</ng-template>

View File

@ -1,18 +1,9 @@
import { ChangeDetectorRef, Component, computed, effect, ElementRef, HostListener, OnDestroy, ViewChild } from '@angular/core';
import { ChangeDetectorRef, Component, computed, effect, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { MatDialog } from '@angular/material/dialog';
import scrollIntoView from 'scroll-into-view-if-needed';
import {
AutoUnsubscribe,
bool,
CircleButtonTypes,
Debounce,
FilterService,
IconButtonTypes,
INestedFilter,
IqserEventTarget,
} from '@iqser/common-ui';
import { CircleButtonTypes, IconButtonTypes } from '@iqser/common-ui';
import { combineLatest, delay, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ExcludedPagesService } from '../../services/excluded-pages.service';
@ -30,6 +21,9 @@ import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.
import { SuggestionsService } from '../../services/suggestions.service';
import { ListItem } from '@models/file/list-item';
import { PageRotationService } from '../../../pdf-viewer/services/page-rotation.service';
import { getLocalStorageDataByFileId } from '@utils/local-storage';
import { FilterService, INestedFilter } from '@iqser/common-ui/lib/filtering';
import { AutoUnsubscribe, bool, Debounce, IqserEventTarget } from '@iqser/common-ui/lib/utils';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
@ -39,10 +33,11 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
templateUrl: './file-workload.component.html',
styleUrls: ['./file-workload.component.scss'],
})
export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy {
export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
displayedAnnotations = new Map<number, AnnotationWrapper[]>();
displayedPages: number[] = [];
pagesPanelActive = true;
@ -51,8 +46,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
this.viewModeService.isEarmarks() ? _('file-preview.tabs.highlights.label') : _('file-preview.tabs.annotations.label'),
);
readonly currentPageIsExcluded = computed(() => this.state.file().excludedPages.includes(this.pdf.currentPage()));
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
constructor(
readonly filterService: FilterService,
@ -155,6 +148,20 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy
}
}
ngOnInit(): void {
setTimeout(() => {
const showExcludePages = getLocalStorageDataByFileId(this.state.file()?.id, 'show-exclude-pages') ?? false;
if (showExcludePages) {
this.excludedPagesService.show();
}
const showDocumentInfo = getLocalStorageDataByFileId(this.state.file()?.id, 'show-document-info') ?? false;
if (showDocumentInfo) {
this.documentInfoService.show();
}
});
}
selectAllOnActivePage() {
this.listingService.selectAnnotations(this.activeAnnotations);
}

View File

@ -3,12 +3,13 @@ import { PermissionsService } from '@services/permissions.service';
import { ViewedPagesService } from '@services/files/viewed-pages.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { PageRotationService } from '../../../pdf-viewer/services/page-rotation.service';
import { ContextComponent, getConfig } from '@iqser/common-ui';
import { getConfig } from '@iqser/common-ui';
import { map, tap } from 'rxjs/operators';
import { AppConfig, ViewedPage } from '@red/domain';
import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service';
import { pairwise } from 'rxjs';
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
import { ContextComponent } from '@iqser/common-ui/lib/utils';
interface PageIndicatorContext {
isRotated: boolean;
@ -21,14 +22,13 @@ interface PageIndicatorContext {
styleUrls: ['./page-indicator.component.scss'],
})
export class PageIndicatorComponent extends ContextComponent<PageIndicatorContext> implements OnChanges, OnInit {
readonly #config = getConfig<AppConfig>();
@Input({ required: true }) number: number;
@Input() showDottedIcon = false;
@Input() activeSelection = false;
@Input() read = false;
@Output() readonly pageSelected = new EventEmitter<number>();
pageReadTimeout: number = null;
readonly #config = getConfig<AppConfig>();
constructor(
private readonly _viewedPagesService: ViewedPagesService,

View File

@ -1,5 +1,5 @@
import { Component, inject, Input } from '@angular/core';
import { List } from '@iqser/common-ui';
import { List } from '@iqser/common-ui/lib/utils';
import { PdfViewer } from '../../../pdf-viewer/services/pdf-viewer.service';
import { MultiSelectService } from '../../services/multi-select.service';
import { AnnotationsListingService } from '../../services/annotations-listing.service';
@ -14,13 +14,13 @@ import { ViewedPage } from '@red/domain';
styleUrls: ['./pages.component.scss'],
})
export class PagesComponent {
@Input({ required: true }) pages: List<number>;
@Input({ required: true }) displayedAnnotations: Map<number, AnnotationWrapper[]>;
protected readonly _pdf = inject(PdfViewer);
readonly #state = inject(FilePreviewStateService);
readonly viewedPages$ = inject(ViewedPagesMapService).get$(this.#state.fileId);
readonly #multiSelectService = inject(MultiSelectService);
readonly #listingService = inject(AnnotationsListingService);
protected readonly _pdf = inject(PdfViewer);
@Input({ required: true }) pages: List<number>;
@Input({ required: true }) displayedAnnotations: Map<number, AnnotationWrapper[]>;
readonly viewedPages$ = inject(ViewedPagesMapService).get$(this.#state.fileId);
pageSelectedByClick($event: number): void {
this._pdf.navigateTo($event);

View File

@ -1,6 +1,6 @@
import { Component, computed } from '@angular/core';
import { File, User } from '@red/domain';
import { getCurrentUser, LoadingService, Toaster } from '@iqser/common-ui';
import { LoadingService, Toaster } from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -9,6 +9,7 @@ import { FilesService } from '@services/files/files.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { FileAssignService } from '../../../shared-dossiers/services/file-assign.service';
import { moveElementInArray } from '@utils/functions';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
@Component({
selector: 'redaction-user-management',
@ -16,17 +17,6 @@ import { moveElementInArray } from '@utils/functions';
styleUrls: ['./user-management.component.scss'],
})
export class UserManagementComponent {
readonly translations = workflowFileStatusTranslations;
readonly statusBarConfig = computed(() => [{ length: 1, color: this.state.file().workflowStatus }]);
readonly assignTooltip = computed(() => {
const file = this.state.file();
return file.isUnderApproval
? _('dossier-overview.assign-approver')
: file.assignee
? _('file-preview.change-reviewer')
: _('file-preview.assign-reviewer');
});
editingReviewer = false;
protected readonly _canAssignToSelf = computed(() => this.permissionsService.canAssignToSelf(this.state.file(), this.state.dossier()));
protected readonly _canAssignUser = computed(() => this.permissionsService.canAssignUser(this.state.file(), this.state.dossier()));
protected readonly _canUnassignUser = computed(() => this.permissionsService.canUnassignUser(this.state.file(), this.state.dossier()));
@ -45,6 +35,17 @@ export class UserManagementComponent {
: this.#customSort([...dossier.memberIds, ...unassignUser]);
});
protected readonly _currentUserId = getCurrentUser().id;
readonly translations = workflowFileStatusTranslations;
readonly statusBarConfig = computed(() => [{ length: 1, color: this.state.file().workflowStatus }]);
readonly assignTooltip = computed(() => {
const file = this.state.file();
return file.isUnderApproval
? _('dossier-overview.assign-approver')
: file.assignee
? _('file-preview.change-reviewer')
: _('file-preview.assign-reviewer');
});
editingReviewer = false;
constructor(
readonly fileAssignService: FileAssignService,

View File

@ -3,11 +3,12 @@ import { ViewMode, ViewModes } from '@red/domain';
import { ViewModeService } from '../../services/view-mode.service';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { FileDataService } from '../../services/file-data.service';
import { BASE_HREF, ConfirmOptions, IConfirmationDialogData, Toaster } from '@iqser/common-ui';
import { ConfirmOptions, IConfirmationDialogData, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserPreferenceService } from '@users/user-preference.service';
import { FilePreviewDialogService } from '../../services/file-preview-dialog.service';
import { Roles } from '@users/roles';
import { BASE_HREF } from '@iqser/common-ui/lib/utils';
@Component({
selector: 'redaction-view-switch',

View File

@ -1,6 +1,6 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
<div [translate]="!hint ? 'redact-text.dialog.title' : 'redact-text.dialog.hint-title'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="iqser-input-group w-450">
@ -42,7 +42,7 @@
</ng-container>
<ng-container *deny="roles.getRss; if: dictionaryRequest || type === 'HINT'">
<div class="iqser-input-group required w-450">
<div class="iqser-input-group required w-450" *ngIf="dictionaryRequest">
<label [translate]="'redact-text.dialog.content.type'"></label>
<mat-form-field>
@ -61,7 +61,7 @@
</div>
</ng-container>
<div class="iqser-input-group w-450">
<div *ngIf="!dictionaryRequest" class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.comment'"></label>
<textarea
formControlName="comment"

View File

@ -28,6 +28,7 @@ interface RedactTextData {
dossierId: string;
file: File;
applyToAllDossiers: boolean;
isApprover: boolean;
}
interface DialogResult {
@ -58,7 +59,7 @@ export class RedactTextDialogComponent
readonly #translations = redactTextTranslations;
readonly #dossier: Dossier;
readonly #hint: boolean;
readonly hint: boolean;
constructor(
private readonly _justificationsService: JustificationsService,
@ -70,7 +71,7 @@ export class RedactTextDialogComponent
super();
this.#dossier = _activeDossiersService.find(this.data.dossierId);
this.type = this.data.manualRedactionEntryWrapper.type;
this.#hint = this.type === ManualRedactionEntryTypes.HINT;
this.hint = this.type === ManualRedactionEntryTypes.HINT;
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
this.#manualRedactionTypeExists = this._dictionaryService.hasManualType(this.#dossier.dossierTemplateId);
this.options = this.#options();
@ -98,14 +99,18 @@ export class RedactTextDialogComponent
}
get disabled() {
if (this.dictionaryRequest || this.#hint) {
if (this.dictionaryRequest || this.hint) {
return !this.form.get('dictionary').value;
}
return !this.form.get('reason').value;
}
async ngOnInit(): Promise<void> {
this.dictionaries = this._dictionaryService.getPossibleDictionaries(this.#dossier.dossierTemplateId, this.#hint);
this.dictionaries = this._dictionaryService.getPossibleDictionaries(
this.#dossier.dossierTemplateId,
this.hint,
!this.#applyToAllDossiers,
);
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
this.legalOptions = data.map(lbm => ({
@ -121,6 +126,19 @@ export class RedactTextDialogComponent
extraOptionChanged(option: DetailsRadioOption<RedactTextOption>): void {
this.#applyToAllDossiers = option.extraOption.checked;
this.dictionaries = this._dictionaryService.getPossibleDictionaries(
this.#dossier.dossierTemplateId,
this.hint,
!this.#applyToAllDossiers,
);
if (this.#applyToAllDossiers && this.form.get('dictionary').value) {
const selectedDictionaryLabel = this.form.get('dictionary').value;
const selectedDictionary = this.dictionaries.find(d => d.type === selectedDictionaryLabel);
if (!selectedDictionary) {
this.form.get('dictionary').setValue(null);
}
}
}
save(): void {
@ -185,6 +203,7 @@ export class RedactTextDialogComponent
addRedactionRequest.value = addRedactionRequest.rectangle
? this.form.get('classification').value
: this.form.get('selectedText').value;
addRedactionRequest.addToAllDossiers = this.data.isApprover && this.dictionaryRequest && this.#applyToAllDossiers;
}
#options() {
@ -206,6 +225,7 @@ export class RedactTextDialogComponent
extraOption: {
label: this.#translations[this.type].inDossier.extraOptionLabel,
checked: this.data.applyToAllDossiers ?? true,
hidden: !this.data.isApprover,
},
});
}

View File

@ -9,6 +9,7 @@ import { IqserDialogComponent } from '../../../../../../../../libs/common-ui/src
import { PermissionsService } from '@services/permissions.service';
import { tap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { RedactTextOption } from '../redact-text-dialog/redact-text-options';
const PIN_ICON = 'red:push-pin';
const FOLDER_ICON = 'red:folder';
@ -25,6 +26,8 @@ export interface RemoveRedactionData {
dossier: Dossier;
falsePositiveContext: string;
permissions: RemoveRedactionPermissions;
applyToAllDossiers: boolean;
isApprover: boolean;
}
export interface RemoveRedactionResult {
@ -50,6 +53,7 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
readonly #redaction: AnnotationWrapper;
readonly #permissions: RemoveRedactionPermissions;
readonly #translations = removeRedactionTranslations;
#applyToAllDossiers: boolean;
constructor(private readonly _formBuilder: FormBuilder, private readonly _permissionsService: PermissionsService) {
super();
@ -57,13 +61,17 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
this.#permissions = this.data.permissions;
this.options = this.#options();
this.form = this.#getForm();
this.#applyToAllDossiers = this.data.applyToAllDossiers ?? true;
this.form
.get('option')
.valueChanges.pipe(
tap(() => {
this.options[1].extraOption.checked = true;
this.options[2].extraOption.checked = true;
for (const option of this.options) {
if (option.extraOption) {
option.extraOption.checked = this.#applyToAllDossiers;
}
}
}),
takeUntilDestroyed(),
)
@ -101,7 +109,8 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
value: RemoveRedactionOptions.IN_DOSSIER,
extraOption: {
label: this.#translations.IN_DOSSIER.extraOptionLabel,
checked: true,
checked: this.data.applyToAllDossiers ?? true,
hidden: !this.data.isApprover,
},
});
}
@ -114,7 +123,8 @@ export class RemoveRedactionDialogComponent extends IqserDialogComponent<
value: RemoveRedactionOptions.FALSE_POSITIVE,
extraOption: {
label: this.#translations.FALSE_POSITIVE.extraOptionLabel,
checked: true,
checked: this.data.applyToAllDossiers ?? true,
hidden: !this.data.isApprover,
},
});
}

View File

@ -7,7 +7,7 @@ import { SkippedService } from './services/skipped.service';
import { AnnotationActionsService } from './services/annotation-actions.service';
import { FilePreviewStateService } from './services/file-preview-state.service';
import { AnnotationReferencesService } from './services/annotation-references.service';
import { EntitiesService, FilterService, ListingService, SearchService, SortingService } from '@iqser/common-ui';
import { EntitiesService, ListingService, SearchService } from '@iqser/common-ui';
import { AnnotationProcessingService } from './services/annotation-processing.service';
import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider';
import { FileDataService } from './services/file-data.service';
@ -15,6 +15,8 @@ import { AnnotationsListingService } from './services/annotations-listing.servic
import { StampService } from './services/stamp.service';
import { PdfProxyService } from './services/pdf-proxy.service';
import { PdfAnnotationActionsService } from './services/pdf-annotation-actions.service';
import { FilterService } from '@iqser/common-ui/lib/filtering';
import { SortingService } from '@iqser/common-ui/lib/sorting';
export const filePreviewScreenProviders = [
FilterService,

View File

@ -11,27 +11,17 @@ import {
TemplateRef,
ViewChild,
} from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import { ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import {
AutoUnsubscribe,
Bind,
bool,
CircleButtonTypes,
ConfirmOption,
ConfirmOptions,
CustomError,
Debounce,
ErrorService,
FilterService,
HelpModeService,
IConfirmationDialogData,
List,
IqserDialog,
LoadingService,
NestedFilter,
OnAttach,
OnDetach,
processFilters,
TenantsService,
Toaster,
} from '@iqser/common-ui';
import { MatDialog } from '@angular/material/dialog';
@ -41,7 +31,7 @@ import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.se
import { AnnotationProcessingService } from './services/annotation-processing.service';
import { Dictionary, File, ViewModes } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { combineLatest, firstValueFrom, of, pairwise } from 'rxjs';
import { combineLatest, first, firstValueFrom, of, pairwise } from 'rxjs';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
import { byId, byPage, download, handleFilterDelta, hasChanges } from '../../utils';
import { FilesService } from '@services/files/files.service';
@ -74,9 +64,11 @@ import { ConfigService } from '@services/config.service';
import { ReadableRedactionsService } from '../pdf-viewer/services/readable-redactions.service';
import { Roles } from '@users/roles';
import { SuggestionsService } from './services/suggestions.service';
import { IqserDialog } from '../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service';
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { copyLocalStorageFiltersValues, FilterService, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering';
import { AutoUnsubscribe, Bind, bool, Debounce, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
import { TenantsService } from '@iqser/common-ui/lib/tenants';
const textActions = [TextPopups.REDACT_TEXT, TextPopups.ADD_HINT, TextPopups.ADD_FALSE_POSITIVE];
@ -89,18 +81,18 @@ export class FilePreviewScreenComponent
extends AutoUnsubscribe
implements AfterViewInit, OnInit, OnDestroy, OnAttach, OnDetach, ComponentCanDeactivate
{
readonly circleButtonTypes = CircleButtonTypes;
readonly roles = Roles;
fullScreen = false;
readonly fileId = this.state.fileId;
readonly dossierId = this.state.dossierId;
width: number;
@ViewChild('annotationFilterTemplate', {
read: TemplateRef,
static: false,
})
private readonly _filterTemplate: TemplateRef<unknown>;
@ViewChild('actionsWrapper', { static: false }) private readonly _actionsWrapper: ElementRef;
readonly circleButtonTypes = CircleButtonTypes;
readonly roles = Roles;
fullScreen = false;
readonly fileId = this.state.fileId;
readonly dossierId = this.state.dossierId;
width: number;
constructor(
readonly pdf: PdfViewer,
@ -116,7 +108,6 @@ export class FilePreviewScreenComponent
private readonly _annotationManager: REDAnnotationManager,
private readonly _errorService: ErrorService,
private readonly _filterService: FilterService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _loadingService: LoadingService,
private readonly _filesMapService: FilesMapService,
private readonly _dossiersService: DossiersService,
@ -167,18 +158,6 @@ export class FilePreviewScreenComponent
}
});
effect(() => {
const selectedText = this._documentViewer.selectedText();
const canPerformActions = this.pdfProxyService.canPerformActions();
const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage());
if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) {
this.pdf.enable(textActions);
} else {
this.pdf.disable(textActions);
}
});
effect(() => {
if (this._viewModeService.viewMode()) {
this.updateViewMode().then();
@ -329,13 +308,14 @@ export class FilePreviewScreenComponent
this._loadingService.start();
this.userPreferenceService.saveLastOpenedFileForDossier(this.dossierId, this.fileId).then();
this._subscribeToFileUpdates();
this.#subscribeToFileUpdates();
if (file?.analysisRequired && !file.excludedFromAutomaticAnalysis) {
await this._reanalysisService.reanalyzeFilesForDossier([file], this.dossierId, { force: true });
}
this.pdfProxyService.configureElements();
this.#restoreOldFilters();
document.documentElement.addEventListener('fullscreenchange', this.fullscreenListener);
}
@ -374,34 +354,35 @@ export class FilePreviewScreenComponent
async openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
const file = this.state.file();
const dossierTemplate = this._dossierTemplatesService.find(this.state.dossierTemplateId);
const result = await this._iqserDialog
.openDefault(RedactTextDialogComponent, {
data: {
manualRedactionEntryWrapper,
dossierId: this.dossierId,
file,
applyToAllDossiers: dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault,
},
})
.result();
const isApprover = this.permissionsService.isApprover(this.state.dossier());
const ref = this._iqserDialog.openDefault(RedactTextDialogComponent, {
data: {
manualRedactionEntryWrapper,
dossierId: this.dossierId,
file,
applyToAllDossiers: isApprover ? dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault : false,
isApprover,
},
});
if (result) {
const add$ = this._manualRedactionService.addAnnotation(
[result.redaction],
this.dossierId,
this.fileId,
result.dictionary?.label,
);
if (result.applyToAllDossiers !== null) {
const { ...body } = dossierTemplate;
body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers;
await this._dossierTemplatesService.createOrUpdate(body);
}
const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file)));
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
const result = await ref.result();
if (!result) {
return;
}
const add$ = this._manualRedactionService.addAnnotation([result.redaction], this.dossierId, this.fileId, result.dictionary?.label);
if (isApprover && result.applyToAllDossiers !== null) {
const { ...body } = dossierTemplate;
body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers;
await this._dossierTemplatesService.createOrUpdate(body);
}
const addAndReload$ = add$.pipe(
tap(() => this._documentViewer.clearSelection()),
switchMap(() => this._filesService.reload(this.dossierId, file)),
);
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
}
toggleFullScreen() {
@ -630,7 +611,7 @@ export class FilePreviewScreenComponent
}, 100);
}
private _subscribeToFileUpdates(): void {
#subscribeToFileUpdates(): void {
this.addActiveScreenSubscription = this.loadAnnotations().subscribe();
this.addActiveScreenSubscription = this._dossiersService
@ -662,7 +643,7 @@ export class FilePreviewScreenComponent
});
this.addActiveScreenSubscription = this.pdfProxyService.redactTextRequested$.subscribe($event => {
this.openRedactTextDialog($event);
this.openRedactTextDialog($event).then();
});
this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page =>
@ -741,6 +722,20 @@ export class FilePreviewScreenComponent
),
)
.subscribe();
const selectedTextEffect = this._documentViewer.selectedText$.pipe(
tap(selectedText => {
const canPerformActions = this.pdfProxyService.canPerformActions();
const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage());
if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) {
this.pdf.enable(textActions);
} else {
this.pdf.disable(textActions);
}
}),
);
this.addActiveScreenSubscription = selectedTextEffect.subscribe();
}
#handleDeletedDossier(): void {
@ -822,4 +817,18 @@ export class FilePreviewScreenComponent
private _isJapaneseString(text: string) {
return text.match(/[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/);
}
#restoreOldFilters() {
combineLatest([
this._filterService.getGroup$('primaryFilters').pipe(first(filterGroup => !!filterGroup?.filters.length)),
this._filterService.getGroup$('secondaryFilters').pipe(first(secondaryFilters => !!secondaryFilters?.filters.length)),
]).subscribe(([primaryFilters, secondaryFilters]) => {
const localStorageFiltersString = localStorage.getItem('workload-filters') ?? '{}';
const localStorageFilters = JSON.parse(localStorageFiltersString)[this.fileId];
if (localStorageFilters) {
copyLocalStorageFiltersValues(primaryFilters.filters, localStorageFilters.primaryFilters);
copyLocalStorageFiltersValues(secondaryFilters.filters, localStorageFilters.secondaryFilters);
}
});
}
}

View File

@ -12,17 +12,13 @@ import {
InputWithActionComponent,
IqserAllowDirective,
IqserDenyDirective,
IqserFiltersModule,
IqserHelpModeModule,
IqserRoutes,
IqserUploadFileModule,
IqserUsersModule,
LogPipe,
PreventDefaultDirective,
RoundCheckboxComponent,
StatusBarComponent,
StopPropagationDirective,
TenantPipe,
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule } from '@angular/router';
@ -66,13 +62,16 @@ import { SharedModule } from '@shared/shared.module';
import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module';
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
import { RemoveRedactionDialogComponent } from './dialogs/remove-redaction-dialog/remove-redaction-dialog.component';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { IqserFiltersModule } from '@iqser/common-ui/lib/filtering';
import { StatusBarComponent } from '@iqser/common-ui/lib/shared';
import { TenantPipe } from '@iqser/common-ui/lib/tenants';
const routes: IqserRoutes = [
{
path: '',
component: FilePreviewScreenComponent,
pathMatch: 'full',
data: { reuse: true },
canDeactivate: [PendingChangesGuard, DocumentUnloadedGuard],
},
];

View File

@ -1,13 +1,12 @@
import { Injectable } from '@angular/core';
import { ManualRedactionService } from './manual-redaction.service';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { firstValueFrom, Observable, of } from 'rxjs';
import { firstValueFrom, Observable } from 'rxjs';
import { getFirstRelevantTextPart } from '../../../utils';
import { Core } from '@pdftron/webviewer';
import {
DictionaryEntryTypes,
EarmarkOperation,
IAddRedactionRequest,
ILegalBasisChangeRequest,
IRecategorizationRequest,
IRectangle,
@ -20,7 +19,7 @@ import {
AcceptRecommendationDialogComponent,
AcceptRecommendationReturnType,
} from '../dialogs/accept-recommendation-dialog/accept-recommendation-dialog.component';
import { defaultDialogConfig, isJustOne, List } from '@iqser/common-ui';
import { defaultDialogConfig } from '@iqser/common-ui';
import { filter } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
import { FilePreviewStateService } from './file-preview-state.service';
@ -38,6 +37,8 @@ import {
import { RemoveRedactionOptions } from '../dialogs/remove-redaction-dialog/remove-redaction-options';
import { IqserDialog } from '../../../../../../../libs/common-ui/src/lib/dialog/iqser-dialog.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { isJustOne, List } from '@iqser/common-ui/lib/utils';
import { PermissionsService } from '@services/permissions.service';
@Injectable()
export class AnnotationActionsService {
@ -54,6 +55,7 @@ export class AnnotationActionsService {
private readonly _fileDataService: FileDataService,
private readonly _skippedService: SkippedService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _permissionsService: PermissionsService,
) {}
acceptSuggestion(annotations: AnnotationWrapper[]) {
@ -125,6 +127,8 @@ export class AnnotationActionsService {
canRemoveOrSuggestToRemoveFromDictionary: permissions.canRemoveOrSuggestToRemoveFromDictionary,
canMarkAsFalsePositive: permissions.canMarkAsFalsePositive,
};
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
const isApprover = this._permissionsService.isApprover(this._state.dossier());
const result: RemoveRedactionResult = await this._iqserDialog
.openDefault(RemoveRedactionDialogComponent, {
@ -133,24 +137,24 @@ export class AnnotationActionsService {
dossier: this._state.dossier(),
falsePositiveContext: this._getFalsePositiveText(redaction),
permissions: removePermissions,
applyToAllDossiers: isApprover ? dossierTemplate.applyDictionaryUpdatesToAllDossiersByDefault : false,
isApprover,
},
})
.result();
if (result) {
if (result.option.value === RemoveRedactionOptions.FALSE_POSITIVE) {
this.#setAsFalsePositive(redaction, result.comment);
this.#setAsFalsePositive(redaction, result);
} else {
const removeFromDictionary = result.option.value === RemoveRedactionOptions.IN_DOSSIER;
this.#removeRedaction(redaction, result.comment, removeFromDictionary);
this.#removeRedaction(redaction, result);
}
}
if (result.option.extraOption) {
const dossierTemplate = this._dossierTemplatesService.find(this._state.dossierTemplateId);
const { ...body } = dossierTemplate;
body.applyDictionaryUpdatesToAllDossiersByDefault = result.applyToAllDossiers;
await this._dossierTemplatesService.createOrUpdate(body);
if (isApprover && result.option.extraOption) {
const { ...body } = dossierTemplate;
body.applyDictionaryUpdatesToAllDossiersByDefault = result.option.extraOption.checked;
await this._dossierTemplatesService.createOrUpdate(body);
}
}
}
@ -380,33 +384,36 @@ export class AnnotationActionsService {
return words;
}
#setAsFalsePositive(redaction: AnnotationWrapper, comment: string) {
#setAsFalsePositive(redaction: AnnotationWrapper, dialogResult: RemoveRedactionResult) {
const request = {
sourceId: redaction.id,
value: this._getFalsePositiveText(redaction),
type: redaction.type,
positions: redaction.positions,
addToDictionary: true,
addToAllDossiers: !!dialogResult.option.extraOption?.checked,
reason: 'False Positive',
dictionaryEntryType: redaction.isRecommendation
? DictionaryEntryTypes.FALSE_RECOMMENDATION
: DictionaryEntryTypes.FALSE_POSITIVE,
comment: comment ? { text: comment } : null,
comment: dialogResult.comment ? { text: dialogResult.comment } : null,
};
const { dossierId, fileId } = this._state;
this.#processObsAndEmit(this._manualRedactionService.addAnnotation([request], dossierId, fileId)).then();
}
#removeRedaction(redaction: AnnotationWrapper, comment: string, removeFromDictionary: boolean) {
#removeRedaction(redaction: AnnotationWrapper, dialogResult: RemoveRedactionResult) {
const removeFromDictionary = dialogResult.option.value === RemoveRedactionOptions.IN_DOSSIER;
const body = {
annotationId: redaction.id,
comment: comment,
comment: dialogResult.comment,
removeFromDictionary,
removeFromAllDossiers: !!dialogResult.option.extraOption?.checked,
};
const { dossierId, fileId } = this._state;
this.#processObsAndEmit(
this._manualRedactionService.removeOrSuggestRemove([body], dossierId, fileId, removeFromDictionary, redaction.isHint),
this._manualRedactionService.removeRedaction([body], dossierId, fileId, removeFromDictionary, redaction.isHint),
).then();
}
}

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { SuperTypeSorter } from '../../../utils';
import { Filter, handleCheckedValue, IFilter, INestedFilter, NestedFilter } from '@iqser/common-ui';
import { Filter, handleCheckedValue, IFilter, INestedFilter, NestedFilter } from '@iqser/common-ui/lib/filtering';
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { annotationDefaultColorConfig } from '@red/domain';

View File

@ -1,11 +1,13 @@
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Injectable, OnDestroy } from '@angular/core';
import { EntitiesService, FilterService, ListingService, SearchService, SortingService } from '@iqser/common-ui';
import { EntitiesService, ListingService, SearchService } from '@iqser/common-ui';
import { filter, tap } from 'rxjs/operators';
import { MultiSelectService } from './multi-select.service';
import { PdfViewer } from '../../pdf-viewer/services/pdf-viewer.service';
import { REDAnnotationManager } from '../../pdf-viewer/services/annotation-manager.service';
import { Subscription } from 'rxjs';
import { FilterService } from '@iqser/common-ui/lib/filtering';
import { SortingService } from '@iqser/common-ui/lib/sorting';
@Injectable()
export class AnnotationsListingService extends ListingService<AnnotationWrapper> implements OnDestroy {

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { shareDistinctLast } from '@iqser/common-ui';
import { shareDistinctLast } from '@iqser/common-ui/lib/utils';
@Injectable()
export class CommentingService {

View File

@ -3,7 +3,7 @@ import { firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchM
import { Dictionary, Dossier, DOSSIER_ID, DOSSIER_TEMPLATE_ID, File, FILE_ID } from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service';
import { PermissionsService } from '@services/permissions.service';
import { getParam, LoadingService, wipeCache } from '@iqser/common-ui';
import { LoadingService, wipeCache } from '@iqser/common-ui';
import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators';
import { FileManagementService } from '@services/files/file-management.service';
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
@ -13,8 +13,10 @@ import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angu
import { TranslateService } from '@ngx-translate/core';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
import { toSignal } from '@angular/core/rxjs-interop';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { ViewModeService } from './view-mode.service';
import { getParam } from '@iqser/common-ui/lib/utils';
import { NGXLogger } from 'ngx-logger';
const ONE_MEGABYTE = 1024 * 1024;
@ -31,6 +33,7 @@ function isDownload(event: HttpEvent<Blob>): event is HttpProgressEvent {
@Injectable()
export class FilePreviewStateService {
readonly #reloadBlob$ = new Subject();
readonly file$: Observable<File>;
readonly file: Signal<File>;
readonly dossier: Signal<Dossier>;
@ -38,18 +41,9 @@ export class FilePreviewStateService {
readonly isWritable: Signal<boolean>;
readonly dossierDictionary: Signal<Dictionary>;
readonly blob$: Observable<Blob>;
readonly dossierId = getParam(DOSSIER_ID);
readonly dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly fileId = getParam(FILE_ID);
readonly #reloadBlob$ = new Subject();
readonly #dossierFileChange: Signal<boolean>;
// readonly #routeKey = getReusableRouteKey(inject(ActivatedRoute).snapshot);
// readonly isAttached = inject(CustomRouteReuseStrategy).attached$.pipe(
// map(route => getReusableRouteKey(route) === this.#routeKey),
// startWith(true),
// );
constructor(
private readonly _permissionsService: PermissionsService,
@ -60,28 +54,27 @@ export class FilePreviewStateService {
private readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService,
private readonly _viewModeService: ViewModeService,
private readonly _logger: NGXLogger,
) {
this.dossier = toSignal(dossiersServiceResolver().getEntityChanged$(this.dossierId));
this.file$ = inject(FilesMapService).watch$(this.dossierId, this.fileId);
this.file = toSignal(this.file$);
// this.file$ = combineLatest([this.isAttached, file$]).pipe(
// filter(([isAttached]) => isAttached),
// map(([, file]) => file),
// log('file$'),
// shareDistinctLast(),
// );
this.isWritable = computed(() => this._permissionsService.canPerformAnnotationActions(this.file(), this.dossier()));
this.isWritable = computed(() => {
const isWritable = this._permissionsService.canPerformAnnotationActions(this.file(), this.dossier());
this._logger.info('[FILE] Is writeable:', isWritable);
return isWritable;
});
this.isReadonly = computed(() => !this.isWritable());
this.blob$ = this.#blob$;
this.dossierDictionary = toSignal(inject(DossierDictionariesMapService).watch$(this.dossierId, 'dossier_redaction'));
this.#dossierFileChange = toSignal(this.#dossierFilesChange$);
effect(() => {
if (this.#dossierFileChange()) {
this._filesService.loadAll(this.dossierId);
}
});
this.#dossierFilesChange$
.pipe(
switchMap(() => this._filesService.loadAll(this.dossierId)),
takeUntilDestroyed(),
)
.subscribe();
effect(
() => {
if (this._viewModeService.isEarmarks() && !this.file().hasHighlights) {

View File

@ -11,7 +11,7 @@ import type {
ManualRedactionActions,
} from '@red/domain';
import { type AnnotationWrapper } from '@models/file/annotation.wrapper';
import { GenericService, IqserPermissionsService, List, Toaster } from '@iqser/common-ui';
import { GenericService, IqserPermissionsService, Toaster } from '@iqser/common-ui';
import { tap } from 'rxjs/operators';
import { PermissionsService } from '@services/permissions.service';
import { dictionaryActionsTranslations, manualRedactionActionsTranslations } from '@translations/annotation-actions-translations';
@ -22,6 +22,7 @@ import { type ManualRedactionEntryType } from '@models/file/manual-redaction-ent
import { NGXLogger } from 'ngx-logger';
import { Roles } from '@users/roles';
import { firstValueFrom, of } from 'rxjs';
import { List } from '@iqser/common-ui/lib/utils';
function getResponseType(error: boolean, isConflict: boolean) {
const isConflictError = isConflict ? 'conflictError' : 'error';
@ -96,15 +97,10 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
addAnnotation(requests: List<IAddRedactionRequest>, dossierId: string, fileId: string, dictionaryLabel?: string) {
const toast = requests[0].addToDictionary ? this.#showAddToDictionaryToast(requests, dictionaryLabel) : this.#showToast('add');
const canAddRedaction = this._iqaerPermissionsService.has(Roles.redactions.write);
if (canAddRedaction && this._permissionsService.isApprover(this.#dossier(dossierId))) {
if (canAddRedaction) {
return this.add(requests, dossierId, fileId).pipe(toast);
}
const canRequestRedaction = this._iqaerPermissionsService.has(Roles.redactions.request);
if (canRequestRedaction) {
return this.requestAdd(requests, dossierId, fileId).pipe(toast);
}
return of(undefined);
}
@ -140,20 +136,8 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
return this.requestResize(requests, dossierId, fileId);
}
removeOrSuggestRemove(
body: List<IRemoveRedactionRequest>,
dossierId: string,
fileId: string,
removeFromDictionary = false,
isHint = false,
) {
if (this._permissionsService.isApprover(this.#dossier(dossierId))) {
return this.remove(body, dossierId, fileId).pipe(this.#showToast(!isHint ? 'remove' : 'remove-hint', removeFromDictionary));
}
return this.requestRemoveRedaction(body, dossierId, fileId).pipe(
this.#showToast(!isHint ? 'request-remove' : 'request-remove-hint', removeFromDictionary),
);
removeRedaction(body: List<IRemoveRedactionRequest>, dossierId: string, fileId: string, removeFromDictionary = false, isHint = false) {
return this.remove(body, dossierId, fileId).pipe(this.#showToast(!isHint ? 'remove' : 'remove-hint', removeFromDictionary));
}
getTitle(type: ManualRedactionEntryType) {
@ -193,10 +177,6 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
);
}
requestRemoveRedaction(body: List<IRemoveRedactionRequest>, dossierId: string, fileId: string) {
return this._post(body, `${this.#bulkRequest}/remove/${dossierId}/${fileId}`).pipe(this.#log('Request remove', body));
}
approve(annotationIds: List, dossierId: string, fileId: string) {
return this._post(annotationIds, `${this._defaultModelPath}/bulk/approve/${dossierId}/${fileId}`).pipe(
this.#log('Approve', annotationIds),
@ -219,10 +199,6 @@ export class ManualRedactionService extends GenericService<IManualAddResponse> {
return this._post(body, `${this.#bulkRedaction}/remove/${dossierId}/${fileId}`).pipe(this.#log('Remove', body));
}
requestAdd(body: List<IAddRedactionRequest>, dossierId: string, fileId: string) {
return this._post(body, `${this.#bulkRequest}/add/${dossierId}/${fileId}`).pipe(this.#log('Request add', body));
}
forceRedaction(body: List<ILegalBasisChangeRequest>, dossierId: string, fileId: string) {
return this._post(body, `${this.#bulkRedaction}/force/${dossierId}/${fileId}`).pipe(this.#log('Force redaction', body));
}

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