red-ui/apps/red-ui/src/app/modules/dossier-overview/screen/dossier-overview-screen.component.ts
2024-08-10 14:18:22 +02:00

281 lines
12 KiB
TypeScript

import { AsyncPipe, NgIf } from '@angular/common';
import { Component, ElementRef, HostListener, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import {
CircleButtonTypes,
CustomError,
ErrorService,
HasScrollbarDirective,
IqserListingModule,
IqserPermissionsService,
ListingComponent,
ListingModes,
listingProvidersFactory,
LoadingService,
TableColumnConfig,
TableComponent,
WorkflowConfig,
} from '@iqser/common-ui';
import { NestedFilter } from '@iqser/common-ui/lib/filtering';
import { getParam, OnAttach, OnDetach, shareLast } from '@iqser/common-ui/lib/utils';
import { TranslateModule } from '@ngx-translate/core';
import {
Dossier,
DOSSIER_ID,
DossierAttributeConfig,
DossierAttributeWithValue,
File,
IFileAttributeConfig,
WorkflowFileStatus,
} from '@red/domain';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
import { DossiersService } from '@services/dossiers/dossiers.service';
import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service';
import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { FilesMapService } from '@services/files/files-map.service';
import { FilesService } from '@services/files/files.service';
import { PermissionsService } from '@services/permissions.service';
import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.component';
import { FileUploadModel } from '@upload-download/model/file-upload.model';
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
import { FileUploadService } from '@upload-download/services/file-upload.service';
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service';
import { convertFiles, Files, handleFileDrop } from '@utils/index';
import { merge, Observable } from 'rxjs';
import { filter, skip, switchMap, tap } from 'rxjs/operators';
import { DossierOverviewBulkActionsComponent } from '../components/bulk-actions/dossier-overview-bulk-actions.component';
import { DossierDetailsComponent } from '../components/dossier-details/dossier-details.component';
import { DossierOverviewScreenHeaderComponent } from '../components/screen-header/dossier-overview-screen-header.component';
import { TableItemComponent } from '../components/table-item/table-item.component';
import { WorkflowItemComponent } from '../components/workflow-item/workflow-item.component';
import { ConfigService } from '../config.service';
import { BulkActionsService } from '../services/bulk-actions.service';
@Component({
templateUrl: './dossier-overview-screen.component.html',
styleUrls: ['./dossier-overview-screen.component.scss'],
providers: [...listingProvidersFactory(DossierOverviewScreenComponent), ConfigService, BulkActionsService, dossiersServiceProvider],
standalone: true,
imports: [
DossierOverviewScreenHeaderComponent,
AsyncPipe,
IqserListingModule,
TranslateModule,
NgIf,
WorkflowItemComponent,
DossierDetailsComponent,
DossierOverviewBulkActionsComponent,
TableItemComponent,
TypeFilterComponent,
HasScrollbarDirective,
],
})
export default class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnAttach, OnDetach, OnDestroy {
#dossier: Dossier;
@ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true })
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
@ViewChild('fileInput', { static: true }) private readonly _fileInput: ElementRef;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
#fileAttributeConfigs: IFileAttributeConfig[];
readonly listingModes = ListingModes;
readonly circleButtonTypes = CircleButtonTypes;
readonly tableHeaderLabel = _('dossier-overview.table-header.title');
collapsedDetails = false;
dossierAttributes: DossierAttributeWithValue[] = [];
tableColumnConfigs: readonly TableColumnConfig<File>[];
displayedInFileListAttributes: IFileAttributeConfig[] = [];
displayedAttributes: IFileAttributeConfig[] = [];
displayedWorkflowAttributes: IFileAttributeConfig[] = [];
readonly listingMode$ = this.configService.listingMode$.pipe(
tap(() => this.#computeAllFilters()),
shareLast(),
);
readonly dossier$: Observable<Dossier>;
readonly files$: Observable<File[]>;
readonly dossierId = getParam(DOSSIER_ID);
readonly dossierTemplateId: string;
readonly workflowConfig: WorkflowConfig<File, WorkflowFileStatus>;
readonly dossierAttributes$: Observable<DossierAttributeConfig[]>;
constructor(
readonly configService: ConfigService,
private readonly _errorService: ErrorService,
private readonly _filesService: FilesService,
readonly permissionsService: PermissionsService,
iqserPermissionsService: IqserPermissionsService,
private readonly _loadingService: LoadingService,
private readonly _fileMapService: FilesMapService,
private readonly _dossiersService: DossiersService,
private readonly _fileUploadService: FileUploadService,
private readonly _statusOverlayService: StatusOverlayService,
private readonly _fileAttributesService: FileAttributesService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _fileDropOverlayService: FileDropOverlayService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _dossierAttributesService: DossierAttributesService,
private readonly _dossiersCacheService: DossiersCacheService,
) {
super();
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId).pipe(tap(dossier => (this.#dossier = dossier)));
this.dossierAttributes$ = this._dossierAttributesService.all$.pipe(tap(() => this.#updateDossierAttributes()));
this.#dossier = _dossiersService.find(this.dossierId);
this.dossierTemplateId = this.#dossier.dossierTemplateId;
const hasRss = iqserPermissionsService.has(Roles.getRss);
this.workflowConfig = hasRss ? configService.workflowConfigRss() : configService.workflowConfig();
this.files$ = merge(this.#files$, this.#dossierFilesChange$).pipe(shareLast());
this.#updateFileAttributes();
}
get checkedRequiredFilters(): NestedFilter[] {
return this.filterService.getGroup('quickFilters')?.filters.filter(f => f.required && f.checked);
}
get checkedNotRequiredFilters(): NestedFilter[] {
return this.filterService.getGroup('quickFilters')?.filters.filter(f => !f.required && f.checked);
}
get #files$() {
return this._fileMapService.get$(this.dossierId).pipe(
tap(files => this.entitiesService.setEntities(files)),
tap(() => this.#computeAllFilters()),
);
}
get #dossierFilesChange$() {
return this._dossiersService.dossierFileChanges$.pipe(
filter(dossierId => dossierId === this.dossierId && !!this._dossiersCacheService.get(dossierId)),
switchMap(dossierId => this._filesService.loadAll(dossierId)),
);
}
disabledFn = (file: File) => file.excluded;
lastOpenedFn = (file: File) => this._userPreferenceService.getLastOpenedFileForDossier(file.dossierId) === file.id;
async ngOnInit(): Promise<void> {
this.#computeAllFilters();
this.#setRemovableSubscriptions();
this.#initFileDropHandling();
this._loadingService.stop();
}
ngOnAttach() {
this.#initFileDropHandling();
this.#setRemovableSubscriptions();
this.#updateFileAttributes();
this._tableComponent?.scrollToLastIndex();
}
override ngOnDetach() {
this.#cleanupFileDropHandling();
super.ngOnDetach();
}
override ngOnDestroy() {
this.#cleanupFileDropHandling();
super.ngOnDestroy();
}
@HostListener('drop', ['$event'])
onDrop(event: DragEvent): void {
if (this.permissionsService.canUploadFiles(this.#dossier)) {
handleFileDrop(event, this.#dossier, this.#uploadFiles.bind(this));
}
}
@HostListener('dragover', ['$event'])
onDragOver(event): void {
event.stopPropagation();
event.preventDefault();
}
async uploadFiles(files: Files): Promise<void> {
await this.#uploadFiles(convertFiles(files, this.#dossier));
(this._fileInput as any).nativeElement.value = null;
}
#cleanupFileDropHandling() {
if (this.permissionsService.canUploadFiles(this.#dossier)) {
this._fileDropOverlayService.cleanupFileDropHandling();
}
}
#initFileDropHandling(): void {
if (this.permissionsService.canUploadFiles(this.#dossier)) {
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
}
}
async #updateDossierAttributes(): Promise<void> {
try {
this.dossierAttributes = await this._dossierAttributesService.getWithValues(this.#dossier);
} catch (e) {
console.error('[DOSSIER ATTRIBUTES] Error: ', e);
}
}
#setRemovableSubscriptions(): void {
this.addActiveScreenSubscription = this._dossiersService
.getEntityDeleted$(this.dossierId)
.pipe(tap(() => this.#handleDeletedDossier()))
.subscribe();
this.addActiveScreenSubscription = this._dossierTemplatesService
.getEntityChanged$(this.#dossier.dossierTemplateId)
.pipe(
skip(1),
tap(() => this.#updateFileAttributes()),
)
.subscribe();
}
#handleDeletedDossier(): void {
this._errorService.set(
new CustomError(_('error.deleted-entity.dossier.label'), _('error.deleted-entity.dossier.action'), 'iqser:expand'),
);
}
#updateFileAttributes() {
const attributes = this._fileAttributesService.getFileAttributeConfig(this.#dossier.dossierTemplateId);
this.#fileAttributeConfigs = attributes?.fileAttributeConfigs || [];
this.displayedInFileListAttributes = this.#fileAttributeConfigs.filter(config => config.displayedInFileList);
this.displayedAttributes = this.displayedInFileListAttributes.filter(c => c.displayedInFileList);
this.displayedWorkflowAttributes = this.#getDisplayedWorkflowAttributes(this.displayedAttributes);
this.tableColumnConfigs = this.configService.tableConfig(this.displayedAttributes);
this.#computeAllFilters();
}
#getDisplayedWorkflowAttributes(displayedAttributes: IFileAttributeConfig[]): IFileAttributeConfig[] {
let primaryAttribute = displayedAttributes.find(c => c.primaryAttribute);
if (!primaryAttribute) {
primaryAttribute = this.#fileAttributeConfigs.find(config => config.primaryAttribute);
return primaryAttribute ? [primaryAttribute, ...displayedAttributes] : displayedAttributes;
}
return displayedAttributes.sort((c1, c2) => (c1.primaryAttribute ? -1 : c2.primaryAttribute ? 1 : 0));
}
async #uploadFiles(files: FileUploadModel[]) {
const fileCount = await this._fileUploadService.uploadFiles(files, this.dossierId);
if (fileCount) {
this._statusOverlayService.openUploadStatusOverlay();
}
}
#computeAllFilters() {
const filterGroups = this.configService.filterGroups(
this.entitiesService.all,
this.#fileAttributeConfigs,
this.#dossier.dossierTemplateId,
this._needsWorkFilterTemplate,
() => this.checkedRequiredFilters,
() => this.checkedNotRequiredFilters,
);
this.filterService.addFilterGroups(filterGroups, true);
}
}