RED-10139: removed getters from template and component refactoring.
for dossier overview.
This commit is contained in:
parent
4bf2e79c57
commit
b7ff80ecac
@ -1,9 +1,11 @@
|
||||
<ng-container (longPress)="forceReanalysisAction($event)" *ngIf="selectedFiles.length" redactionLongPress>
|
||||
<redaction-expandable-file-actions
|
||||
[actions]="buttons"
|
||||
[buttonType]="buttonType"
|
||||
[maxWidth]="maxWidth"
|
||||
[tooltipPosition]="IqserTooltipPositions.above"
|
||||
>
|
||||
</redaction-expandable-file-actions>
|
||||
</ng-container>
|
||||
@if (selectedFiles().length) {
|
||||
<ng-container (longPress)="forceReanalysisAction($event)" redactionLongPress>
|
||||
<redaction-expandable-file-actions
|
||||
[actions]="buttons"
|
||||
[buttonType]="buttonType()"
|
||||
[maxWidth]="maxWidth()"
|
||||
[tooltipPosition]="IqserTooltipPositions.above"
|
||||
>
|
||||
</redaction-expandable-file-actions>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { Component, input, OnChanges } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { CircleButtonType, CircleButtonTypes } from '@iqser/common-ui';
|
||||
import { Action, ActionTypes, Dossier, File, ProcessingFileStatuses } from '@red/domain';
|
||||
@ -8,14 +8,13 @@ import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { BulkActionsService } from '../../services/bulk-actions.service';
|
||||
import { ExpandableFileActionsComponent } from '@shared/components/expandable-file-actions/expandable-file-actions.component';
|
||||
import { IqserTooltipPositions } from '@common-ui/utils';
|
||||
import { NgIf } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-overview-bulk-actions [dossier] [selectedFiles]',
|
||||
templateUrl: './dossier-overview-bulk-actions.component.html',
|
||||
styleUrls: ['./dossier-overview-bulk-actions.component.scss'],
|
||||
standalone: true,
|
||||
imports: [LongPressDirective, ExpandableFileActionsComponent, NgIf],
|
||||
imports: [LongPressDirective, ExpandableFileActionsComponent],
|
||||
})
|
||||
export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
#analysisForced: boolean;
|
||||
@ -37,10 +36,10 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
#toggleAnalysisTooltip: string;
|
||||
#allFilesAreExcluded: boolean;
|
||||
#canMoveToSameState: boolean;
|
||||
@Input() dossier: Dossier;
|
||||
@Input() selectedFiles: File[];
|
||||
@Input() buttonType: CircleButtonType = CircleButtonTypes.default;
|
||||
@Input() maxWidth: number;
|
||||
readonly dossier = input<Dossier>();
|
||||
readonly selectedFiles = input<File[]>();
|
||||
readonly buttonType = input<CircleButtonType>(CircleButtonTypes.default);
|
||||
readonly maxWidth = input<number>();
|
||||
buttons: Action[];
|
||||
readonly IqserTooltipPositions = IqserTooltipPositions;
|
||||
|
||||
@ -50,12 +49,12 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
private readonly _bulkActionsService: BulkActionsService,
|
||||
) {}
|
||||
|
||||
private get _buttons(): Action[] {
|
||||
get #buttons(): Action[] {
|
||||
const actions: Action[] = [
|
||||
{
|
||||
id: 'delete-files-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.delete(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.delete(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.bulk.delete'),
|
||||
icon: 'iqser:trash',
|
||||
show: this.#canDelete,
|
||||
@ -63,7 +62,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'assign-files-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.assign(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.assign(this.selectedFiles()),
|
||||
tooltip: this.#assignTooltip,
|
||||
icon: 'red:assign',
|
||||
show: this.#canAssign,
|
||||
@ -71,7 +70,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'assign-files-to-me-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.assignToMe(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.assignToMe(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.assign-me'),
|
||||
icon: 'red:assign-me',
|
||||
show: this.#canAssignToSelf,
|
||||
@ -79,7 +78,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'to-new-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.setToNew(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.setToNew(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.back-to-new'),
|
||||
icon: 'red:undo',
|
||||
show: this.#canSetToNew,
|
||||
@ -87,7 +86,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'to-under-approval-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.under-approval'),
|
||||
icon: 'red:ready-for-approval',
|
||||
show: this.#canSetToUnderApproval,
|
||||
@ -95,7 +94,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'to-under-review-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.backToUnderReview(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.backToUnderReview(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.under-review'),
|
||||
icon: 'red:undo',
|
||||
show: this.#canSetToUnderReview,
|
||||
@ -103,14 +102,14 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'download-files-btn',
|
||||
type: ActionTypes.downloadBtn,
|
||||
show: !this.selectedFiles.some(file => file.processingStatus === ProcessingFileStatuses.ERROR || !file.lastProcessed),
|
||||
files: this.selectedFiles,
|
||||
dossier: this.dossier,
|
||||
show: !this.selectedFiles().some(file => file.processingStatus === ProcessingFileStatuses.ERROR || !file.lastProcessed),
|
||||
files: this.selectedFiles(),
|
||||
dossier: this.dossier(),
|
||||
},
|
||||
{
|
||||
id: 'approve-files-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.approve(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.approve(this.selectedFiles()),
|
||||
disabled: !this.#canApprove,
|
||||
tooltip: this.#canApprove ? _('dossier-overview.approve') : _('dossier-overview.approve-disabled'),
|
||||
icon: 'red:approved',
|
||||
@ -119,7 +118,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'set-under-approval-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.setToUnderApproval(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.under-approval'),
|
||||
icon: 'red:undo',
|
||||
show: this.#canUndoApproval,
|
||||
@ -127,7 +126,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'ocr-files-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.ocr(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.ocr(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.ocr-file'),
|
||||
icon: 'iqser:ocr',
|
||||
show: this.#canOcr,
|
||||
@ -135,17 +134,17 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'reanalyse-files-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.reanalyse(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.reanalyse(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.bulk.reanalyse'),
|
||||
icon: 'iqser:refresh',
|
||||
show:
|
||||
this.#canReanalyse &&
|
||||
(this.#analysisForced || this.#canEnableAutoAnalysis || this.selectedFiles.every(file => file.isError)),
|
||||
(this.#analysisForced || this.#canEnableAutoAnalysis || this.selectedFiles().every(file => file.isError)),
|
||||
},
|
||||
{
|
||||
id: 'stop-automatic-analysis-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.stop-auto-analysis'),
|
||||
icon: 'red:disable-analysis',
|
||||
show: this.#canDisableAutoAnalysis,
|
||||
@ -153,7 +152,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'start-automatic-analysis-btn',
|
||||
type: ActionTypes.circleBtn,
|
||||
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles),
|
||||
action: () => this._bulkActionsService.toggleAutomaticAnalysis(this.selectedFiles()),
|
||||
tooltip: _('dossier-overview.start-auto-analysis'),
|
||||
icon: 'red:enable-analysis',
|
||||
show: this.#canEnableAutoAnalysis,
|
||||
@ -161,7 +160,7 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
{
|
||||
id: 'toggle-analysis-btn',
|
||||
type: ActionTypes.toggle,
|
||||
action: () => this._bulkActionsService.toggleAnalysis(this.selectedFiles, !this.#allFilesAreExcluded),
|
||||
action: () => this._bulkActionsService.toggleAnalysis(this.selectedFiles(), !this.#allFilesAreExcluded),
|
||||
tooltip: this.#toggleAnalysisTooltip,
|
||||
checked: !this.#allFilesAreExcluded,
|
||||
show: this.#canToggleAnalysis,
|
||||
@ -172,56 +171,56 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this._setup();
|
||||
this.#setup();
|
||||
}
|
||||
|
||||
forceReanalysisAction($event: LongPressEvent) {
|
||||
this.#analysisForced = !$event.touchEnd && this._userPreferenceService.isIqserDevMode;
|
||||
this._setup();
|
||||
this.#setup();
|
||||
}
|
||||
|
||||
private _setup() {
|
||||
if (!this.selectedFiles.length) {
|
||||
#setup() {
|
||||
if (!this.selectedFiles().length) {
|
||||
return;
|
||||
}
|
||||
const allFilesAreUnderReviewOrUnassigned = this.selectedFiles.reduce(
|
||||
const allFilesAreUnderReviewOrUnassigned = this.selectedFiles().reduce(
|
||||
(acc, file) => acc && (file.isUnderReview || file.isNew),
|
||||
true,
|
||||
);
|
||||
const allFilesAreUnderApproval = this.selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true);
|
||||
const allFilesAreApproved = this.selectedFiles.reduce((acc, file) => acc && file.isApproved, true);
|
||||
this.#allFilesAreExcluded = this.selectedFiles.reduce((acc, file) => acc && file.excluded, true);
|
||||
const allFilesAreUnderApproval = this.selectedFiles().reduce((acc, file) => acc && file.isUnderApproval, true);
|
||||
const allFilesAreApproved = this.selectedFiles().reduce((acc, file) => acc && file.isApproved, true);
|
||||
this.#allFilesAreExcluded = this.selectedFiles().reduce((acc, file) => acc && file.excluded, true);
|
||||
this.#canMoveToSameState = allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval || allFilesAreApproved;
|
||||
|
||||
this.#canAssign =
|
||||
this.#canMoveToSameState &&
|
||||
(this._permissionsService.canAssignUser(this.selectedFiles, this.dossier) ||
|
||||
this._permissionsService.canUnassignUser(this.selectedFiles, this.dossier));
|
||||
this.#canAssignToSelf = this.#canMoveToSameState && this._permissionsService.canAssignToSelf(this.selectedFiles, this.dossier);
|
||||
(this._permissionsService.canAssignUser(this.selectedFiles(), this.dossier()) ||
|
||||
this._permissionsService.canUnassignUser(this.selectedFiles(), this.dossier()));
|
||||
this.#canAssignToSelf = this.#canMoveToSameState && this._permissionsService.canAssignToSelf(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canDelete = this._permissionsService.canSoftDeleteFile(this.selectedFiles, this.dossier);
|
||||
this.#canDelete = this._permissionsService.canSoftDeleteFile(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canReanalyse = this._permissionsService.canReanalyseFile(this.selectedFiles, this.dossier);
|
||||
this.#canReanalyse = this._permissionsService.canReanalyseFile(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis(this.selectedFiles, this.dossier);
|
||||
this.#canDisableAutoAnalysis = this._permissionsService.canDisableAutoAnalysis(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis(this.selectedFiles, this.dossier);
|
||||
this.#canEnableAutoAnalysis = this._permissionsService.canEnableAutoAnalysis(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canToggleAnalysis = this._permissionsService.canToggleAnalysis(this.selectedFiles, this.dossier);
|
||||
this.#canToggleAnalysis = this._permissionsService.canToggleAnalysis(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canOcr = this._permissionsService.canOcrFile(this.selectedFiles, this.dossier);
|
||||
this.#canOcr = this._permissionsService.canOcrFile(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canSetToNew = this._permissionsService.canSetToNew(this.selectedFiles, this.dossier);
|
||||
this.#canSetToNew = this._permissionsService.canSetToNew(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canSetToUnderReview = this._permissionsService.canSetUnderReview(this.selectedFiles, this.dossier);
|
||||
this.#canSetToUnderReview = this._permissionsService.canSetUnderReview(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canSetToUnderApproval = this._permissionsService.canSetUnderApproval(this.selectedFiles, this.dossier);
|
||||
this.#canSetToUnderApproval = this._permissionsService.canSetUnderApproval(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#isReadyForApproval = this._permissionsService.isReadyForApproval(this.selectedFiles, this.dossier);
|
||||
this.#isReadyForApproval = this._permissionsService.isReadyForApproval(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canApprove = this._permissionsService.canBeApproved(this.selectedFiles, this.dossier);
|
||||
this.#canApprove = this._permissionsService.canBeApproved(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#canUndoApproval = this._permissionsService.canUndoApproval(this.selectedFiles, this.dossier);
|
||||
this.#canUndoApproval = this._permissionsService.canUndoApproval(this.selectedFiles(), this.dossier());
|
||||
|
||||
this.#assignTooltip = allFilesAreUnderApproval ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer');
|
||||
|
||||
@ -229,6 +228,6 @@ export class DossierOverviewBulkActionsComponent implements OnChanges {
|
||||
? _('file-preview.toggle-analysis.enable')
|
||||
: _('file-preview.toggle-analysis.disable');
|
||||
|
||||
this.buttons = this._buttons;
|
||||
this.buttons = this.#buttons;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,81 +1,97 @@
|
||||
<ng-container *ngIf="dossierStats$ | async as stats">
|
||||
@if (dossierStats$ | async; as stats) {
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:document"></mat-icon>
|
||||
<span>{{ 'dossier-overview.dossier-details.stats.documents' | translate : { count: stats.numberOfFiles } }}</span>
|
||||
<span>{{ 'dossier-overview.dossier-details.stats.documents' | translate: { count: stats.numberOfFiles } }}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="stats.numberOfProcessingFiles">
|
||||
<mat-icon svgIcon="red:reanalyse"></mat-icon>
|
||||
<span>{{
|
||||
'dossier-overview.dossier-details.stats.processing-documents' | translate : { count: stats.numberOfProcessingFiles }
|
||||
}}</span>
|
||||
</div>
|
||||
@if (stats.numberOfProcessingFiles) {
|
||||
<div>
|
||||
<mat-icon svgIcon="red:reanalyse"></mat-icon>
|
||||
<span>{{
|
||||
'dossier-overview.dossier-details.stats.processing-documents' | translate: { count: stats.numberOfProcessingFiles }
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<span>{{ 'dossier-overview.dossier-details.stats.people' | translate : { count: dossier.memberIds.length } }}</span>
|
||||
<span>{{ 'dossier-overview.dossier-details.stats.people' | translate: { count: dossier().memberIds.length } }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:pages"></mat-icon>
|
||||
<span>{{ 'dossier-overview.dossier-details.stats.analysed-pages' | translate : { count: stats.numberOfPages | number } }}</span>
|
||||
<span>{{ 'dossier-overview.dossier-details.stats.analysed-pages' | translate: { count: stats.numberOfPages | number } }}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="dossier.date | date : 'd MMM yyyy' as date">
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
<span [innerHTML]="'dossier-overview.dossier-details.stats.created-on' | translate : { date }"></span>
|
||||
</div>
|
||||
@if (dossier().date | date: 'd MMM yyyy'; as date) {
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:calendar"></mat-icon>
|
||||
<span [innerHTML]="'dossier-overview.dossier-details.stats.created-on' | translate: { date }"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div *ngIf="dossier.dueDate | date : 'd MMM yyyy' as dueDate">
|
||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||
<span [innerHTML]="'dossier-overview.dossier-details.stats.due-date' | translate : { date: dueDate }"></span>
|
||||
</div>
|
||||
@if (dossier().dueDate | date: 'd MMM yyyy'; as dueDate) {
|
||||
<div>
|
||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||
<span [innerHTML]="'dossier-overview.dossier-details.stats.due-date' | translate: { date: dueDate }"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<mat-icon svgIcon="red:template"></mat-icon>
|
||||
<span>{{ dossierTemplateName }} </span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
(click)="openEditDossierDialog('dossierDictionary')"
|
||||
*ngIf="!isDocumine"
|
||||
[attr.help-mode-key]="'edit_dossier_dossier_dictionary'"
|
||||
class="link-property"
|
||||
>
|
||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||
<span>{{ 'dossier-overview.dossier-details.dictionary' | translate }} </span>
|
||||
</div>
|
||||
@if (!isDocumine) {
|
||||
<div
|
||||
(click)="openEditDossierDialog('dossierDictionary')"
|
||||
[attr.help-mode-key]="'edit_dossier_dossier_dictionary'"
|
||||
class="link-property"
|
||||
>
|
||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||
<span>{{ 'dossier-overview.dossier-details.dictionary' | translate }} </span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!--TODO: Navigate to trash with filter on click?-->
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:trash"></mat-icon>
|
||||
<span>{{ 'dossier-overview.dossier-details.stats.deleted' | translate : { count: stats.numberOfSoftDeletedFiles } }}</span>
|
||||
<span>{{ 'dossier-overview.dossier-details.stats.deleted' | translate: { count: stats.numberOfSoftDeletedFiles } }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<ng-container *ngIf="dossierAttributes?.length">
|
||||
<div
|
||||
(click)="attributesExpanded = true"
|
||||
*ngIf="!attributesExpanded"
|
||||
[attr.help-mode-key]="'edit_dossier_dossier_attributes'"
|
||||
class="all-caps-label show-attributes"
|
||||
>
|
||||
{{ 'dossier-overview.dossier-details.attributes.expand' | translate : { count: dossierAttributes.length } }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="attributesExpanded" [attr.help-mode-key]="'edit_dossier_dossier_attributes'" class="attributes">
|
||||
<div (click)="openEditDossierDialog('dossierAttributes')" *ngFor="let attr of dossierAttributes" class="link-property">
|
||||
<mat-icon svgIcon="red:attribute"></mat-icon>
|
||||
<span *ngIf="!attr.value"> {{ attr.label + ': -' }}</span>
|
||||
<span *ngIf="attr.value && attr.type === 'DATE'"> {{ attr.label + ': ' + (attr.value | date : 'd MMM yyyy') }}</span>
|
||||
<span *ngIf="attr.value && attr.type === 'IMAGE'">
|
||||
{{ attr.label + ': ' + ('dossier-overview.dossier-details.attributes.image-uploaded' | translate) }}</span
|
||||
>
|
||||
<span *ngIf="attr.value && (attr.type === 'TEXT' || attr.type === 'NUMBER')"> {{ attr.label + ': ' + attr.value }}</span>
|
||||
@if (dossierAttributes()?.length) {
|
||||
@if (!attributesExpanded) {
|
||||
<div
|
||||
(click)="attributesExpanded = true"
|
||||
[attr.help-mode-key]="'edit_dossier_dossier_attributes'"
|
||||
class="all-caps-label show-attributes"
|
||||
>
|
||||
{{ 'dossier-overview.dossier-details.attributes.expand' | translate: { count: dossierAttributes().length } }}
|
||||
</div>
|
||||
} @else {
|
||||
<div [attr.help-mode-key]="'edit_dossier_dossier_attributes'" class="attributes">
|
||||
@for (attr of dossierAttributes(); track attr.id) {
|
||||
<div (click)="openEditDossierDialog('dossierAttributes')" class="link-property">
|
||||
<mat-icon svgIcon="red:attribute"></mat-icon>
|
||||
@if (!attr.value) {
|
||||
<span> {{ attr.label + ': -' }}</span>
|
||||
}
|
||||
@if (attr.value && attr.type === 'DATE') {
|
||||
<span> {{ attr.label + ': ' + (attr.value | date: 'd MMM yyyy') }}</span>
|
||||
}
|
||||
@if (attr.value && attr.type === 'IMAGE') {
|
||||
<span> {{ attr.label + ': ' + ('dossier-overview.dossier-details.attributes.image-uploaded' | translate) }}</span>
|
||||
}
|
||||
@if (attr.value && (attr.type === 'TEXT' || attr.type === 'NUMBER')) {
|
||||
<span> {{ attr.label + ': ' + attr.value }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div (click)="attributesExpanded = false" class="all-caps-label hide-attributes">
|
||||
{{ 'dossier-overview.dossier-details.attributes.show-less' | translate }}
|
||||
<div (click)="attributesExpanded = false" class="all-caps-label hide-attributes">
|
||||
{{ 'dossier-overview.dossier-details.attributes.show-less' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { AsyncPipe, DecimalPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { AsyncPipe, DecimalPipe } from '@angular/common';
|
||||
import { Component, input, OnInit, untracked } from '@angular/core';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { getConfig, largeDialogConfig } from '@iqser/common-ui';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@ -17,11 +17,11 @@ import { DossiersDialogService } from '../../../shared-dossiers/services/dossier
|
||||
templateUrl: './dossier-details-stats.component.html',
|
||||
styleUrls: ['./dossier-details-stats.component.scss'],
|
||||
standalone: true,
|
||||
imports: [MatIcon, NgIf, AsyncPipe, TranslateModule, DatePipe, DecimalPipe, NgForOf],
|
||||
imports: [MatIcon, AsyncPipe, TranslateModule, DatePipe, DecimalPipe],
|
||||
})
|
||||
export class DossierDetailsStatsComponent implements OnInit {
|
||||
@Input() dossierAttributes: DossierAttributeWithValue[];
|
||||
@Input() dossier: Dossier;
|
||||
readonly dossierAttributes = input<DossierAttributeWithValue[]>();
|
||||
readonly dossier = input<Dossier>();
|
||||
|
||||
attributesExpanded = false;
|
||||
dossierTemplateName: string;
|
||||
@ -36,14 +36,16 @@ export class DossierDetailsStatsComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.dossierStats$ = this._dossierStatsService.watch$(this.dossier.id);
|
||||
this.dossierTemplateName = this._dossierTemplatesService.find(this.dossier.dossierTemplateId)?.name || '-';
|
||||
const dossier = untracked(this.dossier);
|
||||
this.dossierStats$ = this._dossierStatsService.watch$(dossier.id);
|
||||
this.dossierTemplateName = this._dossierTemplatesService.find(dossier.dossierTemplateId)?.name || '-';
|
||||
}
|
||||
|
||||
openEditDossierDialog(section: string): void {
|
||||
const data = { dossierId: this.dossier.id, section };
|
||||
const dossier = untracked(this.dossier);
|
||||
const data = { dossierId: dossier.id, section };
|
||||
this._dialogService.open(EditDossierDialogComponent, data, { ...largeDialogConfig, width: '98vw', maxWidth: '98vw' }, async () => {
|
||||
await firstValueFrom(this._filesService.loadAll(this.dossier.id));
|
||||
await firstValueFrom(this._filesService.loadAll(dossier.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<ng-container *ngIf="componentContext$ | async as ctx">
|
||||
@if (componentContext$ | async; as ctx) {
|
||||
<div class="collapsed-wrapper">
|
||||
<ng-container *ngTemplateOutlet="collapsible; context: { action: 'expand', tooltip: (expandTooltip | translate) }"></ng-container>
|
||||
<div class="all-caps-label" translate="dossier-details.title"></div>
|
||||
@ -14,7 +14,7 @@
|
||||
<div class="mt-24">
|
||||
<div class="all-caps-label" translate="dossier-details.owner"></div>
|
||||
<div class="mt-12 flex">
|
||||
<ng-container *ngIf="!editingOwner; else editOwner">
|
||||
@if (!editingOwner) {
|
||||
<iqser-initials-avatar [user]="ctx.dossier?.ownerId" [withName]="true" color="gray" size="large"></iqser-initials-avatar>
|
||||
|
||||
<iqser-circle-button
|
||||
@ -25,7 +25,14 @@
|
||||
[tooltip]="'dossier-details.edit-owner' | translate"
|
||||
class="ml-14"
|
||||
></iqser-circle-button>
|
||||
</ng-container>
|
||||
} @else {
|
||||
<redaction-assign-user-dropdown
|
||||
(cancel)="editingOwner = false"
|
||||
(save)="editingOwner = false; assignOwner($event, ctx.dossier)"
|
||||
[options]="managers()"
|
||||
[value]="ctx.dossier?.ownerId"
|
||||
></redaction-assign-user-dropdown>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -38,69 +45,63 @@
|
||||
></redaction-team-members>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="ctx.dossierStats as stats">
|
||||
<div *ngIf="stats.hasFiles" class="mt-24">
|
||||
<redaction-donut-chart
|
||||
(subtitleChanged)="onSubtitleChanged($event)"
|
||||
[config]="chartConfig"
|
||||
[filterKey]="'statusFilters'"
|
||||
[helpModeKey]="'dashboard_in_dossier'"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="[
|
||||
'dossier-overview.dossier-details.charts.documents-in-dossier' | translate,
|
||||
'dossier-overview.dossier-details.charts.pages-in-dossier' | translate,
|
||||
]"
|
||||
direction="row"
|
||||
></redaction-donut-chart>
|
||||
</div>
|
||||
|
||||
<div *ngIf="ctx.statusConfig as statusConfig" class="mt-24">
|
||||
<div class="all-caps-label mb-8" translate="dossier-details.document-status"></div>
|
||||
<iqser-progress-bar
|
||||
*ngFor="let config of statusConfig"
|
||||
[attr.help-mode-key]="'dashboard_in_dossier'"
|
||||
[config]="config"
|
||||
filterKey="processingTypeFilters"
|
||||
></iqser-progress-bar>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="stats.hasFiles && !isDocumine && ctx.needsWorkFilters as filters"
|
||||
[attr.help-mode-key]="'dashboard_in_dossier'"
|
||||
class="mt-32 legend pb-32"
|
||||
>
|
||||
<div
|
||||
(click)="filterService.toggleFilter('needsWorkFilters', filter.id)"
|
||||
*ngFor="let filter of filters"
|
||||
[class.active]="filter.checked"
|
||||
>
|
||||
<redaction-type-filter [dossierTemplateId]="ctx.dossier?.dossierTemplateId" [filter]="filter"></redaction-type-filter>
|
||||
@if (ctx.dossierStats; as stats) {
|
||||
@if (stats.hasFiles) {
|
||||
<div class="mt-24">
|
||||
<redaction-donut-chart
|
||||
(subtitleChanged)="onSubtitleChanged($event)"
|
||||
[config]="chartConfig"
|
||||
[filterKey]="'statusFilters'"
|
||||
[helpModeKey]="'dashboard_in_dossier'"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitles]="[
|
||||
'dossier-overview.dossier-details.charts.documents-in-dossier' | translate,
|
||||
'dossier-overview.dossier-details.charts.pages-in-dossier' | translate,
|
||||
]"
|
||||
direction="row"
|
||||
></redaction-donut-chart>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (ctx.statusConfig; as statusConfig) {
|
||||
<div class="mt-24">
|
||||
<div class="all-caps-label mb-8" translate="dossier-details.document-status"></div>
|
||||
@for (config of statusConfig; track config.id) {
|
||||
<iqser-progress-bar
|
||||
[attr.help-mode-key]="'dashboard_in_dossier'"
|
||||
[config]="config"
|
||||
filterKey="processingTypeFilters"
|
||||
></iqser-progress-bar>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (stats.hasFiles && !isDocumine && ctx.needsWorkFilters; as filters) {
|
||||
<div [attr.help-mode-key]="'dashboard_in_dossier'" class="mt-32 legend pb-32">
|
||||
@for (filter of filters; track filter.id) {
|
||||
<div (click)="filterService.toggleFilter('needsWorkFilters', filter.id)" [class.active]="filter.checked">
|
||||
<redaction-type-filter
|
||||
[dossierTemplateId]="ctx.dossier?.dossierTemplateId"
|
||||
[filter]="filter"
|
||||
></redaction-type-filter>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div [class.mt-24]="!stats.hasFiles" class="pb-32">
|
||||
<redaction-dossier-details-stats
|
||||
[dossierAttributes]="dossierAttributes"
|
||||
[dossierAttributes]="dossierAttributes()"
|
||||
[dossier]="ctx.dossier"
|
||||
></redaction-dossier-details-stats>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="ctx.dossier?.description as description" class="pb-32">
|
||||
<div class="heading" translate="dossier-overview.dossier-details.description"></div>
|
||||
<div class="mt-8">{{ description }}</div>
|
||||
</div>
|
||||
|
||||
<ng-template #editOwner>
|
||||
<redaction-assign-user-dropdown
|
||||
(cancel)="editingOwner = false"
|
||||
(save)="editingOwner = false; assignOwner($event, ctx.dossier)"
|
||||
[options]="managers"
|
||||
[value]="ctx.dossier?.ownerId"
|
||||
></redaction-assign-user-dropdown>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
}
|
||||
@if (ctx.dossier?.description; as description) {
|
||||
<div class="pb-32">
|
||||
<div class="heading" translate="dossier-overview.dossier-details.description"></div>
|
||||
<div class="mt-8">{{ description }}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<ng-template #collapsible let-action="action" let-tooltip="tooltip">
|
||||
<iqser-circle-button
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
|
||||
import { Component, computed, input, output } from '@angular/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import {
|
||||
CircleButtonComponent,
|
||||
@ -55,14 +55,12 @@ interface DossierDetailsContext {
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgTemplateOutlet,
|
||||
NgIf,
|
||||
AsyncPipe,
|
||||
CircleButtonComponent,
|
||||
IqserAllowDirective,
|
||||
TranslateModule,
|
||||
DonutChartComponent,
|
||||
IqserLoadingModule,
|
||||
NgForOf,
|
||||
TypeFilterComponent,
|
||||
DossierDetailsStatsComponent,
|
||||
AssignUserDropdownComponent,
|
||||
@ -74,8 +72,8 @@ export class DossierDetailsComponent extends ContextComponent<DossierDetailsCont
|
||||
#currentChartSubtitleIndex = 0;
|
||||
readonly #dossierId = getParam(DOSSIER_ID);
|
||||
protected readonly circleButtonTypes = CircleButtonTypes;
|
||||
@Input() dossierAttributes: DossierAttributeWithValue[];
|
||||
@Output() readonly toggleCollapse = new EventEmitter();
|
||||
readonly dossierAttributes = input<DossierAttributeWithValue[]>();
|
||||
readonly toggleCollapse = output();
|
||||
editingOwner = false;
|
||||
readonly roles = Roles;
|
||||
readonly currentUser = getCurrentUser<User>();
|
||||
@ -84,6 +82,12 @@ export class DossierDetailsComponent extends ContextComponent<DossierDetailsCont
|
||||
chartConfig: DonutChartConfig[] = [];
|
||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
readonly IqserTooltipPositions = IqserTooltipPositions;
|
||||
readonly managers = computed(() =>
|
||||
this._userService
|
||||
.allUsers()
|
||||
.filter(u => u.isManager)
|
||||
.map(u => u.id),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
@ -113,10 +117,6 @@ export class DossierDetailsComponent extends ContextComponent<DossierDetailsCont
|
||||
});
|
||||
}
|
||||
|
||||
get managers() {
|
||||
return this._userService.all.filter(u => u.isManager).map(u => u.id);
|
||||
}
|
||||
|
||||
async assignOwner(user: User | string, dossier: Dossier) {
|
||||
const owner = typeof user === 'string' ? this._userService.find(user) : user;
|
||||
const dossierRequest: IDossierRequest = { ...dossier, ownerId: owner.id };
|
||||
|
||||
@ -1,90 +1,95 @@
|
||||
<ng-container *ngIf="configService.listingMode$ | async as mode">
|
||||
@if (configService.listingMode$ | async; as mode) {
|
||||
<div
|
||||
(click)="handleFieldClick($event)"
|
||||
(mousedown)="handleClick($event)"
|
||||
[ngClass]="{ 'workflow-attribute': mode === 'workflow', 'file-name-column': fileNameColumn }"
|
||||
[ngClass]="{ 'workflow-attribute': mode === 'workflow', 'file-name-column': fileNameColumn() }"
|
||||
class="file-attribute"
|
||||
>
|
||||
<div [ngClass]="{ 'workflow-value': mode === 'workflow' }" class="value" *ngIf="!isInEditMode || mode === 'workflow'">
|
||||
<mat-icon *ngIf="!fileAttribute.editable" [matTooltip]="'readonly' | translate" svgIcon="red:read-only"></mat-icon>
|
||||
<span
|
||||
*ngIf="!isDate; else date"
|
||||
[style.max-width]="attributeValueWidth"
|
||||
[matTooltip]="fileAttributeValue"
|
||||
[ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }"
|
||||
>
|
||||
<b *ngIf="mode === 'workflow' && !isInEditMode"> {{ fileAttribute.label }}: </b>
|
||||
{{ fileAttributeValue || '-' }}
|
||||
</span>
|
||||
<ng-template #date>
|
||||
<span [ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }">
|
||||
<b *ngIf="mode === 'workflow' && !isInEditMode"> {{ fileAttribute.label }}: </b>
|
||||
{{ fileAttributeValue ? (fileAttributeValue | date: 'd MMM yyyy') : '-' }}
|
||||
</span>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<ng-container
|
||||
*ngIf="
|
||||
(fileAttributesService.isEditingFileAttribute() === false || isInEditMode) &&
|
||||
!file.isInitialProcessing &&
|
||||
permissionsService.canEditFileAttributes(file, dossier) &&
|
||||
fileAttribute.editable
|
||||
"
|
||||
>
|
||||
<div
|
||||
(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',
|
||||
'action-buttons edit-button': !fileNameColumn,
|
||||
'filename-edit-button': fileNameColumn,
|
||||
}"
|
||||
>
|
||||
<div [ngClass]="{ 'workflow-edit-icon': mode === 'workflow', 'edit-icon': !fileNameColumn }">
|
||||
<mat-icon [svgIcon]="'iqser:edit'"></mat-icon>
|
||||
</div>
|
||||
@if (!isInEditMode || mode === 'workflow') {
|
||||
<div [ngClass]="{ 'workflow-value': mode === 'workflow' }" class="value">
|
||||
@if (!fileAttribute().editable) {
|
||||
<mat-icon [matTooltip]="'readonly' | translate" svgIcon="red:read-only"></mat-icon>
|
||||
}
|
||||
@if (!isDate()) {
|
||||
<span
|
||||
[style.max-width]="attributeValueWidth()"
|
||||
[matTooltip]="value()"
|
||||
[ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }"
|
||||
>
|
||||
@if (mode === 'workflow' && !isInEditMode) {
|
||||
<b> {{ fileAttribute().label }}: </b>
|
||||
}
|
||||
{{ value() || '-' }}
|
||||
</span>
|
||||
} @else {
|
||||
<span [ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }">
|
||||
@if (mode === 'workflow' && !isInEditMode) {
|
||||
<b> {{ fileAttribute().label }}: </b>
|
||||
}
|
||||
{{ value() ? (value() | date: 'd MMM yyyy') : '-' }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
@if (
|
||||
(fileAttributesService.isEditingFileAttribute() === false || isInEditMode) &&
|
||||
!file().isInitialProcessing &&
|
||||
permissionsService.canEditFileAttributes(file(), dossier()) &&
|
||||
fileAttribute().editable
|
||||
) {
|
||||
@if (!isInEditMode) {
|
||||
<div
|
||||
(click)="editFileAttribute($event)"
|
||||
[attr.help-mode-key]="'edit_file_attributes'"
|
||||
[class.help-mode-button]="helpModeService.isHelpModeActive$ | async"
|
||||
[ngClass]="{
|
||||
'workflow-edit-button': mode === 'workflow',
|
||||
'action-buttons edit-button': !fileNameColumn(),
|
||||
'filename-edit-button': fileNameColumn(),
|
||||
}"
|
||||
>
|
||||
<div [ngClass]="{ 'workflow-edit-icon': mode === 'workflow', 'edit-icon': !fileNameColumn() }">
|
||||
<mat-icon [svgIcon]="'iqser:edit'"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
[ngClass]="{ 'workflow-edit-input': mode === 'workflow', 'file-name-column-input': fileNameColumn() }"
|
||||
class="edit-input"
|
||||
iqserStopPropagation
|
||||
>
|
||||
<form [formGroup]="form">
|
||||
<iqser-dynamic-input
|
||||
(closedDatepicker)="closedDatepicker = $event"
|
||||
(keyup.enter)="form.valid && save()"
|
||||
(keydown.escape)="close()"
|
||||
[style.max-width]="editFieldWidth()"
|
||||
[style.min-width]="editFieldWidth()"
|
||||
[formControlName]="fileAttribute().id"
|
||||
[id]="fileAttribute().id"
|
||||
[ngClass]="{ 'workflow-input': mode === 'workflow' || fileNameColumn(), 'file-name-input': fileNameColumn() }"
|
||||
[type]="fileAttribute().type"
|
||||
></iqser-dynamic-input>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="save()"
|
||||
[disabled]="disabled"
|
||||
[icon]="'iqser:check'"
|
||||
[size]="mode === 'workflow' || fileNameColumn() ? 15 : 34"
|
||||
[ngClass]="{ 'file-name-btn': fileNameColumn() }"
|
||||
class="save"
|
||||
></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>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<ng-template #input>
|
||||
<div
|
||||
[ngClass]="{ 'workflow-edit-input': mode === 'workflow', 'file-name-column-input': fileNameColumn }"
|
||||
class="edit-input"
|
||||
iqserStopPropagation
|
||||
>
|
||||
<form [formGroup]="form">
|
||||
<iqser-dynamic-input
|
||||
(closedDatepicker)="closedDatepicker = $event"
|
||||
(keyup.enter)="form.valid && save()"
|
||||
(keydown.escape)="close()"
|
||||
[style.max-width]="editFieldWidth"
|
||||
[style.min-width]="editFieldWidth"
|
||||
[formControlName]="fileAttribute.id"
|
||||
[id]="fileAttribute.id"
|
||||
[ngClass]="{ 'workflow-input': mode === 'workflow' || fileNameColumn, 'file-name-input': fileNameColumn }"
|
||||
[type]="fileAttribute.type"
|
||||
></iqser-dynamic-input>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="save()"
|
||||
[disabled]="disabled"
|
||||
[icon]="'iqser:check'"
|
||||
[size]="mode === 'workflow' || fileNameColumn ? 15 : 34"
|
||||
[ngClass]="{ 'file-name-btn': fileNameColumn }"
|
||||
class="save"
|
||||
></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>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { AsyncPipe, NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
|
||||
import { Component, computed, effect, HostListener, Input, OnDestroy } from '@angular/core';
|
||||
import { AsyncPipe, NgClass, NgTemplateOutlet } from '@angular/common';
|
||||
import { Component, computed, effect, HostListener, input, untracked } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormsModule, ReactiveFormsModule, UntypedFormGroup, ValidatorFn } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { DynamicInputComponent } from '@common-ui/inputs/dynamic-input/dynamic-input.component';
|
||||
import {
|
||||
@ -14,7 +13,7 @@ import {
|
||||
StopPropagationDirective,
|
||||
Toaster,
|
||||
} from '@iqser/common-ui';
|
||||
import { Debounce, log } from '@iqser/common-ui/lib/utils';
|
||||
import { Debounce } from '@iqser/common-ui/lib/utils';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
@ -22,9 +21,9 @@ import { FilesService } from '@services/files/files.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||
import dayjs from 'dayjs';
|
||||
import { firstValueFrom, Subscription } from 'rxjs';
|
||||
import { filter, map, tap } from 'rxjs/operators';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ConfigService } from '../../config.service';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-attribute',
|
||||
@ -32,7 +31,6 @@ import { ConfigService } from '../../config.service';
|
||||
styleUrls: ['./file-attribute.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgIf,
|
||||
NgClass,
|
||||
MatTooltipModule,
|
||||
MatIconModule,
|
||||
@ -47,29 +45,34 @@ import { ConfigService } from '../../config.service';
|
||||
StopPropagationDirective,
|
||||
],
|
||||
})
|
||||
export class FileAttributeComponent extends BaseFormComponent implements OnDestroy {
|
||||
readonly #subscriptions = new Subscription();
|
||||
#widthFactor = window.innerWidth >= 1800 ? 0.85 : 0.7;
|
||||
export class FileAttributeComponent extends BaseFormComponent {
|
||||
isInEditMode = false;
|
||||
closedDatepicker = true;
|
||||
@Input({ required: true }) fileAttribute!: IFileAttributeConfig;
|
||||
@Input({ required: true }) file!: File;
|
||||
readonly fileAttribute = input.required<IFileAttributeConfig>();
|
||||
readonly file = input.required<File>();
|
||||
readonly dossier = input<Dossier>();
|
||||
readonly fileNameColumn = input(false);
|
||||
readonly width = input<number>();
|
||||
readonly editFieldWidth = computed(() => (this.width() ? `${this.width() * this.#widthFactor}px` : 'unset'));
|
||||
readonly attributeValueWidth = computed(() => (this.width() ? `${this.width() * 0.9}px` : 'unset'));
|
||||
|
||||
readonly isText = computed(() => this.fileAttribute().type === FileAttributeConfigTypes.TEXT);
|
||||
readonly value = computed(() => this.file().fileAttributes.attributeIdToValue[this.fileAttribute().id]);
|
||||
readonly isDate = computed(() => this.fileAttribute().type === FileAttributeConfigTypes.DATE);
|
||||
|
||||
readonly #selectedLength = toSignal(this._listingService.selectedLength$);
|
||||
readonly #shouldClose = computed(
|
||||
() =>
|
||||
(this.fileAttributesService.isEditingFileAttribute() &&
|
||||
this.file &&
|
||||
this.fileAttribute &&
|
||||
(this.fileAttribute.id !== this.fileAttributesService.openAttributeEdit() ||
|
||||
this.file.fileId !== this.fileAttributesService.fileEdit())) ||
|
||||
(this.fileAttribute().id !== this.fileAttributesService.openAttributeEdit() ||
|
||||
this.file().fileId !== this.fileAttributesService.fileEdit())) ||
|
||||
!this.fileAttributesService.openAttributeEdits().length,
|
||||
);
|
||||
@Input({ required: true }) dossier!: Dossier;
|
||||
@Input() fileNameColumn = false;
|
||||
readonlyAttrs: string[] = [];
|
||||
@Input() width?: number;
|
||||
|
||||
#widthFactor = window.innerWidth >= 1800 ? 0.85 : 0.7;
|
||||
#readonlyAttrs: string[] = [];
|
||||
|
||||
constructor(
|
||||
router: Router,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _filesService: FilesService,
|
||||
@ -80,14 +83,11 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
readonly configService: ConfigService,
|
||||
) {
|
||||
super();
|
||||
const sub = router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => this.close());
|
||||
this.#subscriptions.add(sub);
|
||||
|
||||
const sub2 = this._listingService.selectedLength$.pipe(
|
||||
map(selectedLength => !!selectedLength),
|
||||
tap(() => this.close()),
|
||||
);
|
||||
this.#subscriptions.add(sub2.subscribe());
|
||||
effect(() => {
|
||||
if (this.#selectedLength()) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
effect(
|
||||
() => {
|
||||
@ -99,30 +99,6 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
);
|
||||
}
|
||||
|
||||
get editFieldWidth(): string {
|
||||
return this.width ? `${this.width * this.#widthFactor}px` : 'unset';
|
||||
}
|
||||
|
||||
get attributeValueWidth(): string {
|
||||
return this.width ? `${this.width * 0.9}px` : 'unset';
|
||||
}
|
||||
|
||||
get isDate(): boolean {
|
||||
return this.fileAttribute.type === FileAttributeConfigTypes.DATE;
|
||||
}
|
||||
|
||||
get isNumber(): boolean {
|
||||
return this.fileAttribute.type === FileAttributeConfigTypes.NUMBER;
|
||||
}
|
||||
|
||||
get isText(): boolean {
|
||||
return this.fileAttribute.type === FileAttributeConfigTypes.TEXT;
|
||||
}
|
||||
|
||||
get fileAttributeValue(): string {
|
||||
return this.file.fileAttributes.attributeIdToValue[this.fileAttribute.id];
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
onResize() {
|
||||
if (window.innerWidth >= 1800) {
|
||||
@ -136,7 +112,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
@HostListener('document:click', ['$event'])
|
||||
clickOutside($event: MouseEvent) {
|
||||
const clickCalendarCell = ($event.target as HTMLElement).classList?.contains('mat-calendar-body-cell-content');
|
||||
if (this.isDate) {
|
||||
if (this.isDate()) {
|
||||
this.#focusOnEditInput();
|
||||
}
|
||||
|
||||
@ -150,13 +126,9 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
$event.preventDefault();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.#subscriptions.unsubscribe();
|
||||
}
|
||||
|
||||
handleFieldClick($event: MouseEvent) {
|
||||
$event.preventDefault();
|
||||
if (!this.fileNameColumn) {
|
||||
if (!this.fileNameColumn()) {
|
||||
this.editFileAttribute($event);
|
||||
}
|
||||
}
|
||||
@ -164,34 +136,34 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
editFileAttribute($event: MouseEvent) {
|
||||
$event.preventDefault();
|
||||
if (
|
||||
!this.file.isInitialProcessing &&
|
||||
this.permissionsService.canEditFileAttributes(this.file, this.dossier) &&
|
||||
this.fileAttribute.editable &&
|
||||
!this.file().isInitialProcessing &&
|
||||
this.permissionsService.canEditFileAttributes(this.file(), this.dossier()) &&
|
||||
this.fileAttribute().editable &&
|
||||
!this.isInEditMode
|
||||
) {
|
||||
$event.stopPropagation();
|
||||
this.fileAttributesService.openAttributeEdits.update(value => [
|
||||
...value,
|
||||
{ attribute: this.fileAttribute.id, file: this.file.id },
|
||||
{ attribute: this.fileAttribute().id, file: this.file().id },
|
||||
]);
|
||||
this.fileAttributesService.setFileEdit(this.file.fileId);
|
||||
this.fileAttributesService.setOpenAttributeEdit(this.fileAttribute.id);
|
||||
this.fileAttributesService.setFileEdit(this.file().fileId);
|
||||
this.fileAttributesService.setOpenAttributeEdit(this.fileAttribute().id);
|
||||
this.#toggleEdit();
|
||||
}
|
||||
}
|
||||
|
||||
async save() {
|
||||
const rawFormValue = this.form.getRawValue();
|
||||
const fileAttrValue = rawFormValue[this.fileAttribute.id];
|
||||
const fileAttrValue = rawFormValue[this.fileAttribute().id];
|
||||
const attributeIdToValue = {
|
||||
...this.#getForm().getRawValue(),
|
||||
[this.fileAttribute.id]: this.#formatAttributeValue(fileAttrValue),
|
||||
[this.fileAttribute().id]: this.#formatAttributeValue(fileAttrValue),
|
||||
};
|
||||
try {
|
||||
await firstValueFrom(
|
||||
this.fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file.dossierId, this.file.fileId),
|
||||
this.fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file().dossierId, this.file().fileId),
|
||||
);
|
||||
await this._filesService.reload(this.file.dossierId, this.file);
|
||||
await this._filesService.reload(this.file().dossierId, this.file());
|
||||
this.initialFormValue = rawFormValue;
|
||||
this._toaster.success(_('file-attribute.update.success'));
|
||||
} catch (e) {
|
||||
@ -203,6 +175,8 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
|
||||
close(): void {
|
||||
if (this.isInEditMode) {
|
||||
const fileAttribute = untracked(this.fileAttribute);
|
||||
const file = untracked(this.file);
|
||||
this.form = this.#getForm();
|
||||
this.#toggleEdit();
|
||||
this.fileAttributesService.openAttributeEdits.update(value =>
|
||||
@ -210,8 +184,8 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
val =>
|
||||
JSON.stringify(val) !==
|
||||
JSON.stringify({
|
||||
attribute: this.fileAttribute.id,
|
||||
file: this.file.id,
|
||||
attribute: fileAttribute.id,
|
||||
file: file.id,
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -219,24 +193,26 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
}
|
||||
|
||||
#initFileAttributes() {
|
||||
const configs = this.fileAttributesService.getFileAttributeConfig(this.file.dossierTemplateId).fileAttributeConfigs;
|
||||
this.readonlyAttrs = configs.filter(config => config.editable === false).map(config => config.id);
|
||||
const file = untracked(this.file);
|
||||
const configs = this.fileAttributesService.getFileAttributeConfig(file.dossierTemplateId).fileAttributeConfigs;
|
||||
this.#readonlyAttrs = configs.filter(config => config.editable === false).map(config => config.id);
|
||||
configs.forEach(config => {
|
||||
if (!this.file.fileAttributes.attributeIdToValue[config.id]) {
|
||||
this.file.fileAttributes.attributeIdToValue[config.id] = null;
|
||||
if (!file.fileAttributes.attributeIdToValue[config.id]) {
|
||||
file.fileAttributes.attributeIdToValue[config.id] = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#getForm(): UntypedFormGroup {
|
||||
const config = {};
|
||||
const fileAttributes = this.file.fileAttributes.attributeIdToValue;
|
||||
const file = untracked(this.file);
|
||||
const fileAttributes = file.fileAttributes.attributeIdToValue;
|
||||
Object.keys(fileAttributes).forEach(key => {
|
||||
const attrValue = fileAttributes[key];
|
||||
config[key] = [
|
||||
{
|
||||
value: dayjs(attrValue, 'YYYY-MM-DD', true).isValid() ? dayjs(attrValue).toDate() : attrValue,
|
||||
disabled: this.readonlyAttrs.includes(key),
|
||||
disabled: this.#readonlyAttrs.includes(key),
|
||||
},
|
||||
];
|
||||
});
|
||||
@ -246,19 +222,25 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
}
|
||||
|
||||
#checkEmptyInput(): ValidatorFn {
|
||||
const isText = untracked(this.isText);
|
||||
const fileAttribute = untracked(this.fileAttribute);
|
||||
const value = untracked(this.value);
|
||||
return (control: AbstractControl) =>
|
||||
(this.isText && !control.get(this.fileAttribute.id)?.value?.trim().length && !this.fileAttributeValue) ||
|
||||
control.get(this.fileAttribute.id)?.value === this.fileAttributeValue
|
||||
(isText && !control.get(fileAttribute.id)?.value?.trim().length && !this.value()) ||
|
||||
control.get(fileAttribute.id)?.value === value
|
||||
? { emptyString: true }
|
||||
: null;
|
||||
}
|
||||
|
||||
#checkDate(): ValidatorFn {
|
||||
const isDate = untracked(this.isDate);
|
||||
const fileAttribute = untracked(this.fileAttribute);
|
||||
const value = untracked(this.value);
|
||||
return (control: AbstractControl) => {
|
||||
const expr = new RegExp('(0?[1-9]|[12][0-9]|3[01])(/|.)(0?[1-9]|1[12])(/|.)\\d{2}');
|
||||
return this.isDate
|
||||
? (!expr.test(control.get(this.fileAttribute.id)?.value) && control.get(this.fileAttribute.id)?.value?.length) ||
|
||||
this.#formatAttributeValue(control.get(this.fileAttribute.id)?.value) === this.fileAttributeValue
|
||||
return isDate
|
||||
? (!expr.test(control.get(fileAttribute.id)?.value) && control.get(fileAttribute.id)?.value?.length) ||
|
||||
this.#formatAttributeValue(control.get(fileAttribute.id)?.value) === value
|
||||
? { invalidDate: true }
|
||||
: null
|
||||
: null;
|
||||
@ -266,11 +248,12 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
}
|
||||
|
||||
#formatAttributeValue(attrValue) {
|
||||
if (this.isDate) {
|
||||
const isDate = untracked(this.isDate);
|
||||
if (isDate) {
|
||||
return attrValue && dayjs(attrValue).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (this.isText) {
|
||||
const isText = untracked(this.isText);
|
||||
if (isText) {
|
||||
return attrValue.trim().replaceAll(/\s\s+/g, ' ');
|
||||
}
|
||||
|
||||
@ -292,9 +275,12 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
|
||||
}
|
||||
|
||||
#focusOnEditInput(): void {
|
||||
if (this.isDate || this.isText) {
|
||||
const isDate = untracked(this.isDate);
|
||||
const isText = untracked(this.isText);
|
||||
const fileAttribute = untracked(this.fileAttribute);
|
||||
if (isDate || isText) {
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById(this.fileAttribute.id) as HTMLInputElement;
|
||||
const input = document.getElementById(fileAttribute.id) as HTMLInputElement;
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<iqser-page-header
|
||||
(closeAction)="router.navigate([dossier.dossiersListRouterLink])"
|
||||
(closeAction)="router.navigate([dossier().dossiersListRouterLink])"
|
||||
[actionConfigs]="actionConfigs"
|
||||
[helpModeKey]="'document'"
|
||||
[showCloseButton]="true"
|
||||
@ -10,40 +10,43 @@
|
||||
[attr.help-mode-key]="isDocumine ? 'dossier_download_dossier' : 'download_dossier_in_dossier'"
|
||||
[buttonId]="'download-files-btn'"
|
||||
[disabled]="downloadFilesDisabled$ | async"
|
||||
[dossier]="dossier"
|
||||
[dossier]="dossier()"
|
||||
[files]="entitiesService.all$ | async"
|
||||
dossierDownload
|
||||
></redaction-file-download-btn>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="downloadDossierAsCSV()"
|
||||
*ngIf="permissionsService.canDownloadCsvReport(dossier)"
|
||||
[attr.help-mode-key]="'download_csv'"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[icon]="'iqser:csv'"
|
||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||
></iqser-circle-button>
|
||||
@if (permissionsService.canDownloadCsvReport(dossier())) {
|
||||
<iqser-circle-button
|
||||
(action)="downloadDossierAsCSV()"
|
||||
[attr.help-mode-key]="'download_csv'"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[icon]="'iqser:csv'"
|
||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||
></iqser-circle-button>
|
||||
}
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="reanalyseDossier()"
|
||||
*ngIf="permissionsService.displayReanalyseBtn(dossier)"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[icon]="'iqser:refresh'"
|
||||
[tooltipClass]="'small warn'"
|
||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||
[type]="circleButtonTypes.warn"
|
||||
></iqser-circle-button>
|
||||
@if (permissionsService.displayReanalyseBtn(dossier())) {
|
||||
<iqser-circle-button
|
||||
(action)="reanalyseDossier()"
|
||||
[disabled]="listingService.areSomeSelected$ | async"
|
||||
[icon]="'iqser:refresh'"
|
||||
[tooltipClass]="'small warn'"
|
||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||
[type]="circleButtonTypes.warn"
|
||||
></iqser-circle-button>
|
||||
}
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="upload.emit()"
|
||||
*ngIf="permissionsService.canUploadFiles(dossier)"
|
||||
[attr.help-mode-key]="'upload_document'"
|
||||
[buttonId]="'upload-document-btn'"
|
||||
[icon]="'iqser:upload'"
|
||||
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-14"
|
||||
></iqser-circle-button>
|
||||
@if (permissionsService.canUploadFiles(dossier())) {
|
||||
<iqser-circle-button
|
||||
(action)="upload.emit()"
|
||||
[attr.help-mode-key]="'upload_document'"
|
||||
[buttonId]="'upload-document-btn'"
|
||||
[icon]="'iqser:upload'"
|
||||
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-14"
|
||||
></iqser-circle-button>
|
||||
}
|
||||
</ng-container>
|
||||
</iqser-page-header>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Component, input, OnInit, output, untracked } from '@angular/core';
|
||||
import {
|
||||
ActionConfig,
|
||||
CircleButtonComponent,
|
||||
@ -25,9 +25,8 @@ 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 { ComponentLogService } from '@services/files/component-log.service';
|
||||
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FileDownloadBtnComponent } from '@shared/components/buttons/file-download-btn/file-download-btn.component';
|
||||
import { ViewModeSelectionComponent } from '../view-mode-selection/view-mode-selection.component';
|
||||
@ -44,7 +43,6 @@ import { ViewModeSelectionComponent } from '../view-mode-selection/view-mode-sel
|
||||
AsyncPipe,
|
||||
TranslateModule,
|
||||
FileDownloadBtnComponent,
|
||||
NgIf,
|
||||
ViewModeSelectionComponent,
|
||||
DisableStopPropagationDirective,
|
||||
MatMenu,
|
||||
@ -52,8 +50,8 @@ import { ViewModeSelectionComponent } from '../view-mode-selection/view-mode-sel
|
||||
],
|
||||
})
|
||||
export class DossierOverviewScreenHeaderComponent implements OnInit {
|
||||
@Input() dossier: Dossier;
|
||||
@Output() readonly upload = new EventEmitter<void>();
|
||||
readonly dossier = input<Dossier>();
|
||||
readonly upload = output();
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly roles = Roles;
|
||||
actionConfigs: List<ActionConfig>;
|
||||
@ -84,13 +82,14 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.actionConfigs = this.configService.actionConfig(this.dossier.id, this.listingService.areSomeSelected$);
|
||||
this.actionConfigs = this.configService.actionConfig(this.dossier().id, this.listingService.areSomeSelected$);
|
||||
}
|
||||
|
||||
async reanalyseDossier() {
|
||||
this._loadingService.start();
|
||||
try {
|
||||
await this._reanalysisService.reanalyzeDossier(this.dossier, true);
|
||||
const dossier = untracked(this.dossier);
|
||||
await this._reanalysisService.reanalyzeDossier(dossier, true);
|
||||
this._toaster.success(_('dossier-overview.reanalyse-dossier.success'));
|
||||
} catch (e) {
|
||||
this._toaster.error(_('dossier-overview.reanalyse-dossier.error'));
|
||||
@ -101,12 +100,13 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
|
||||
async downloadDossierAsCSV() {
|
||||
const displayedEntities = await firstValueFrom(this.listingService.displayed$);
|
||||
const entities = this.sortingService.defaultSort(displayedEntities);
|
||||
const fileName = this.dossier.dossierName + '.export.csv';
|
||||
const dossier = untracked(this.dossier);
|
||||
const fileName = dossier.dossierName + '.export.csv';
|
||||
const mapper = (file?: File) => ({
|
||||
...file,
|
||||
hasAnnotations: file.hasRedactions,
|
||||
assignee: this._userService.getName(file.assignee) || '-',
|
||||
primaryAttribute: this._primaryFileAttributeService.getPrimaryFileAttributeValue(file, this.dossier.dossierTemplateId),
|
||||
primaryAttribute: this._primaryFileAttributeService.getPrimaryFileAttributeValue(file, this.dossier().dossierTemplateId),
|
||||
});
|
||||
const documineOnlyFields = ['hasAnnotations'];
|
||||
const redactionOnlyFields = ['hasHints', 'hasImages', 'hasUpdates', 'hasRedactions'];
|
||||
|
||||
@ -1,19 +1,27 @@
|
||||
<div class="needs-work">
|
||||
<redaction-annotation-icon
|
||||
*ngIf="file().analysisRequired"
|
||||
[color]="analysisColor$ | async"
|
||||
label="A"
|
||||
type="square"
|
||||
></redaction-annotation-icon>
|
||||
<redaction-annotation-icon *ngIf="updated()" [color]="updatedColor$ | async" label="U" type="square"></redaction-annotation-icon>
|
||||
<redaction-annotation-icon
|
||||
*ngIf="file().hasRedactions"
|
||||
[color]="redactionColor$ | async"
|
||||
[label]="'redaction-abbreviation' | translate"
|
||||
type="square"
|
||||
></redaction-annotation-icon>
|
||||
<redaction-annotation-icon *ngIf="file().hasImages" [color]="imageColor$ | async" label="I" type="square"></redaction-annotation-icon>
|
||||
<redaction-annotation-icon *ngIf="file().hintsOnly" [color]="hintColor$ | async" label="H" type="circle"></redaction-annotation-icon>
|
||||
<mat-icon *ngIf="file().hasAnnotationComments" svgIcon="red:comment"></mat-icon>
|
||||
<ng-container *ngIf="noWorkloadItems()"> -</ng-container>
|
||||
@if (file().analysisRequired) {
|
||||
<redaction-annotation-icon [color]="analysisColor$ | async" label="A" type="square"></redaction-annotation-icon>
|
||||
}
|
||||
@if (updated()) {
|
||||
<redaction-annotation-icon [color]="updatedColor$ | async" label="U" type="square"></redaction-annotation-icon>
|
||||
}
|
||||
@if (file().hasRedactions) {
|
||||
<redaction-annotation-icon
|
||||
[color]="redactionColor$ | async"
|
||||
[label]="'redaction-abbreviation' | translate"
|
||||
type="square"
|
||||
></redaction-annotation-icon>
|
||||
}
|
||||
@if (file().hasImages) {
|
||||
<redaction-annotation-icon [color]="imageColor$ | async" label="I" type="square"></redaction-annotation-icon>
|
||||
}
|
||||
@if (file().hintsOnly) {
|
||||
<redaction-annotation-icon [color]="hintColor$ | async" label="H" type="circle"></redaction-annotation-icon>
|
||||
}
|
||||
@if (file().hasAnnotationComments) {
|
||||
<mat-icon svgIcon="red:comment"></mat-icon>
|
||||
}
|
||||
@if (noWorkloadItems()) {
|
||||
-
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, input, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, computed, input, OnInit } from '@angular/core';
|
||||
import { annotationDefaultColorConfig, DefaultBasedColorType, File } from '@red/domain';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
|
||||
@ -7,7 +7,7 @@ import { UserService } from '@users/user.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AnnotationIconComponent } from '@shared/components/annotation-icon/annotation-icon.component';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
|
||||
@ -17,7 +17,7 @@ import { MatIcon } from '@angular/material/icon';
|
||||
styleUrls: ['./file-workload.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [AnnotationIconComponent, AsyncPipe, TranslateModule, MatIcon, NgIf],
|
||||
imports: [AnnotationIconComponent, AsyncPipe, TranslateModule, MatIcon],
|
||||
})
|
||||
export class FileWorkloadComponent implements OnInit {
|
||||
#dossierTemplateId: string;
|
||||
|
||||
@ -1,78 +1,94 @@
|
||||
<div class="cell" [class.file-name-cell]="!fileAttributesService.isEditingFileAttribute()">
|
||||
<redaction-file-name-column [dossierTemplateId]="dossierTemplateId" [file]="file" [dossier]="dossier"></redaction-file-name-column>
|
||||
<redaction-file-name-column
|
||||
[dossierTemplateId]="dossierTemplateId()"
|
||||
[file]="file()"
|
||||
[dossier]="dossier()"
|
||||
></redaction-file-name-column>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<redaction-date-column [date]="file.added" [isError]="file.isError"></redaction-date-column>
|
||||
<redaction-date-column [date]="file().added" [isError]="file().isError"></redaction-date-column>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<redaction-date-column [date]="file.redactionModificationDate" [isError]="file.isError"></redaction-date-column>
|
||||
<redaction-date-column [date]="file().redactionModificationDate" [isError]="file().isError"></redaction-date-column>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let config of displayedAttributes" class="cell">
|
||||
<redaction-file-attribute [file]="file" [dossier]="dossier" [fileAttribute]="config"></redaction-file-attribute>
|
||||
</div>
|
||||
@for (config of displayedAttributes(); track config.id) {
|
||||
<div class="cell">
|
||||
<redaction-file-attribute [file]="file()" [dossier]="dossier()" [fileAttribute]="config"></redaction-file-attribute>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- always show A for error-->
|
||||
<div *ngIf="file.isError && !isDocumine" class="cell">
|
||||
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!file.isError">
|
||||
<div class="cell" *ngIf="!isDocumine">
|
||||
<redaction-file-workload *ngIf="!file.excluded" [file]="file"></redaction-file-workload>
|
||||
@if (file().isError && !isDocumine) {
|
||||
<div class="cell">
|
||||
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!file().isError) {
|
||||
@if (!isDocumine) {
|
||||
<div class="cell">
|
||||
@if (!file().excluded) {
|
||||
<redaction-file-workload [file]="file()"></redaction-file-workload>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="user-column cell">
|
||||
<iqser-initials-avatar [user]="file.assignee" [withName]="true"></iqser-initials-avatar>
|
||||
<iqser-initials-avatar [user]="file().assignee" [withName]="true"></iqser-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="iqser:pages"></mat-icon>
|
||||
{{ file.numberOfPages }}
|
||||
{{ file().numberOfPages }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<div [class.extend-cols]="file.isError" class="status-container cell">
|
||||
<div *ngIf="file.isError" class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
|
||||
<div [class.extend-cols]="file().isError" class="status-container cell">
|
||||
@if (file().isError) {
|
||||
<div class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
|
||||
}
|
||||
|
||||
<div *ngIf="file.isUnprocessed" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
|
||||
@if (file().isUnprocessed) {
|
||||
<div class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
|
||||
}
|
||||
|
||||
<div class="status-wrapper">
|
||||
<ng-container *ngIf="file.isOcrProcessing; else defaultProcessing">
|
||||
@if (file().isOcrProcessing) {
|
||||
<redaction-ocr-progress-bar
|
||||
[numberOfOCRedPages]="file.numberOfOCRedPages"
|
||||
[numberOfPagesToOCR]="file.numberOfPagesToOCR"
|
||||
[numberOfOCRedPages]="file().numberOfOCRedPages"
|
||||
[numberOfPagesToOCR]="file().numberOfPagesToOCR"
|
||||
[progressColor]="'primary'"
|
||||
></redaction-ocr-progress-bar>
|
||||
</ng-container>
|
||||
} @else {
|
||||
<redaction-processing-indicator [file]="file()"></redaction-processing-indicator>
|
||||
|
||||
@if (!file().isError && !file().isUnprocessed && !file().isInitialProcessing) {
|
||||
<iqser-status-bar
|
||||
[configs]="[
|
||||
{
|
||||
color: file().workflowStatus,
|
||||
length: 1,
|
||||
},
|
||||
]"
|
||||
></iqser-status-bar>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<ng-template #defaultProcessing>
|
||||
<redaction-processing-indicator [file]="file"></redaction-processing-indicator>
|
||||
|
||||
<iqser-status-bar
|
||||
*ngIf="!file.isError && !file.isUnprocessed && !file.isInitialProcessing"
|
||||
[configs]="[
|
||||
{
|
||||
color: file.workflowStatus,
|
||||
length: 1,
|
||||
},
|
||||
]"
|
||||
></iqser-status-bar>
|
||||
</ng-template>
|
||||
|
||||
<redaction-file-actions
|
||||
*ngIf="!file.isProcessing"
|
||||
[dossier]="dossier"
|
||||
[file]="file"
|
||||
[singleEntityAction]="true"
|
||||
class="mr-4"
|
||||
type="dossier-overview-list"
|
||||
></redaction-file-actions>
|
||||
@if (!file().isProcessing) {
|
||||
<redaction-file-actions
|
||||
[dossier]="dossier()"
|
||||
[file]="file()"
|
||||
[singleEntityAction]="true"
|
||||
class="mr-4"
|
||||
type="dossier-overview-list"
|
||||
></redaction-file-actions>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, input } from '@angular/core';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
import { Dossier, File, IFileAttributeConfig } from '@red/domain';
|
||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||
import { FileNameColumnComponent } from '@shared/components/file-name-column/file-name-column.component';
|
||||
import { DateColumnComponent } from '../../../shared-dossiers/components/date-column/date-column.component';
|
||||
import { FileAttributeComponent } from '../file-attribute/file-attribute.component';
|
||||
import { NgForOf, NgIf } from '@angular/common';
|
||||
import { AnnotationIconComponent } from '@shared/components/annotation-icon/annotation-icon.component';
|
||||
import { FileWorkloadComponent } from './file-workload/file-workload.component';
|
||||
import { InitialsAvatarComponent, IqserUsersModule } from '@common-ui/users';
|
||||
@ -25,7 +24,6 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
FileNameColumnComponent,
|
||||
DateColumnComponent,
|
||||
FileAttributeComponent,
|
||||
NgIf,
|
||||
AnnotationIconComponent,
|
||||
FileWorkloadComponent,
|
||||
IqserUsersModule,
|
||||
@ -35,15 +33,14 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
StatusBarComponent,
|
||||
FileActionsComponent,
|
||||
TranslateModule,
|
||||
NgForOf,
|
||||
InitialsAvatarComponent,
|
||||
],
|
||||
})
|
||||
export class TableItemComponent {
|
||||
@Input({ required: true }) file: File;
|
||||
@Input({ required: true }) dossier: Dossier;
|
||||
@Input({ required: true }) displayedAttributes: IFileAttributeConfig[];
|
||||
@Input({ required: true }) dossierTemplateId: string;
|
||||
readonly file = input.required<File>();
|
||||
readonly dossier = input.required<Dossier>();
|
||||
readonly displayedAttributes = input.required<IFileAttributeConfig[]>();
|
||||
readonly dossierTemplateId = input.required<string>();
|
||||
|
||||
readonly isDocumine = getConfig().IS_DOCUMINE;
|
||||
|
||||
|
||||
@ -1,21 +1,23 @@
|
||||
<div *ngIf="configService.listingMode$ | async as mode" class="view-mode-selection">
|
||||
<div class="all-caps-label" translate="view-mode.view-as"></div>
|
||||
@if (configService.listingMode$ | async; as mode) {
|
||||
<div class="view-mode-selection">
|
||||
<div class="all-caps-label" translate="view-mode.view-as"></div>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="setListingMode(listingModes.table)"
|
||||
[attr.aria-expanded]="mode === listingModes.table"
|
||||
[attr.help-mode-key]="'document_list_view'"
|
||||
[tooltip]="'view-mode.list' | translate"
|
||||
greySelected
|
||||
icon="iqser:list"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="setListingMode(listingModes.table)"
|
||||
[attr.aria-expanded]="mode === listingModes.table"
|
||||
[attr.help-mode-key]="'document_list_view'"
|
||||
[tooltip]="'view-mode.list' | translate"
|
||||
greySelected
|
||||
icon="iqser:list"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="setListingMode(listingModes.workflow)"
|
||||
[attr.aria-expanded]="mode === listingModes.workflow"
|
||||
[attr.help-mode-key]="'workflow_view'"
|
||||
[tooltip]="'view-mode.workflow' | translate"
|
||||
greySelected
|
||||
icon="iqser:lanes"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
<iqser-circle-button
|
||||
(action)="setListingMode(listingModes.workflow)"
|
||||
[attr.aria-expanded]="mode === listingModes.workflow"
|
||||
[attr.help-mode-key]="'workflow_view'"
|
||||
[tooltip]="'view-mode.workflow' | translate"
|
||||
greySelected
|
||||
icon="iqser:lanes"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { CircleButtonComponent, ListingMode, ListingModes, ListingService } from
|
||||
import { File } from '@red/domain';
|
||||
import { ConfigService } from '../../config.service';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-view-mode-selection',
|
||||
@ -11,7 +11,7 @@ import { AsyncPipe, NgIf } from '@angular/common';
|
||||
styleUrls: ['./view-mode-selection.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CircleButtonComponent, TranslateModule, AsyncPipe, NgIf],
|
||||
imports: [CircleButtonComponent, TranslateModule, AsyncPipe],
|
||||
})
|
||||
export class ViewModeSelectionComponent {
|
||||
readonly listingModes = ListingModes;
|
||||
|
||||
@ -7,41 +7,44 @@
|
||||
<div class="details">
|
||||
<div
|
||||
[attr.help-mode-key]="'workflow_view'"
|
||||
[matTooltip]="file.filename"
|
||||
[routerLink]="file.routerLink"
|
||||
[matTooltip]="file().filename"
|
||||
[routerLink]="fileRouterLink()"
|
||||
class="filename pointer"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
{{ file.filename }}
|
||||
{{ file().filename }}
|
||||
</div>
|
||||
|
||||
<redaction-file-stats [file]="file"></redaction-file-stats>
|
||||
<redaction-file-stats [file]="file()"></redaction-file-stats>
|
||||
</div>
|
||||
|
||||
<div class="user">
|
||||
<iqser-initials-avatar [user]="file.assignee"></iqser-initials-avatar>
|
||||
<iqser-initials-avatar [user]="file().assignee"></iqser-initials-avatar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let config of displayedAttributes; trackBy: trackBy" class="small-label mt-8 attribute">
|
||||
<redaction-file-attribute [dossier]="dossier" [fileAttribute]="config" [file]="file"></redaction-file-attribute>
|
||||
</div>
|
||||
@for (config of displayedAttributes(); track config.id) {
|
||||
<div class="small-label mt-8 attribute">
|
||||
<redaction-file-attribute [dossier]="dossier()" [fileAttribute]="config" [file]="file()"></redaction-file-attribute>
|
||||
</div>
|
||||
}
|
||||
|
||||
<redaction-file-workload [file]="file"></redaction-file-workload>
|
||||
<redaction-file-workload [file]="file()"></redaction-file-workload>
|
||||
|
||||
<div class="file-actions overflow-visible">
|
||||
<redaction-processing-indicator [file]="file" class="mr-8"></redaction-processing-indicator>
|
||||
<redaction-processing-indicator [file]="file()" class="mr-8"></redaction-processing-indicator>
|
||||
|
||||
<div #actionsWrapper class="actions-wrapper">
|
||||
<redaction-file-actions
|
||||
*ngIf="!file.isProcessing"
|
||||
[dossier]="dossier"
|
||||
[file]="file"
|
||||
[maxWidth]="width"
|
||||
[singleEntityAction]="true"
|
||||
iqserDisableStopPropagation
|
||||
type="dossier-overview-workflow"
|
||||
></redaction-file-actions>
|
||||
@if (!file().isProcessing) {
|
||||
<redaction-file-actions
|
||||
[dossier]="dossier()"
|
||||
[file]="file()"
|
||||
[maxWidth]="width"
|
||||
[singleEntityAction]="true"
|
||||
iqserDisableStopPropagation
|
||||
type="dossier-overview-workflow"
|
||||
></redaction-file-actions>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectorRef, Component, computed, ElementRef, Input, OnInit, Optional, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, computed, ElementRef, input, OnInit, Optional, ViewChild } from '@angular/core';
|
||||
import { DisableStopPropagationDirective, HelpModeService } from '@iqser/common-ui';
|
||||
import { Debounce, trackByFactory } from '@iqser/common-ui/lib/utils';
|
||||
import { Dossier, File, IFileAttributeConfig } from '@red/domain';
|
||||
@ -11,7 +11,7 @@ import { FileAttributeComponent } from '../file-attribute/file-attribute.compone
|
||||
import { FileWorkloadComponent } from '../table-item/file-workload/file-workload.component';
|
||||
import { ProcessingIndicatorComponent } from '@shared/components/processing-indicator/processing-indicator.component';
|
||||
import { FileActionsComponent } from '../../../shared-dossiers/components/file-actions/file-actions.component';
|
||||
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-workflow-item',
|
||||
@ -27,10 +27,8 @@ import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
|
||||
FileWorkloadComponent,
|
||||
ProcessingIndicatorComponent,
|
||||
FileActionsComponent,
|
||||
NgIf,
|
||||
DisableStopPropagationDirective,
|
||||
AsyncPipe,
|
||||
NgForOf,
|
||||
InitialsAvatarComponent,
|
||||
],
|
||||
})
|
||||
@ -38,9 +36,10 @@ export class WorkflowItemComponent implements OnInit {
|
||||
@ViewChild('actionsWrapper', { static: true }) private _actionsWrapper: ElementRef;
|
||||
width: number;
|
||||
readonly trackBy = trackByFactory();
|
||||
@Input({ required: true }) file: File;
|
||||
@Input({ required: true }) dossier: Dossier;
|
||||
@Input({ required: true }) displayedAttributes: IFileAttributeConfig[];
|
||||
readonly file = input.required<File>();
|
||||
readonly dossier = input.required<Dossier>();
|
||||
readonly displayedAttributes = input.required<IFileAttributeConfig[]>();
|
||||
readonly fileRouterLink = computed(() => this.file().routerLink);
|
||||
|
||||
constructor(
|
||||
readonly fileAttributesService: FileAttributesService,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<ng-container *ngIf="(files$ | async) && dossier$ | async as dossier">
|
||||
@if ((files$ | async) && dossier$ | async; as dossier) {
|
||||
<section>
|
||||
<redaction-dossier-overview-screen-header
|
||||
(upload)="fileInput.click()"
|
||||
@ -8,60 +8,64 @@
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<div class="content-inner">
|
||||
<div *ngIf="listingMode$ | async as mode" [class.extended]="collapsedDetails" class="content-container">
|
||||
<iqser-table
|
||||
(noDataAction)="fileInput.click()"
|
||||
*ngIf="mode === listingModes.table"
|
||||
[bulkActions]="bulkActions"
|
||||
[hasScrollButton]="true"
|
||||
[headerHelpModeKey]="'document_list'"
|
||||
[helpModeKey]="'document'"
|
||||
[itemSize]="80"
|
||||
[noDataButtonIcon]="'iqser:upload'"
|
||||
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
|
||||
[noDataIcon]="'iqser:document'"
|
||||
[noDataText]="'dossier-overview.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-overview.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[showNoDataButton]="permissionsService.canUploadFiles(dossier)"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableItemClasses]="{ disabled: disabledFn, 'last-opened': lastOpenedFn }"
|
||||
[rowIdPrefix]="'file'"
|
||||
[namePropertyKey]="'filename'"
|
||||
></iqser-table>
|
||||
@if (listingMode$ | async; as mode) {
|
||||
<div [class.extended]="collapsedDetails" class="content-container">
|
||||
@if (mode === listingModes.table) {
|
||||
<iqser-table
|
||||
(noDataAction)="fileInput.click()"
|
||||
[bulkActions]="bulkActions"
|
||||
[hasScrollButton]="true"
|
||||
[headerHelpModeKey]="'document_list'"
|
||||
[helpModeKey]="'document'"
|
||||
[itemSize]="80"
|
||||
[noDataButtonIcon]="'iqser:upload'"
|
||||
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
|
||||
[noDataIcon]="'iqser:document'"
|
||||
[noDataText]="'dossier-overview.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-overview.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[showNoDataButton]="permissionsService.canUploadFiles(dossier)"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableItemClasses]="{ disabled: disabledFn, 'last-opened': lastOpenedFn }"
|
||||
[rowIdPrefix]="'file'"
|
||||
[namePropertyKey]="'filename'"
|
||||
></iqser-table>
|
||||
} @else if (mode === listingModes.workflow) {
|
||||
<iqser-workflow
|
||||
(addElement)="fileInput.click()"
|
||||
(noDataAction)="fileInput.click()"
|
||||
[addElementIcon]="'iqser:upload'"
|
||||
[bulkActions]="bulkActions"
|
||||
[config]="workflowConfig"
|
||||
[itemClasses]="{ disabled: disabledFn }"
|
||||
[noDataButtonIcon]="'iqser:upload'"
|
||||
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
|
||||
[noDataIcon]="'iqser:document'"
|
||||
[noDataText]="'dossier-overview.no-data.title' | translate"
|
||||
[showNoDataButton]="true"
|
||||
[id]="'workflow-view'"
|
||||
addElementColumn="NEW"
|
||||
>
|
||||
<ng-template #workflowItemTemplate let-entity="entity">
|
||||
<redaction-workflow-item
|
||||
[displayedAttributes]="displayedWorkflowAttributes"
|
||||
[dossier]="dossier"
|
||||
[file]="entity"
|
||||
></redaction-workflow-item>
|
||||
</ng-template>
|
||||
</iqser-workflow>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<iqser-workflow
|
||||
(addElement)="fileInput.click()"
|
||||
(noDataAction)="fileInput.click()"
|
||||
*ngIf="mode === listingModes.workflow"
|
||||
[addElementIcon]="'iqser:upload'"
|
||||
[bulkActions]="bulkActions"
|
||||
[config]="workflowConfig"
|
||||
[itemClasses]="{ disabled: disabledFn }"
|
||||
[noDataButtonIcon]="'iqser:upload'"
|
||||
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
|
||||
[noDataIcon]="'iqser:document'"
|
||||
[noDataText]="'dossier-overview.no-data.title' | translate"
|
||||
[showNoDataButton]="true"
|
||||
[id]="'workflow-view'"
|
||||
addElementColumn="NEW"
|
||||
>
|
||||
<ng-template #workflowItemTemplate let-entity="entity">
|
||||
<redaction-workflow-item
|
||||
[displayedAttributes]="displayedWorkflowAttributes"
|
||||
[dossier]="dossier"
|
||||
[file]="entity"
|
||||
></redaction-workflow-item>
|
||||
</ng-template>
|
||||
</iqser-workflow>
|
||||
</div>
|
||||
|
||||
<div *ngIf="dossierAttributes$ | async" [class.collapsed]="collapsedDetails" class="right-container">
|
||||
<redaction-dossier-details
|
||||
(toggleCollapse)="collapsedDetails = !collapsedDetails"
|
||||
[dossierAttributes]="dossierAttributes"
|
||||
></redaction-dossier-details>
|
||||
</div>
|
||||
@if (dossierAttributes$ | async) {
|
||||
<div [class.collapsed]="collapsedDetails" class="right-container">
|
||||
<redaction-dossier-details
|
||||
(toggleCollapse)="collapsedDetails = !collapsedDetails"
|
||||
[dossierAttributes]="dossierAttributes"
|
||||
></redaction-dossier-details>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -82,7 +86,7 @@
|
||||
[file]="file"
|
||||
></redaction-table-item>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<ng-template #needsWorkFilterTemplate let-filter="filter">
|
||||
<redaction-type-filter [dossierTemplateId]="dossierTemplateId" [filter]="filter"></redaction-type-filter>
|
||||
|
||||
@ -45,7 +45,7 @@ import { filter, skip, switchMap, tap } from 'rxjs/operators';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { BulkActionsService } from '../services/bulk-actions.service';
|
||||
import { DossiersCacheService } from '@services/dossiers/dossiers-cache.service';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import { DossierOverviewScreenHeaderComponent } from '../components/screen-header/dossier-overview-screen-header.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { WorkflowItemComponent } from '../components/workflow-item/workflow-item.component';
|
||||
@ -64,7 +64,6 @@ import { TypeFilterComponent } from '@shared/components/type-filter/type-filter.
|
||||
AsyncPipe,
|
||||
IqserListingModule,
|
||||
TranslateModule,
|
||||
NgIf,
|
||||
WorkflowItemComponent,
|
||||
DossierDetailsComponent,
|
||||
DossierOverviewBulkActionsComponent,
|
||||
@ -128,11 +127,11 @@ export default class DossierOverviewScreenComponent extends ListingComponent<Fil
|
||||
this.#updateFileAttributes();
|
||||
}
|
||||
|
||||
get checkedRequiredFilters(): NestedFilter[] {
|
||||
get #checkedRequiredFilters(): NestedFilter[] {
|
||||
return this.filterService.getGroup('quickFilters')?.filters.filter(f => f.required && f.checked);
|
||||
}
|
||||
|
||||
get checkedNotRequiredFilters(): NestedFilter[] {
|
||||
get #checkedNotRequiredFilters(): NestedFilter[] {
|
||||
return this.filterService.getGroup('quickFilters')?.filters.filter(f => !f.required && f.checked);
|
||||
}
|
||||
|
||||
@ -270,8 +269,8 @@ export default class DossierOverviewScreenComponent extends ListingComponent<Fil
|
||||
this.#fileAttributeConfigs,
|
||||
this.#dossier.dossierTemplateId,
|
||||
this._needsWorkFilterTemplate,
|
||||
() => this.checkedRequiredFilters,
|
||||
() => this.checkedNotRequiredFilters,
|
||||
() => this.#checkedRequiredFilters,
|
||||
() => this.#checkedNotRequiredFilters,
|
||||
);
|
||||
this.filterService.addFilterGroups(filterGroups, true);
|
||||
}
|
||||
|
||||
@ -105,21 +105,6 @@ export class EditRedactionDialogComponent
|
||||
readonly reasonStatus = formStatusToSignal(this.form.controls.reason);
|
||||
readonly reasonValue = formValueToSignal(this.form.controls.reason);
|
||||
readonly sectionValue = formValueToSignal(this.form.controls.section);
|
||||
constructor(
|
||||
private readonly _justificationsService: JustificationsService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
) {
|
||||
super();
|
||||
|
||||
if (this.allRectangles) {
|
||||
prefillPageRange(
|
||||
this.data.annotations[0],
|
||||
this.data.allFileAnnotations,
|
||||
this.options as DetailsRadioOption<RectangleRedactOption>[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
readonly displayedDictionaryLabel = computed(() => {
|
||||
const selectedDictionaryType = this.dictionaryType();
|
||||
if (selectedDictionaryType) {
|
||||
@ -142,6 +127,14 @@ export class EditRedactionDialogComponent
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
) {
|
||||
super();
|
||||
|
||||
if (this.allRectangles) {
|
||||
prefillPageRange(
|
||||
this.data.annotations[0],
|
||||
this.data.allFileAnnotations,
|
||||
this.options as DetailsRadioOption<RectangleRedactOption>[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
|
||||
@ -8,10 +8,10 @@
|
||||
[icon]="btn.icon"
|
||||
[showDot]="btn.showDot"
|
||||
[tooltipClass]="btn.tooltipClass"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[tooltipPosition]="tooltipPosition()"
|
||||
[tooltip]="btn.tooltip | translate"
|
||||
[type]="btn.buttonType || buttonType"
|
||||
[attr.help-mode-key]="helpModeKey(btn)"
|
||||
[type]="btn.buttonType || buttonType()"
|
||||
[attr.help-mode-key]="btn.helpModeKey"
|
||||
></iqser-circle-button>
|
||||
|
||||
<!-- download redacted file-->
|
||||
@ -22,10 +22,10 @@
|
||||
[dossier]="btn.dossier"
|
||||
[files]="btn.files"
|
||||
[tooltipClass]="btn.tooltipClass"
|
||||
[tooltipPosition]="tooltipPosition"
|
||||
[type]="buttonType"
|
||||
[attr.help-mode-key]="helpModeKey(btn)"
|
||||
[singleFileDownload]="singleEntityAction"
|
||||
[tooltipPosition]="tooltipPosition()"
|
||||
[type]="buttonType()"
|
||||
[attr.help-mode-key]="btn.helpModeKey"
|
||||
[singleFileDownload]="singleEntityAction()"
|
||||
></redaction-file-download-btn>
|
||||
|
||||
<!-- exclude from redaction -->
|
||||
@ -35,10 +35,10 @@
|
||||
[checked]="btn.checked"
|
||||
[disabled]="btn.disabled"
|
||||
[id]="btn.id"
|
||||
[matTooltipPosition]="tooltipPosition"
|
||||
[matTooltipPosition]="tooltipPosition()"
|
||||
[matTooltip]="btn.tooltip | translate"
|
||||
[ngClass]="btn.class"
|
||||
[attr.help-mode-key]="helpModeKey(btn)"
|
||||
[attr.help-mode-key]="btn.helpModeKey"
|
||||
color="primary"
|
||||
iqserStopPropagation
|
||||
></mat-slide-toggle>
|
||||
@ -52,7 +52,7 @@
|
||||
[attr.aria-expanded]="expanded"
|
||||
[icon]="'iqser:more-actions'"
|
||||
[matMenuTriggerFor]="hiddenButtonsMenu"
|
||||
[type]="buttonType"
|
||||
[type]="buttonType()"
|
||||
buttonId="file-actions-menu-trigger-btn"
|
||||
></iqser-circle-button>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { booleanAttribute, Component, inject, input, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { Component, computed, effect, inject, input, viewChild } from '@angular/core';
|
||||
import { Action, ActionTypes, Dossier, File } from '@red/domain';
|
||||
import { CircleButtonComponent, CircleButtonType, IqserDialog, StopPropagationDirective, Toaster } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
@ -36,19 +36,20 @@ import { MatIcon } from '@angular/material/icon';
|
||||
StopPropagationDirective,
|
||||
],
|
||||
})
|
||||
export class ExpandableFileActionsComponent implements OnChanges {
|
||||
@Input({ required: true }) actions: Action[];
|
||||
@Input() maxWidth: number;
|
||||
@Input() minWidth: number;
|
||||
@Input() buttonType: CircleButtonType;
|
||||
@Input() tooltipPosition: IqserTooltipPosition;
|
||||
@Input() helpModeKeyPrefix: 'dossier' | 'editor';
|
||||
@Input() isDossierOverviewWorkflow = false;
|
||||
@Input() singleEntityAction = false;
|
||||
export class ExpandableFileActionsComponent {
|
||||
readonly _actions = input.required<Action[]>({ alias: 'actions' });
|
||||
readonly maxWidth = input<number>();
|
||||
readonly minWidth = input<number>();
|
||||
readonly buttonType = input<CircleButtonType>();
|
||||
readonly tooltipPosition = input<IqserTooltipPosition>();
|
||||
readonly helpModeKeyPrefix = input<'dossier' | 'editor'>();
|
||||
readonly isDossierOverviewWorkflow = input(false);
|
||||
readonly singleEntityAction = input(false);
|
||||
readonly actions = computed(() => this._actions().map(action => ({ ...action, helpModeKey: this.helpModeKey(action) })));
|
||||
displayedButtons: Action[];
|
||||
hiddenButtons: Action[];
|
||||
expanded = false;
|
||||
@ViewChild(MatMenuTrigger) readonly matMenu: MatMenuTrigger;
|
||||
readonly matMenu = viewChild<MatMenuTrigger>(MatMenuTrigger);
|
||||
readonly trackBy = trackByFactory();
|
||||
readonly #appBaseHref = inject(APP_BASE_HREF);
|
||||
|
||||
@ -57,54 +58,51 @@ export class ExpandableFileActionsComponent implements OnChanges {
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _dialog: IqserDialog,
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.actions || changes.maxWidth || changes.minWidth) {
|
||||
) {
|
||||
effect(() => {
|
||||
let count = 0;
|
||||
if (this.maxWidth) {
|
||||
count = Math.floor(this.maxWidth / 36) || 1;
|
||||
} else if (this.minWidth <= 850) {
|
||||
count = Math.floor(this.minWidth / (this.actions.length * 15)) || 1;
|
||||
if (this.minWidth <= 450) {
|
||||
if (this.maxWidth()) {
|
||||
count = Math.floor(this.maxWidth() / 36) || 1;
|
||||
} else if (this.minWidth() <= 850) {
|
||||
count = Math.floor(this.minWidth() / (this.actions().length * 15)) || 1;
|
||||
if (this.minWidth() <= 450) {
|
||||
this.displayedButtons = [];
|
||||
this.hiddenButtons = [...this.actions];
|
||||
this.hiddenButtons = [...this.actions()];
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.displayedButtons = [...this.actions];
|
||||
this.displayedButtons = [...this.actions()];
|
||||
this.hiddenButtons = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (count >= this.actions.length) {
|
||||
this.displayedButtons = [...this.actions];
|
||||
if (count >= this.actions().length) {
|
||||
this.displayedButtons = [...this.actions()];
|
||||
this.hiddenButtons = [];
|
||||
} else {
|
||||
this.displayedButtons = this.actions.slice(0, count - 1);
|
||||
this.hiddenButtons = this.actions.slice(count - 1);
|
||||
this.displayedButtons = this.actions().slice(0, count - 1);
|
||||
this.hiddenButtons = this.actions().slice(count - 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (changes.actions) {
|
||||
// Patch download button
|
||||
const downloadBtn = this.actions.find(btn => btn.type === ActionTypes.downloadBtn);
|
||||
effect(() => {
|
||||
const downloadBtn = this.actions().find(btn => btn.type === ActionTypes.downloadBtn);
|
||||
if (downloadBtn) {
|
||||
downloadBtn.action = () => this.#downloadFiles(downloadBtn.files, downloadBtn.dossier);
|
||||
downloadBtn.disabled = !this._permissionsService.canDownloadFiles(downloadBtn.files, downloadBtn.dossier);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
helpModeKey(action: Action) {
|
||||
return action.helpModeKey
|
||||
? `${this.helpModeKeyPrefix}${this.isDossierOverviewWorkflow ? '_workflow' : ''}_${action.helpModeKey}`
|
||||
? `${this.helpModeKeyPrefix()}${this.isDossierOverviewWorkflow() ? '_workflow' : ''}_${action.helpModeKey}`
|
||||
: '';
|
||||
}
|
||||
|
||||
onButtonClick(button: Action, $event: MouseEvent) {
|
||||
button.action($event);
|
||||
this.matMenu.closeMenu();
|
||||
this.matMenu().closeMenu();
|
||||
}
|
||||
|
||||
async #downloadFiles(files: File[], dossier: Dossier) {
|
||||
|
||||
@ -5,6 +5,7 @@ import { Roles } from '@users/roles';
|
||||
import { of } from 'rxjs';
|
||||
import { IIqserUser, IqserUserService } from '@iqser/common-ui/lib/users';
|
||||
import { List } from '@iqser/common-ui/lib/utils';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@ -12,6 +13,7 @@ import { List } from '@iqser/common-ui/lib/utils';
|
||||
export class UserService extends IqserUserService<IIqserUser, User> {
|
||||
protected readonly _defaultModelPath = 'user';
|
||||
protected readonly _entityClass = User;
|
||||
readonly allUsers = toSignal(this.all$);
|
||||
|
||||
async loadCurrentUser(): Promise<User | undefined> {
|
||||
const currentUser = await super.loadCurrentUser();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user