Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
4c856671e7
@ -150,7 +150,7 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
enabled: true,
|
||||
},
|
||||
CHANGES: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
STATS: {
|
||||
enabled: false,
|
||||
@ -164,6 +164,9 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
PAGES: {
|
||||
enabled: false,
|
||||
},
|
||||
DOSSIERS_CHANGES: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
} as ILoggerConfig,
|
||||
},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
import { AppConfig } from '@red/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-auth-error',
|
||||
@ -8,8 +9,9 @@ import { ConfigService } from '@services/config.service';
|
||||
styleUrls: ['./auth-error.component.scss'],
|
||||
})
|
||||
export class AuthErrorComponent {
|
||||
adminName = this._configService.values.ADMIN_CONTACT_NAME;
|
||||
adminUrl = this._configService.values.ADMIN_CONTACT_URL;
|
||||
readonly #config = getConfig<AppConfig>();
|
||||
readonly adminName = this.#config.ADMIN_CONTACT_NAME;
|
||||
readonly adminUrl = this.#config.ADMIN_CONTACT_URL;
|
||||
|
||||
constructor(readonly userService: UserService, private readonly _configService: ConfigService) {}
|
||||
constructor(readonly userService: UserService) {}
|
||||
}
|
||||
|
||||
@ -11,10 +11,7 @@
|
||||
|
||||
<a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/'] | tenant" class="logo">
|
||||
<div [attr.help-mode-key]="'home'" class="actions">
|
||||
<iqser-logo
|
||||
(iqserHiddenAction)="userPreferenceService.toggleDevFeatures()"
|
||||
[icon]="config.IS_DOCUMINE ? 'red:documine-logo' : 'red:logo'"
|
||||
></iqser-logo>
|
||||
<iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo>
|
||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@ -6,7 +6,7 @@ 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 { getConfig, IqserPermissionsService } 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';
|
||||
@ -24,16 +24,9 @@ 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;
|
||||
readonly config = getConfig();
|
||||
readonly searchActions: List<SpotlightSearchAction> = [
|
||||
{
|
||||
text: this._translateService.instant('search.this-dossier'),
|
||||
@ -53,8 +46,24 @@ export class BaseScreenComponent {
|
||||
action: (query): void => this.#search(query, []),
|
||||
},
|
||||
];
|
||||
readonly #navigationStart$ = this._router.events.pipe(
|
||||
filter(isNavigationStart),
|
||||
map(event => event.url),
|
||||
startWith(this._router.url),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
readonly isSearchScreen$ = this.#navigationStart$.pipe(map(isSearchScreen));
|
||||
|
||||
get #hideSearchThisDossier() {
|
||||
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
|
||||
if (!routerLink) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isDossierOverview = (routerLink.includes(DOSSIERS_ROUTE) || routerLink.includes(ARCHIVE_ROUTE)) && routerLink.length === 3;
|
||||
return !isDossierOverview;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
activatedRoute: ActivatedRoute,
|
||||
@ -71,16 +80,6 @@ export class BaseScreenComponent {
|
||||
activatedRoute.queryParamMap.pipe(takeUntilDestroyed()).subscribe(queryParams => this.#navigate(queryParams));
|
||||
}
|
||||
|
||||
get #hideSearchThisDossier() {
|
||||
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
|
||||
if (!routerLink) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isDossierOverview = (routerLink.includes(DOSSIERS_ROUTE) || routerLink.includes(ARCHIVE_ROUTE)) && routerLink.length === 3;
|
||||
return !isDossierOverview;
|
||||
}
|
||||
|
||||
#navigate(queryParams: ParamMap) {
|
||||
if (queryParams.has('username')) {
|
||||
return this._router.navigate([], {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
</div>
|
||||
<a class="logo">
|
||||
<div class="actions">
|
||||
<iqser-logo [icon]="config.IS_DOCUMINE ? 'red:documine-logo' : 'red:logo'"></iqser-logo>
|
||||
<iqser-logo icon="iqser:logo"></iqser-logo>
|
||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-skeleton-top-bar',
|
||||
@ -9,7 +8,5 @@ import { getConfig } from '@iqser/common-ui';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SkeletonTopBarComponent {
|
||||
readonly config = getConfig();
|
||||
|
||||
constructor(readonly titleService: Title) {}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
placeholder="dd/mm/yy"
|
||||
/>
|
||||
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #fromPicker></mat-datepicker>
|
||||
</ng-container>
|
||||
@ -60,7 +60,7 @@
|
||||
placeholder="dd/mm/yy"
|
||||
/>
|
||||
<mat-datepicker-toggle [for]="toPicker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #toPicker></mat-datepicker>
|
||||
</ng-container>
|
||||
|
||||
@ -30,8 +30,8 @@
|
||||
<mat-select (selectionChange)="filterChange()" formControlName="category" id="select-category">
|
||||
<mat-option
|
||||
*ngFor="let category of categories"
|
||||
[value]="category"
|
||||
[id]="'select-category-' + category.toLocaleLowerCase()"
|
||||
[value]="category"
|
||||
>
|
||||
{{ (translations[category] | translate) || category }}
|
||||
</mat-option>
|
||||
@ -52,7 +52,7 @@
|
||||
<div *ngIf="form.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
|
||||
</mat-select-trigger>
|
||||
|
||||
<mat-option *ngFor="let userId of userIds" [value]="userId" [id]="'select-user-' + userId">
|
||||
<mat-option *ngFor="let userId of userIds" [id]="'select-user-' + userId" [value]="userId">
|
||||
<iqser-initials-avatar *ngIf="userId !== ALL_USERS" [user]="userId" [withName]="true"></iqser-initials-avatar>
|
||||
|
||||
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
|
||||
@ -65,14 +65,14 @@
|
||||
|
||||
<div class="iqser-input-group datepicker-wrapper mr-20">
|
||||
<input
|
||||
id="start-date-input"
|
||||
(dateChange)="filterChange()"
|
||||
[matDatepicker]="fromPicker"
|
||||
formControlName="from"
|
||||
id="start-date-input"
|
||||
placeholder="dd/mm/yy"
|
||||
/>
|
||||
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #fromPicker></mat-datepicker>
|
||||
</div>
|
||||
@ -81,14 +81,14 @@
|
||||
|
||||
<div class="iqser-input-group datepicker-wrapper">
|
||||
<input
|
||||
id="end-date-input"
|
||||
(dateChange)="filterChange()"
|
||||
[matDatepicker]="toPicker"
|
||||
formControlName="to"
|
||||
id="end-date-input"
|
||||
placeholder="dd/mm/yy"
|
||||
/>
|
||||
<mat-datepicker-toggle [for]="toPicker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #toPicker></mat-datepicker>
|
||||
</div>
|
||||
|
||||
@ -17,12 +17,12 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="ctx.dossierTemplate.validTo && ctx.dossierTemplate.validFrom | date : 'd MMM yyyy' as validFrom">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
<span [innerHTML]="'dossier-template-info-screen.valid-from' | translate : { date: validFrom }"></span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="ctx.dossierTemplate.dateAdded | date : 'd MMM yyyy' as createdOn">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
<span [innerHTML]="'dossier-template-info-screen.created-on' | translate : { date: createdOn }"></span>
|
||||
</div>
|
||||
|
||||
@ -32,12 +32,12 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="ctx.dossierTemplate.validFrom && ctx.dossierTemplate.validTo | date : 'd MMM yyyy' as validTo">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
<span [innerHTML]="'dossier-template-info-screen.valid-to' | translate : { date: validTo }"></span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="ctx.dossierTemplate.dateModified | date : 'd MMM yyyy' as dateModified">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
<span [innerHTML]="'dossier-template-info-screen.modified-on' | translate : { date: dateModified }"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { ListingComponent, listingProvidersFactory } from '@iqser/common-ui';
|
||||
import { ListingComponent, listingProvidersFactory, LoadingService } from '@iqser/common-ui';
|
||||
import { Dossier, DOSSIER_TEMPLATE_ID } from '@red/domain';
|
||||
import { ConfigService } from '../../services/config.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { OnAttach } from '@common-ui/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-archived-dossiers-screen',
|
||||
@ -13,7 +14,7 @@ import { Router } from '@angular/router';
|
||||
providers: listingProvidersFactory(ArchivedDossiersScreenComponent),
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ArchivedDossiersScreenComponent extends ListingComponent<Dossier> implements OnInit {
|
||||
export class ArchivedDossiersScreenComponent extends ListingComponent<Dossier> implements OnInit, OnAttach {
|
||||
readonly tableColumnConfigs = this._configService.tableConfig;
|
||||
readonly tableHeaderLabel = _('archived-dossiers-listing.table-header.title');
|
||||
readonly computeFilters$ = this.entitiesService.all$.pipe(tap(() => this._computeAllFilters()));
|
||||
@ -22,6 +23,7 @@ export class ArchivedDossiersScreenComponent extends ListingComponent<Dossier> i
|
||||
constructor(
|
||||
private readonly _configService: ConfigService,
|
||||
private readonly _archivedDossiersService: ArchivedDossiersService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
router: Router,
|
||||
) {
|
||||
super();
|
||||
@ -33,6 +35,11 @@ export class ArchivedDossiersScreenComponent extends ListingComponent<Dossier> i
|
||||
this.addSubscription = this._archivedDossiersService.all$
|
||||
.pipe(tap(dossiers => this.entitiesService.setEntities(dossiers.filter(d => d.dossierTemplateId === this.#dossierTemplateId))))
|
||||
.subscribe();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
ngOnAttach() {
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
private _computeAllFilters() {
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="dossier.date | date : 'd MMM yyyy' as date">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
<span [innerHTML]="'dossier-overview.dossier-details.stats.created-on' | translate : { date }"></span>
|
||||
</div>
|
||||
|
||||
@ -37,8 +37,8 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="!isDocumine"
|
||||
(click)="openEditDossierDialog('dossierDictionary')"
|
||||
*ngIf="!isDocumine"
|
||||
[attr.help-mode-key]="'edit_dossier_dossier_dictionary'"
|
||||
class="link-property"
|
||||
>
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="_state.file().added | date : 'mediumDate' as added">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
<span [innerHTML]="'file-preview.tabs.document-info.details.created-on' | translate : { date: added }"></span>
|
||||
</div>
|
||||
|
||||
|
||||
@ -40,6 +40,14 @@
|
||||
type="file-preview"
|
||||
></redaction-file-actions>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="getTables()"
|
||||
*allow="roles.getTables"
|
||||
[icon]="'red:csv'"
|
||||
[tooltip]="'file-preview.get-tables' | translate"
|
||||
class="ml-2"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="toggleFullScreen()"
|
||||
[icon]="fullScreen ? 'red:exit-fullscreen' : 'red:fullscreen'"
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
@ -75,6 +76,8 @@ import { TenantsService } from '@iqser/common-ui/lib/tenants';
|
||||
import { AddHintDialogComponent } from './dialogs/add-hint-dialog/add-hint-dialog.component';
|
||||
import { AddAnnotationDialogComponent } from './dialogs/docu-mine/add-annotation-dialog/add-annotation-dialog.component';
|
||||
import { RedactTextData } from './utils/dialog-types';
|
||||
import { TablesService } from './services/tables.service';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
const textActions = [TextPopups.REDACT_TEXT, TextPopups.ADD_HINT, TextPopups.ADD_FALSE_POSITIVE];
|
||||
|
||||
@ -93,13 +96,13 @@ export class FilePreviewScreenComponent
|
||||
})
|
||||
private readonly _filterTemplate: TemplateRef<unknown>;
|
||||
@ViewChild('actionsWrapper', { static: false }) private readonly _actionsWrapper: ElementRef;
|
||||
readonly #isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly roles = Roles;
|
||||
fullScreen = false;
|
||||
readonly fileId = this.state.fileId;
|
||||
readonly dossierId = this.state.dossierId;
|
||||
readonly lastAssignee = computed(() => this.getLastAssignee());
|
||||
readonly #isDocumine;
|
||||
width: number;
|
||||
|
||||
constructor(
|
||||
@ -143,6 +146,7 @@ export class FilePreviewScreenComponent
|
||||
private readonly _suggestionsService: SuggestionsService,
|
||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||
private readonly _dialog: MatDialog,
|
||||
private readonly _tablesService: TablesService,
|
||||
) {
|
||||
super();
|
||||
effect(() => {
|
||||
@ -174,7 +178,6 @@ export class FilePreviewScreenComponent
|
||||
this._stampService.stampPDF().then();
|
||||
}
|
||||
});
|
||||
this.#isDocumine = getConfig().IS_DOCUMINE;
|
||||
}
|
||||
|
||||
get changed() {
|
||||
@ -369,29 +372,6 @@ export class FilePreviewScreenComponent
|
||||
);
|
||||
}
|
||||
|
||||
async #openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
||||
const file = this.state.file();
|
||||
const hint = manualRedactionEntryWrapper.type === ManualRedactionEntryTypes.HINT;
|
||||
|
||||
const data = this.#getRedactTextDialogData(manualRedactionEntryWrapper, file);
|
||||
const result = await this.#getRedactTextDialog(hint, data).result();
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const add$ = this._manualRedactionService.addAnnotation([result.redaction], this.dossierId, this.fileId, {
|
||||
hint,
|
||||
dictionaryLabel: result.dictionary?.label,
|
||||
});
|
||||
|
||||
const addAndReload$ = add$.pipe(
|
||||
tap(() => this._documentViewer.clearSelection()),
|
||||
switchMap(() => this._filesService.reload(this.dossierId, file)),
|
||||
);
|
||||
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
|
||||
}
|
||||
|
||||
toggleFullScreen() {
|
||||
this.fullScreen = !this.fullScreen;
|
||||
if (this.fullScreen) {
|
||||
@ -521,6 +501,47 @@ export class FilePreviewScreenComponent
|
||||
return this.#cleanupAndRedrawAnnotations(annotationsToDraw);
|
||||
}
|
||||
|
||||
async getTables() {
|
||||
const currentPage = this.pdf.currentPage();
|
||||
const tables = await this._tablesService.get(this.state.dossierId, this.state.fileId, this.pdf.currentPage());
|
||||
await this._annotationDrawService.drawTables(tables, currentPage, this.state.dossierTemplateId);
|
||||
|
||||
const filename = this.state.file().filename;
|
||||
const zip = new JSZip();
|
||||
|
||||
tables.forEach((t, index) => {
|
||||
const blob = new Blob([atob(t.csvAsBytes)], {
|
||||
type: 'text/csv;charset=utf-8',
|
||||
});
|
||||
zip.file(filename + '_page' + currentPage + '_table' + (index + 1) + '.csv', blob);
|
||||
});
|
||||
|
||||
saveAs(await zip.generateAsync({ type: 'blob' }), filename + '_tables.zip');
|
||||
}
|
||||
|
||||
async #openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
|
||||
const file = this.state.file();
|
||||
const hint = manualRedactionEntryWrapper.type === ManualRedactionEntryTypes.HINT;
|
||||
|
||||
const data = this.#getRedactTextDialogData(manualRedactionEntryWrapper, file);
|
||||
const result = await this.#getRedactTextDialog(hint, data).result();
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const add$ = this._manualRedactionService.addAnnotation([result.redaction], this.dossierId, this.fileId, {
|
||||
hint,
|
||||
dictionaryLabel: result.dictionary?.label,
|
||||
});
|
||||
|
||||
const addAndReload$ = add$.pipe(
|
||||
tap(() => this._documentViewer.clearSelection()),
|
||||
switchMap(() => this._filesService.reload(this.dossierId, file)),
|
||||
);
|
||||
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
|
||||
}
|
||||
|
||||
@Debounce(30)
|
||||
private _updateItemWidth(entry: ResizeObserverEntry): void {
|
||||
this.width = entry.contentRect.width;
|
||||
|
||||
@ -72,6 +72,7 @@ import { RemoveAnnotationDialogComponent } from './dialogs/docu-mine/remove-anno
|
||||
import { ResizeAnnotationDialogComponent } from './dialogs/docu-mine/resize-annotation-dialog/resize-annotation-dialog.component';
|
||||
import { EditAnnotationDialogComponent } from './dialogs/docu-mine/edit-annotation-dialog/edit-annotation-dialog.component';
|
||||
import { EditRedactionDialogComponent } from './dialogs/edit-redaction-dialog/edit-redaction-dialog.component';
|
||||
import { TablesService } from './services/tables.service';
|
||||
|
||||
const routes: IqserRoutes = [
|
||||
{
|
||||
@ -157,6 +158,6 @@ const components = [
|
||||
TenantPipe,
|
||||
LogPipe,
|
||||
],
|
||||
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, SuggestionsService],
|
||||
providers: [FilePreviewDialogService, ManualRedactionService, DocumentUnloadedGuard, SuggestionsService, TablesService],
|
||||
})
|
||||
export class FilePreviewModule {}
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { log } from '@common-ui/utils';
|
||||
|
||||
export interface BoundingBox {
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
interface CellPerRow {
|
||||
readonly boundingBox: BoundingBox;
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
export interface Table {
|
||||
readonly boundingBox: BoundingBox;
|
||||
readonly cellsPerRow: CellPerRow[][];
|
||||
readonly experimental: boolean;
|
||||
readonly csvAsBytes: string;
|
||||
}
|
||||
|
||||
interface Response {
|
||||
readonly tables: Table[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TablesService {
|
||||
readonly #http = inject(HttpClient);
|
||||
readonly #serviceName = 'table-provider';
|
||||
readonly #defaultUrl = 'tables';
|
||||
|
||||
get(dossierId: string, fileId: string, pageNumber: number) {
|
||||
const url = `/${this.#serviceName}/${encodeURI(this.#defaultUrl)}/${dossierId}/${fileId}`;
|
||||
const request$ = this.#http.post<Response>(url, { pageNumber, tableExtractionType: 'STABLE' });
|
||||
return firstValueFrom(
|
||||
request$.pipe(
|
||||
map(response => response.tables),
|
||||
log('TablesService.get'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { getConfig } from '@common-ui/services';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, MatIconModule],
|
||||
@ -9,6 +10,8 @@ import { DomSanitizer } from '@angular/platform-browser';
|
||||
exports: [MatIconModule],
|
||||
})
|
||||
export class IconsModule {
|
||||
readonly config = getConfig();
|
||||
|
||||
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
|
||||
const icons = [
|
||||
'ai',
|
||||
@ -26,7 +29,6 @@ export class IconsModule {
|
||||
'assign',
|
||||
'assign-me',
|
||||
'attribute',
|
||||
'calendar',
|
||||
'case-sensitive',
|
||||
'color-picker',
|
||||
'comment',
|
||||
@ -36,7 +38,6 @@ export class IconsModule {
|
||||
'dictionary',
|
||||
'denied',
|
||||
'disable-analysis',
|
||||
'documine-logo',
|
||||
'double-chevron-right',
|
||||
'enable-analysis',
|
||||
'enter',
|
||||
@ -51,7 +52,6 @@ export class IconsModule {
|
||||
'info',
|
||||
'import_redactions',
|
||||
'lightning',
|
||||
'logo',
|
||||
'nav-first',
|
||||
'nav-last',
|
||||
'nav-next',
|
||||
@ -100,5 +100,13 @@ export class IconsModule {
|
||||
_sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/general/${icon}.svg`),
|
||||
);
|
||||
}
|
||||
|
||||
const logo = this.config.IS_DOCUMINE ? 'documine-logo' : 'logo';
|
||||
|
||||
_iconRegistry.addSvgIconInNamespace(
|
||||
'iqser',
|
||||
'logo',
|
||||
_sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/general/${logo}.svg`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,12 +12,24 @@ import { REDAnnotationManager } from './annotation-manager.service';
|
||||
import { List } from '@iqser/common-ui/lib/utils';
|
||||
import { REDDocumentViewer } from './document-viewer.service';
|
||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
||||
import { BoundingBox, Table } from '../../file-preview/services/tables.service';
|
||||
import Annotation = Core.Annotations.Annotation;
|
||||
import Quad = Core.Math.Quad;
|
||||
|
||||
const DEFAULT_TEXT_ANNOTATION_OPACITY = 1;
|
||||
const DEFAULT_REMOVED_ANNOTATION_OPACITY = 0.2;
|
||||
|
||||
export function getSectionRectangle(box: BoundingBox): ISectionRectangle {
|
||||
return {
|
||||
topLeft: {
|
||||
x: box.x,
|
||||
y: box.y,
|
||||
},
|
||||
width: box.width,
|
||||
height: box.height,
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AnnotationDrawService {
|
||||
constructor(
|
||||
@ -57,6 +69,20 @@ export class AnnotationDrawService {
|
||||
return this._pdf.quad(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
}
|
||||
|
||||
async drawTables(tables: Table[], page: number, dossierTemplateId: string) {
|
||||
const sections: Core.Annotations.RectangleAnnotation[] = [];
|
||||
tables.forEach(table => {
|
||||
sections.push(this._computeSection(page, getSectionRectangle(table.boundingBox), dossierTemplateId));
|
||||
table.cellsPerRow
|
||||
.flatMap(row => row)
|
||||
.forEach(row => {
|
||||
sections.push(this._computeSection(page, getSectionRectangle(row.boundingBox), dossierTemplateId));
|
||||
});
|
||||
});
|
||||
|
||||
await this._annotationManager.add(sections);
|
||||
}
|
||||
|
||||
private async _draw(annotationWrappers: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
|
||||
const totalPages = this._pdf.totalPages();
|
||||
const annotations = annotationWrappers
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
<div *ngIf="hasDueDate" class="iqser-input-group datepicker-wrapper">
|
||||
<input [matDatepicker]="picker" formControlName="dueDate" placeholder="dd/mm/yy" />
|
||||
<mat-datepicker-toggle [for]="picker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</div>
|
||||
@ -81,20 +81,20 @@
|
||||
<iqser-icon-button
|
||||
(action)="deleteDossier()"
|
||||
*ngIf="permissionsService.canDeleteDossier(dossier)"
|
||||
[attr.help-mode-key]="'edit_dossier_delete_dossier_DIALOG'"
|
||||
[buttonId]="'deleteDossier'"
|
||||
[icon]="'iqser:trash'"
|
||||
[label]="'dossier-listing.delete.action' | translate"
|
||||
[type]="iconButtonTypes.dark"
|
||||
[attr.help-mode-key]="'edit_dossier_delete_dossier_DIALOG'"
|
||||
></iqser-icon-button>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="archiveDossier()"
|
||||
*ngIf="permissionsService.canArchiveDossier(dossier)"
|
||||
[attr.help-mode-key]="'edit_dossier_archive_dossier_DIALOG'"
|
||||
[icon]="'red:archive'"
|
||||
[label]="'dossier-listing.archive.action' | translate"
|
||||
[type]="iconButtonTypes.dark"
|
||||
[attr.help-mode-key]="'edit_dossier_archive_dossier_DIALOG'"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div [matTooltip]="dossier.dossierName" class="table-item-title heading mb-6" matTooltipPosition="above" [attr.help-mode-key]="'dossier'">
|
||||
<div [attr.help-mode-key]="'dossier'" [matTooltip]="dossier.dossierName" class="table-item-title heading mb-6" matTooltipPosition="above">
|
||||
{{ dossier.dossierName }}
|
||||
</div>
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="small-label">
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
{{ dossier.date | date : 'mediumDate' }}
|
||||
</div>
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@
|
||||
<div *ngIf="hasDueDate" class="iqser-input-group datepicker-wrapper">
|
||||
<input [matDatepicker]="picker" formControlName="dueDate" placeholder="dd/mm/yy" />
|
||||
<mat-datepicker-toggle [for]="picker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="iqser:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</div>
|
||||
|
||||
@ -16,6 +16,7 @@ export const Roles = {
|
||||
search: 'red-search',
|
||||
searchAudit: 'red-search-audit-log',
|
||||
manageAclPermissions: 'red-manage-acl-permissions',
|
||||
getTables: 'red-get-tables',
|
||||
rules: {
|
||||
read: 'red-read-rules',
|
||||
write: 'red-write-rules',
|
||||
|
||||
@ -1437,6 +1437,7 @@
|
||||
"exclude-pages": "Seiten von Schwärzung ausschließen",
|
||||
"excluded-from-redaction": "Von Schwärzung ausgeschlossen",
|
||||
"fullscreen": "Vollbildmodus",
|
||||
"get-tables": "",
|
||||
"highlights": {
|
||||
"convert": "",
|
||||
"remove": ""
|
||||
|
||||
@ -1437,6 +1437,7 @@
|
||||
"exclude-pages": "Exclude pages from redaction",
|
||||
"excluded-from-redaction": "excluded",
|
||||
"fullscreen": "Full Screen (F)",
|
||||
"get-tables": "Draw tables",
|
||||
"highlights": {
|
||||
"convert": "Convert earmarks",
|
||||
"remove": "Remove earmarks"
|
||||
|
||||
@ -1437,6 +1437,7 @@
|
||||
"exclude-pages": "Seiten von Schwärzung ausschließen",
|
||||
"excluded-from-redaction": "Von Schwärzung ausgeschlossen",
|
||||
"fullscreen": "Vollbildmodus",
|
||||
"get-tables": "",
|
||||
"highlights": {
|
||||
"convert": "",
|
||||
"remove": ""
|
||||
|
||||
@ -1437,6 +1437,7 @@
|
||||
"exclude-pages": "Exclude pages from component",
|
||||
"excluded-from-redaction": "excluded",
|
||||
"fullscreen": "Full Screen (F)",
|
||||
"get-tables": "Draw tables",
|
||||
"highlights": {
|
||||
"convert": "Convert earmarks",
|
||||
"remove": "Remove earmarks"
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="100px" version="1.1" viewBox="0 0 100 100" width="100px"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd" id="calendar" stroke="none" stroke-width="1">
|
||||
<path
|
||||
d="M35,70 L35,80 L25,80 L25,70 L35,70 Z M55,70 L55,80 L45,80 L45,70 L55,70 Z M35,55 L35,65 L25,65 L25,55 L35,55 Z M55,55 L55,65 L45,65 L45,55 L55,55 Z M75,55 L75,65 L65,65 L65,55 L75,55 Z M55,40 L55,50 L45,50 L45,40 L55,40 Z M75,40 L75,50 L65,50 L65,40 L75,40 Z"
|
||||
fill="currentColor" fill-rule="nonzero" id="Combined-Shape"></path>
|
||||
<path
|
||||
d="M90,0 L10,0 C4.5,0 0,4.5 0,10 L0,90 C0,95.5 4.5,100 10,100 L90,100 C95.5,100 100,95.5 100,90 L100,10 C100,4.5 95.5,0 90,0 Z M10,90 L10,10 L90,10 L90,90 L10,90 Z"
|
||||
fill="currentColor" fill-rule="nonzero" id="Shape"></path>
|
||||
<path
|
||||
d="M90,20 L10,20 C4.5,20 0,24.5 0,30 L0,90 C0,95.5 4.5,100 10,100 L90,100 C95.5,100 100,95.5 100,90 L100,30 C100,24.5 95.5,20 90,20 Z M10,90 L10,30 L90,30 L90,90 L10,90 Z"
|
||||
fill="currentColor" fill-rule="nonzero" id="Shape"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1 +1 @@
|
||||
Subproject commit 3b84e73f8060f648e91b61f7df6827778d73917c
|
||||
Subproject commit 5d40f20a32fbcbd99faaaf42688b19b6601a8f1f
|
||||
@ -42,6 +42,7 @@
|
||||
"chart.js": "^4.3.0",
|
||||
"dayjs": "^1.11.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"keycloak-angular": "14.0.0",
|
||||
"keycloak-js": "21.1.1",
|
||||
@ -79,6 +80,7 @@
|
||||
"@nx/linter": "16.3.2",
|
||||
"@nx/workspace": "16.3.2",
|
||||
"@schematics/angular": "16.1.0",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/jest": "29.5.2",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/node": "20.3.1",
|
||||
|
||||
36
yarn.lock
36
yarn.lock
@ -3720,6 +3720,11 @@
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/file-saver@^2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.5.tgz#9ee342a5d1314bb0928375424a2f162f97c310c7"
|
||||
integrity sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==
|
||||
|
||||
"@types/graceful-fs@^4.1.3":
|
||||
version "4.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae"
|
||||
@ -7282,6 +7287,11 @@ image-size@~0.5.0:
|
||||
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
|
||||
integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==
|
||||
|
||||
immediate@~3.0.5:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
||||
|
||||
immutable@^4.0.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be"
|
||||
@ -8233,6 +8243,16 @@ jsonparse@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
||||
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
|
||||
|
||||
jszip@^3.10.1:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
|
||||
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
|
||||
dependencies:
|
||||
lie "~3.3.0"
|
||||
pako "~1.0.2"
|
||||
readable-stream "~2.3.6"
|
||||
setimmediate "^1.0.5"
|
||||
|
||||
jwt-decode@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
||||
@ -8335,6 +8355,13 @@ license-webpack-plugin@4.0.2, license-webpack-plugin@^4.0.2:
|
||||
dependencies:
|
||||
webpack-sources "^3.0.0"
|
||||
|
||||
lie@~3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
|
||||
dependencies:
|
||||
immediate "~3.0.5"
|
||||
|
||||
lilconfig@2.1.0, lilconfig@^2.0.3:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
||||
@ -9448,7 +9475,7 @@ pacote@15.2.0:
|
||||
ssri "^10.0.0"
|
||||
tar "^6.1.11"
|
||||
|
||||
pako@^1.0.3:
|
||||
pako@^1.0.3, pako@~1.0.2:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
@ -10370,7 +10397,7 @@ read-package-json@^6.0.0:
|
||||
normalize-package-data "^5.0.0"
|
||||
npm-normalize-package-bin "^3.0.0"
|
||||
|
||||
readable-stream@^2.0.1, readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||
readable-stream@^2.0.1, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
|
||||
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
|
||||
@ -10819,6 +10846,11 @@ set-blocking@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
|
||||
|
||||
setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
|
||||
|
||||
setprototypeof@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user