Merge branch 'master' into VM/RED-7980

This commit is contained in:
Valentin Mihai 2024-03-07 10:04:08 +02:00
commit 94c35502d9
12 changed files with 125 additions and 41 deletions

View File

@ -5,7 +5,7 @@
[ngClass]="{ 'workflow-attribute': mode === 'workflow', 'file-name-column': fileNameColumn }" [ngClass]="{ 'workflow-attribute': mode === 'workflow', 'file-name-column': fileNameColumn }"
class="file-attribute" class="file-attribute"
> >
<div [ngClass]="{ 'workflow-value': mode === 'workflow' }" class="value"> <div [ngClass]="{ 'workflow-value': mode === 'workflow' }" class="value" *ngIf="!isInEditMode || !fileNameColumn">
<mat-icon *ngIf="!fileAttribute.editable" [matTooltip]="'readonly' | translate" svgIcon="red:read-only"></mat-icon> <mat-icon *ngIf="!fileAttribute.editable" [matTooltip]="'readonly' | translate" svgIcon="red:read-only"></mat-icon>
<div> <div>
<b *ngIf="mode === 'workflow' && !isInEditMode"> {{ fileAttribute.label }}: </b> <b *ngIf="mode === 'workflow' && !isInEditMode"> {{ fileAttribute.label }}: </b>
@ -55,6 +55,8 @@
<form (ngSubmit)="form.valid && save()" [formGroup]="form"> <form (ngSubmit)="form.valid && save()" [formGroup]="form">
<iqser-dynamic-input <iqser-dynamic-input
(closedDatepicker)="closedDatepicker = $event" (closedDatepicker)="closedDatepicker = $event"
[style.max-width]="editFieldWidth"
[style.min-width]="editFieldWidth"
(keydown.escape)="close()" (keydown.escape)="close()"
[formControlName]="fileAttribute.id" [formControlName]="fileAttribute.id"
[id]="fileAttribute.id" [id]="fileAttribute.id"
@ -66,11 +68,17 @@
(action)="save()" (action)="save()"
[disabled]="disabled" [disabled]="disabled"
[icon]="'iqser:check'" [icon]="'iqser:check'"
[size]="mode === 'workflow' ? 15 : 34" [size]="mode === 'workflow' || fileNameColumn ? 15 : 34"
[ngClass]="{ 'file-name-btn': fileNameColumn }"
class="save" class="save"
></iqser-circle-button> ></iqser-circle-button>
<iqser-circle-button (action)="close()" [icon]="'iqser:close'" [size]="mode === 'workflow' ? 15 : 34"></iqser-circle-button> <iqser-circle-button
(action)="close()"
[ngClass]="{ 'file-name-btn': fileNameColumn }"
[icon]="'iqser:close'"
[size]="mode === 'workflow' || fileNameColumn ? 15 : 34"
></iqser-circle-button>
</form> </form>
</div> </div>
</ng-template> </ng-template>

View File

@ -6,7 +6,7 @@
&.file-name-column { &.file-name-column {
height: 20px; height: 20px;
width: fit-content; width: 100%;
border-radius: 4px; border-radius: 4px;
font-size: 11px; font-size: 11px;
line-height: 14px; line-height: 14px;
@ -81,6 +81,18 @@
&.file-name-column-input { &.file-name-column-input {
background: transparent; background: transparent;
border: none;
box-shadow: none;
form {
iqser-circle-button {
margin-left: 15px;
@media screen and (max-width: 1395px) {
margin-left: 6px;
}
}
}
} }
&.workflow-edit-input { &.workflow-edit-input {
@ -99,10 +111,6 @@
iqser-circle-button { iqser-circle-button {
margin: 0 5px; margin: 0 5px;
&:nth-child(2) {
padding-left: 10px;
}
&:last-child { &:last-child {
margin-right: -8px; margin-right: -8px;
} }
@ -118,7 +126,6 @@
margin-top: 0; margin-top: 0;
} }
.file-name-input,
.workflow-input { .workflow-input {
width: 100%; width: 100%;
padding-left: 2px; padding-left: 2px;
@ -143,6 +150,10 @@
} }
} }
.file-name-input {
padding-left: 5px;
}
.save { .save {
margin-left: 7px; margin-left: 7px;
} }

View File

@ -1,4 +1,4 @@
import { Component, computed, effect, HostListener, Input, OnDestroy } from '@angular/core'; import { ChangeDetectionStrategy, Component, computed, effect, HostListener, Input, OnDestroy } from '@angular/core';
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain'; import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
import { import {
BaseFormComponent, BaseFormComponent,
@ -43,6 +43,7 @@ import { TranslateModule } from '@ngx-translate/core';
NgTemplateOutlet, NgTemplateOutlet,
TranslateModule, TranslateModule,
], ],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class FileAttributeComponent extends BaseFormComponent implements OnDestroy { export class FileAttributeComponent extends BaseFormComponent implements OnDestroy {
isInEditMode = false; isInEditMode = false;
@ -62,6 +63,12 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
this.file.fileId !== this.fileAttributesService.fileEdit())) || this.file.fileId !== this.fileAttributesService.fileEdit())) ||
!this.fileAttributesService.openAttributeEdits().length, !this.fileAttributesService.openAttributeEdits().length,
); );
@Input() width?: number;
#widthFactor = window.innerWidth >= 1800 ? 0.85 : 0.7;
get editFieldWidth(): string {
return this.width ? `${this.width * this.#widthFactor}px` : 'unset';
}
get isDate(): boolean { get isDate(): boolean {
return this.fileAttribute.type === FileAttributeConfigTypes.DATE; return this.fileAttribute.type === FileAttributeConfigTypes.DATE;
@ -110,6 +117,15 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
); );
} }
@HostListener('window:resize')
onResize() {
if (window.innerWidth >= 1800) {
this.#widthFactor = 0.85;
} else {
this.#widthFactor = 0.7;
}
}
@Debounce(60) @Debounce(60)
@HostListener('document:click', ['$event']) @HostListener('document:click', ['$event'])
clickOutside($event: MouseEvent) { clickOutside($event: MouseEvent) {

View File

@ -179,8 +179,8 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
async #convertData(entityLog: IEntityLog) { async #convertData(entityLog: IEntityLog) {
const file = this._state.file(); const file = this._state.file();
const annotations: AnnotationWrapper[] = []; const annotations: AnnotationWrapper[] = [];
const dictionaries = this._state.dictionaries;
const defaultColors = this._defaultColorsService.find(this._state.dossierTemplateId); const defaultColors = this._defaultColorsService.find(this._state.dossierTemplateId);
let dictionaries = this._state.dictionaries;
let checkDictionary = true; let checkDictionary = true;
for (const entry of entityLog.entityLogEntry) { for (const entry of entityLog.entityLogEntry) {
@ -213,10 +213,13 @@ export class FileDataService extends EntitiesService<AnnotationWrapper, Annotati
let dictionary = dictionaries.find(dict => dict.type === entry.type); let dictionary = dictionaries.find(dict => dict.type === entry.type);
if (!dictionary && checkDictionary) { if (!dictionary && checkDictionary) {
const dictionaryRequest = this._dictionaryService.loadDictionaryDataForDossierTemplate(this._state.dossierTemplateId); const dictionaryRequest = this._dictionaryService.loadDictionaryDataForDossierTemplate(
await firstValueFrom(dictionaryRequest); this._state.dossierTemplateId,
checkDictionary = false; this._state.isReadonly(),
);
dictionaries = await firstValueFrom(dictionaryRequest);
dictionary = dictionaries.find(dict => dict.type === entry.type); dictionary = dictionaries.find(dict => dict.type === entry.type);
checkDictionary = false;
} }
if (!dictionary) { if (!dictionary) {

View File

@ -49,8 +49,6 @@ export class LayersService {
this._documentViewer.document.setLayersArray(layers); this._documentViewer.document.setLayersArray(layers);
this._documentViewer.refreshAndUpdateView(); this._documentViewer.refreshAndUpdateView();
this.active.update(value => !value);
} }
#updateButton() { #updateButton() {

View File

@ -1,5 +1,4 @@
<ng-container *ngIf="componentContext$ | async as ctx"> <div #filenameColumn>
<div>
<div <div
[attr.help-mode-key]="'document_list'" [attr.help-mode-key]="'document_list'"
[class.error]="file.isError" [class.error]="file.isError"
@ -12,6 +11,7 @@
</div> </div>
</div> </div>
<ng-container *ngIf="componentContext$ | async as ctx">
<div *ngIf="ctx.primaryAttribute" class="primary-attribute"> <div *ngIf="ctx.primaryAttribute" class="primary-attribute">
<div class="small-label" *ngIf="file?.softDeletedTime; else editableFileAttribute"> <div class="small-label" *ngIf="file?.softDeletedTime; else editableFileAttribute">
<div> <div>
@ -26,6 +26,7 @@
[dossier]="dossier" [dossier]="dossier"
[fileAttribute]="ctx.primaryAttribute" [fileAttribute]="ctx.primaryAttribute"
[fileNameColumn]="true" [fileNameColumn]="true"
[width]="width"
></redaction-file-attribute> ></redaction-file-attribute>
</ng-template> </ng-template>
</div> </div>

View File

@ -1,4 +1,14 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Input,
OnChanges,
OnInit,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service'; import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service';
import { Dossier, File, IFileAttributeConfig, TrashFile } from '@red/domain'; import { Dossier, File, IFileAttributeConfig, TrashFile } from '@red/domain';
import { FileAttributesService } from '@services/entity-services/file-attributes.service'; import { FileAttributesService } from '@services/entity-services/file-attributes.service';
@ -18,15 +28,18 @@ interface FileNameColumnContext {
}) })
export class FileNameColumnComponent extends ContextComponent<FileNameColumnContext> implements OnInit, OnChanges { export class FileNameColumnComponent extends ContextComponent<FileNameColumnContext> implements OnInit, OnChanges {
readonly #reloadAttribute = new ReplaySubject<void>(1); readonly #reloadAttribute = new ReplaySubject<void>(1);
@ViewChild('filenameColumn', { static: true }) filenameColumn: ElementRef;
@Input() file?: File | TrashFile; @Input() file?: File | TrashFile;
@Input() dossier: Dossier; @Input() dossier: Dossier;
@Input() dossierTemplateId: string; @Input() dossierTemplateId: string;
ocrByDefault: boolean; ocrByDefault: boolean;
width: number;
constructor( constructor(
private readonly _fileAttributeService: FileAttributesService, private readonly _fileAttributeService: FileAttributesService,
private readonly _primaryFileAttributeService: PrimaryFileAttributeService, private readonly _primaryFileAttributeService: PrimaryFileAttributeService,
private readonly _dossierTemplateService: DossierTemplatesService, private readonly _dossierTemplateService: DossierTemplatesService,
private readonly _changeDetectorRef: ChangeDetectorRef,
) { ) {
super(); super();
} }
@ -37,6 +50,10 @@ export class FileNameColumnComponent extends ContextComponent<FileNameColumnCont
map(() => this.#findPrimaryAttribute()), map(() => this.#findPrimaryAttribute()),
); );
super._initContext({ primaryAttribute: primaryAttribute$ }); super._initContext({ primaryAttribute: primaryAttribute$ });
const _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
this.#updateItemWidth(entries[0]);
});
_observer.observe(this.filenameColumn.nativeElement);
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
@ -57,4 +74,9 @@ export class FileNameColumnComponent extends ContextComponent<FileNameColumnCont
const fileAttributes = this._fileAttributeService.getFileAttributeConfig(this.dossierTemplateId); const fileAttributes = this._fileAttributeService.getFileAttributeConfig(this.dossierTemplateId);
return fileAttributes?.fileAttributeConfigs.find(a => a.primaryAttribute); return fileAttributes?.fileAttributeConfigs.find(a => a.primaryAttribute);
} }
#updateItemWidth(resizeObserverEntry: ResizeObserverEntry) {
this.width = resizeObserverEntry.contentRect.width;
this._changeDetectorRef.markForCheck();
}
} }

View File

@ -11,6 +11,8 @@ import { DashboardStatsService } from '../dossier-templates/dashboard-stats.serv
import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
import { List } from '@iqser/common-ui/lib/utils'; import { List } from '@iqser/common-ui/lib/utils';
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service'; import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
import { Router } from '@angular/router';
import { filterEventsOnPages } from '@utils/operators';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class DossiersChangesService extends GenericService<Dossier> implements OnDestroy { export class DossiersChangesService extends GenericService<Dossier> implements OnDestroy {
@ -20,6 +22,7 @@ export class DossiersChangesService extends GenericService<Dossier> implements O
readonly #dashboardStatsService = inject(DashboardStatsService); readonly #dashboardStatsService = inject(DashboardStatsService);
readonly #logger = inject(NGXLogger); readonly #logger = inject(NGXLogger);
readonly #dossierCache = inject(DossiersCacheService); readonly #dossierCache = inject(DossiersCacheService);
readonly #router = inject(Router);
protected readonly _defaultModelPath = 'dossier'; protected readonly _defaultModelPath = 'dossier';
loadOnlyChanged(): Observable<IDossierChanges> { loadOnlyChanged(): Observable<IDossierChanges> {
@ -60,6 +63,7 @@ export class DossiersChangesService extends GenericService<Dossier> implements O
initialize() { initialize() {
this.#logger.info('[DOSSIERS_CHANGES] Initialize timer'); this.#logger.info('[DOSSIERS_CHANGES] Initialize timer');
const subscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL).pipe( const subscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL).pipe(
filterEventsOnPages(this.#router),
switchMap(() => this.loadOnlyChanged()), switchMap(() => this.loadOnlyChanged()),
tap(changes => { tap(changes => {
this.#activeDossiersService.emitFileChanges(changes); this.#activeDossiersService.emitFileChanges(changes);

View File

@ -48,8 +48,14 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
); );
} }
getAllDictionaries(dossierTemplateId: string, dossierId?: string) { getAllDictionaries(dossierTemplateId: string, readOnly = false, dossierId?: string) {
const queryParams = dossierId ? [{ key: 'dossierId', value: dossierId }] : undefined; const queryParams = [];
if (dossierId) {
queryParams.push({ key: 'dossierId', value: dossierId });
}
if (readOnly) {
queryParams.push({ key: 'includeDeleted', value: true });
}
return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams); return this._getOne<{ types: IDictionary[] }>(['type', dossierTemplateId], this._defaultModelPath, queryParams);
} }
@ -237,8 +243,8 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
return forkJoin(observables); return forkJoin(observables);
} }
loadDictionaryDataForDossierTemplate(dossierTemplateId: string): Observable<Dictionary[]> { loadDictionaryDataForDossierTemplate(dossierTemplateId: string, readOnlyFile = false): Observable<Dictionary[]> {
return this.getAllDictionaries(dossierTemplateId).pipe( return this.getAllDictionaries(dossierTemplateId, readOnlyFile).pipe(
map(typesResponse => typesResponse.types.map(type => new Dictionary(type))), map(typesResponse => typesResponse.types.map(type => new Dictionary(type))),
map(dictionaries => { map(dictionaries => {
let manualTypeExists = false; let manualTypeExists = false;

View File

@ -12,6 +12,8 @@ import { DossiersCacheService } from './dossiers/dossiers-cache.service';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { List, mapEach } from '@iqser/common-ui/lib/utils'; import { List, mapEach } from '@iqser/common-ui/lib/utils';
import { APP_BASE_HREF } from '@angular/common'; import { APP_BASE_HREF } from '@angular/common';
import { Router } from '@angular/router';
import { filterEventsOnPages } from '@utils/operators';
const INCLUDE_SEEN = false; const INCLUDE_SEEN = false;
@ -33,6 +35,7 @@ export class NotificationsService extends EntitiesService<INotification, Notific
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _userService: UserService, private readonly _userService: UserService,
private readonly _dossiersCacheService: DossiersCacheService, private readonly _dossiersCacheService: DossiersCacheService,
private readonly _router: Router,
) { ) {
super(); super();
@ -81,35 +84,39 @@ export class NotificationsService extends EntitiesService<INotification, Notific
const downloadHref = `${this.#appBaseHref}/main/downloads`; const downloadHref = `${this.#appBaseHref}/main/downloads`;
return this._translateService.instant(translation, { return this._translateService.instant(translation, {
fileHref: this._getFileHref(dossier, fileId), fileHref: this.#getFileHref(dossier, fileId),
dossierHref: this._getDossierHref(dossier), dossierHref: this.#getDossierHref(dossier),
dossierName: dossierName ?? this._translateService.instant(_('notifications.deleted-dossier')), dossierName: dossierName ?? this._translateService.instant(_('notifications.deleted-dossier')),
fileName: fileName ?? this._translateService.instant(_('file')), fileName: fileName ?? this._translateService.instant(_('file')),
user: this._getUsername(notification.userId), user: this.#getUsername(notification.userId),
downloadHref: downloadHref, downloadHref: downloadHref,
}); });
} }
private _getFileHref(dossier: Dossier, fileId: string): string { #getFileHref(dossier: Dossier, fileId: string): string {
return dossier ? `${this._getDossierHref(dossier)}/file/${fileId}` : null; return dossier ? `${this.#getDossierHref(dossier)}/file/${fileId}` : null;
} }
private _getDossierHref(dossier: Dossier): string { #getDossierHref(dossier: Dossier): string {
return dossier ? `${dossier.routerLink}` : null; return dossier ? `${dossier.routerLink}` : null;
} }
private _getUsername(userId: string | undefined) { #getUsername(userId: string | undefined) {
return this._userService.getName(userId) || this._translateService.instant(_('unknown')); return this._userService.getName(userId) || this._translateService.instant(_('unknown'));
} }
#initTimerAndChanges() { #initTimerAndChanges() {
const timer$ = timer(0, CHANGED_CHECK_INTERVAL).pipe( const timer$ = timer(0, CHANGED_CHECK_INTERVAL).pipe(
filterEventsOnPages(this._router),
switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))), switchMap(() => (this._dossiersCacheService.empty ? this._dossiersCacheService.load() : of(null))),
switchMap(() => this.#loadNotificationsIfChanged()), switchMap(() => this.#loadNotificationsIfChanged()),
); );
// Rebuild notifications when cached dossiers are updated // Rebuild notifications when cached dossiers are updated
const changed$ = this._dossiersCacheService.changed$.pipe(tap(() => this.setEntities(this.all.map(e => this._new(e))))); const changed$ = this._dossiersCacheService.changed$.pipe(
filterEventsOnPages(this._router),
tap(() => this.setEntities(this.all.map(e => this._new(e)))),
);
return merge(timer$, changed$); return merge(timer$, changed$);
} }

View File

@ -1,6 +1,7 @@
import { DynamicCaches } from '@iqser/common-ui'; import { DynamicCaches } from '@iqser/common-ui';
export const CHANGED_CHECK_INTERVAL = 5000; export const CHANGED_CHECK_INTERVAL = 5000;
export const NO_CHECK_PAGES = ['admin', 'account', 'downloads', 'trash'];
export const FALLBACK_COLOR = '#CCCCCC'; export const FALLBACK_COLOR = '#CCCCCC';
export const UI_CACHES: DynamicCaches = [ export const UI_CACHES: DynamicCaches = [

View File

@ -0,0 +1,7 @@
import { filter } from 'rxjs/operators';
import { NO_CHECK_PAGES } from '@utils/constants';
import { Router } from '@angular/router';
export function filterEventsOnPages(router: Router) {
return filter(() => !NO_CHECK_PAGES.some(route => router.url.includes(route)));
}