Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from "../data-fields/models/data-field-portal-data-injection-token";
import {MultichoiceField} from "../data-fields/multichoice-field/models/multichoice-field";
import {EnumerationField} from "../data-fields/enumeration-field/models/enumeration-field";
import {SelectionBehavior} from '../panel/configuration/selection-behavior';

@Component({
selector: 'ncc-abstract-header',
Expand All @@ -35,7 +36,7 @@ export abstract class AbstractHeaderComponent implements OnInit, OnDestroy {
@Input() showSortButton = true;
@Input() showSearchButton = true;
@Input() showTableSection = true;
@Input() public approval: boolean;
@Input() public showSelection: SelectionBehavior = SelectionBehavior.HIDDEN;

public headerService: AbstractHeaderService;
protected _headerSearch: HeaderSearchService;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Component, Input} from '@angular/core';
import {FormControl} from '@angular/forms';
import { SelectionBehavior } from "../../panel/configuration/selection-behavior";

@Component({
selector: 'ncc-abstract-header-mode',
Expand All @@ -8,15 +9,23 @@ import {FormControl} from '@angular/forms';
export abstract class AbstractHeaderModeComponent {

@Input() public overflowWidth: string;
@Input() public approval: boolean;
@Input() public indeterminate: boolean;
@Input() public approvalFormControl: FormControl<boolean>;
@Input() public typeApproval: string;
@Input() public showSelection: SelectionBehavior = SelectionBehavior.HIDDEN;

constructor() {
}

getMinWidth() {
return this.overflowWidth;
}

public isInSelectionMode(): boolean {
return this.showSelection !== SelectionBehavior.HIDDEN;
}

public isSelectionDisabled(): boolean {
return this.showSelection !== SelectionBehavior.EDITABLE;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, Input, Optional} from '@angular/core';
import {Component,Input,Optional} from '@angular/core';
import {Observable} from 'rxjs';
import {Case} from '../../resources/interface/case';
import {toMoment} from '../../resources/types/nae-date-type';
Expand All @@ -21,6 +21,7 @@ import {CurrencyPipe} from '@angular/common';
import {PermissionService} from '../../authorization/permission/permission.service';
import {PermissionType} from '../../process/permissions';
import {FormControl} from '@angular/forms';
import {SelectionBehavior} from '../configuration/selection-behavior';

@Component({
selector: 'ncc-abstract-case-panel',
Expand All @@ -30,13 +31,13 @@ export abstract class AbstractCasePanelComponent extends AbstractPanelWithImmedi


@Input() public case_: Case;
@Input() public approval: boolean;
@Input() responsiveBody = true;
@Input() first: boolean;
@Input() last: boolean;
@Input() showCasePanelIcon = true;
@Input() showDeleteMenu = false;
@Input() textEllipsis = false;
@Input() public showSelection: SelectionBehavior = SelectionBehavior.HIDDEN;
protected _approvalFormControl: FormControl<boolean | string>;

protected constructor(protected _caseResourceService: CaseResourceService,
Expand All @@ -47,7 +48,7 @@ export abstract class AbstractCasePanelComponent extends AbstractPanelWithImmedi
protected _userService: UserService,
protected _currencyPipe: CurrencyPipe,
protected _permissionService: PermissionService,
@Optional() protected _overflowService: OverflowService,) {
@Optional() protected _overflowService: OverflowService) {
super(_translateService, _currencyPipe, _overflowService);
this._approvalFormControl = new FormControl();
}
Expand Down Expand Up @@ -112,4 +113,12 @@ export abstract class AbstractCasePanelComponent extends AbstractPanelWithImmedi
public getMinWidth() {
return (this._overflowService && this._overflowService.overflowMode) ? `${this._overflowService.columnWidth}px` : '0';
}

public isInSelectionMode(): boolean {
return this.showSelection !== SelectionBehavior.HIDDEN;
}

public isSelectionDisabled(): boolean {
return this.showSelection !== SelectionBehavior.EDITABLE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum SelectionBehavior {
EDITABLE,
VISIBLE,
HIDDEN
}
Comment on lines +1 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider using string enum values.

Numeric enums reverse-map and serialize as integers, which makes logs and any persisted/config representation harder to read (e.g., 0 vs "EDITABLE"). Since this enum is part of the public API and likely to appear in template-facing inputs, string values are easier to debug and safer to refactor (no accidental positional dependency).

♻️ Proposed change
 export enum SelectionBehavior {
-    EDITABLE,
-    VISIBLE,
-    HIDDEN
+    EDITABLE = 'EDITABLE',
+    VISIBLE = 'VISIBLE',
+    HIDDEN = 'HIDDEN'
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export enum SelectionBehavior {
EDITABLE,
VISIBLE,
HIDDEN
}
export enum SelectionBehavior {
EDITABLE = 'EDITABLE',
VISIBLE = 'VISIBLE',
HIDDEN = 'HIDDEN'
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@projects/netgrif-components-core/src/lib/panel/configuration/selection-behavior.ts`
around lines 1 - 5, Replace the numeric enum with a string-based enum for
SelectionBehavior so values serialize and log as readable names; update the enum
declaration SelectionBehavior { EDITABLE = "EDITABLE", VISIBLE = "VISIBLE",
HIDDEN = "HIDDEN" } and then adjust any places that compare or persist these
values (references to SelectionBehavior, usages in templates, inputs,
serialization/deserialization code) to expect and handle string values instead
of numeric indexes to avoid reverse-mapping surprises.

Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export * from './task-panel/models/task-panel-context';

/* ENUM */
export * from './configuration/config-params';
export * from './configuration/selection-behavior';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {MatExpansionPanel} from '@angular/material/expansion';
import {ActivatedRoute} from '@angular/router';
import {filter} from 'rxjs/operators';
import {filter, takeUntil} from 'rxjs/operators';
import {TabbedVirtualScrollComponent} from '../../abstract/tabbed-virtual-scroll.component';
import {AfterViewInit, Component, EventEmitter, Inject, Input, OnDestroy, Optional, Output} from '@angular/core';
import {Observable, Subject, Subscription} from 'rxjs';
Expand All @@ -12,13 +12,11 @@ import {LoggerService} from '../../../logger/services/logger.service';
import {NAE_TAB_DATA} from '../../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../../tabs/interfaces';


@Component({
selector: 'ncc-abstract-default-task-list',
template: ''
})
export abstract class AbstractDefaultTaskListComponent extends TabbedVirtualScrollComponent implements AfterViewInit, OnDestroy {

protected _tasks$: Observable<Array<TaskPanelData>>;
protected _redirectTaskId: string;
protected _unsubscribe$: Subject<void>;
Expand All @@ -30,6 +28,7 @@ export abstract class AbstractDefaultTaskListComponent extends TabbedVirtualScro
@Input() forceLoadDataOnOpen = false;
@Input() textEllipsis = false;
@Input() showMoreMenu: boolean = true;
@Input() public disabled: boolean = false;

@Input()
set allowMultiOpen(bool: boolean) {
Expand Down Expand Up @@ -57,15 +56,15 @@ export abstract class AbstractDefaultTaskListComponent extends TabbedVirtualScro
this._taskPanelRefs = new Map<string, MatExpansionPanel>();
this._unsubscribe$ = new Subject<void>();
if (injectedTabData !== null) {
this._unsub = injectedTabData.tabSelected$.pipe(
filter(bool => bool)
).subscribe( () => {
if (this._canReload) {
this._taskViewService.reloadCurrentPage();
} else {
this._canReload = true;
}
});
this._unsub = injectedTabData.tabSelected$
.pipe(filter(bool => bool))
.subscribe(() => {
if (this._canReload) {
this._taskViewService.reloadCurrentPage();
} else {
this._canReload = true;
}
});
}
}

Expand Down Expand Up @@ -99,16 +98,22 @@ export abstract class AbstractDefaultTaskListComponent extends TabbedVirtualScro
}

public onRedirect() {
this.route.queryParams.pipe(filter(pm => !!pm['taskId'])).subscribe(pm => {
this._redirectTaskId = pm['taskId'];
this._tasks$.pipe().subscribe(tasks => {
const task = tasks.find(t => t.task.stringId === this._redirectTaskId);
if (!!task && !task.initiallyExpanded) {
this._taskPanelRefs.get(this._redirectTaskId).open();
this._taskPanelRefs.get(this._redirectTaskId).expanded = true;
this._unsubscribe$.next();
}
if (!this.route) {
return;
}
this.route.queryParams
.pipe(filter(pm => !!pm['taskId']), takeUntil(this._unsubscribe$))
.subscribe(pm => {
this._redirectTaskId = pm['taskId'];
this._tasks$.pipe(takeUntil(this._unsubscribe$)).subscribe(tasks => {
const task = tasks.find(t => t.task.stringId === this._redirectTaskId);
const panelRef = this._taskPanelRefs.get(this._redirectTaskId);
if (!!task && !task.initiallyExpanded && !!panelRef) {
panelRef.open();
panelRef.expanded = true;
this._unsubscribe$.next();
}
});
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export abstract class AbstractTaskListPaginationComponent extends AbstractDefaul
public pageIndex = 0;
public pageSizeOptions: Array<number> = [10, 20, 50, 100];

@Input() public disabled: boolean;
@Input()
set tasks$(tasks: Observable<Array<TaskPanelData>>) {
this._tasks$ = tasks.pipe((tap(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export abstract class AbstractTaskPanelComponent extends AbstractPanelWithImmedi
@Input() actionRowJustifyContent: 'space-between' | 'flex-start' | 'flex-end' | 'center' | 'space-around' |
'initial' | 'start' | 'end' | 'left' | 'right' | 'revert' | 'inherit' | 'unset'
@Input() showMoreMenu: boolean = true;
@Input() public disabled: boolean = false;

thisContext: TaskPanelContext = {
canAssign: () => this.canAssign(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ export abstract class AbstractCaseListPaginatorComponent extends AbstractDefault
public pageSize = 20;
public pageIndex = 0;
public pageSizeOptions: number[] = [10, 20, 50, 100];
@Input() public approval: boolean;
@Input() public disabled: boolean;

constructor(protected _caseViewService: CaseViewService,
protected _log: LoggerService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {InjectedTabData} from '../../../../tabs/interfaces';
import {ActivatedRoute} from '@angular/router';
import {filter, takeUntil} from 'rxjs/operators';
import {TabbedVirtualScrollComponent} from '../../../../panel/abstract/tabbed-virtual-scroll.component';
import {SelectionBehavior} from '../../../../panel/configuration/selection-behavior';

@Component({
selector: 'ncc-abstract-default-case-list',
Expand All @@ -24,6 +25,7 @@ export abstract class AbstractDefaultCaseListComponent extends TabbedVirtualScro
@Input() textEllipsis = false;
@Input() width: string;
@Input() redirectEnabled = true;
@Input() public showSelection: SelectionBehavior = SelectionBehavior.HIDDEN;

public cases$: Observable<Array<Case>>;
public loading$: Observable<boolean>;
Expand Down Expand Up @@ -84,4 +86,8 @@ export abstract class AbstractDefaultCaseListComponent extends TabbedVirtualScro
});
});
}

public isSelectionDisabled(): boolean {
return this.showSelection !== SelectionBehavior.EDITABLE;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center" class="netgrif-input netgrif-header-input netgrif-input-fix netgrif-zero-field-wrapper">
<mat-checkbox *ngIf="approval && typeApproval === 'multichoice'" [formControl]="approvalFormControl" [indeterminate]="indeterminate"
(click)="$event.stopPropagation();" color='primary' class="checkbox-padding"></mat-checkbox>
<mat-icon *ngIf="approval && typeApproval === 'enumeration'" color="warn" (click)="setValue();$event.stopPropagation();" class="checkbox-padding cursor-fix">close</mat-icon>
<mat-checkbox *ngIf="isInSelectionMode() && typeApproval === 'multichoice'" [formControl]="approvalFormControl" [indeterminate]="indeterminate"
(click)="$event.stopPropagation();" color='primary' class="checkbox-padding" [disabled]="isSelectionDisabled()"></mat-checkbox>
<button *ngIf="isInSelectionMode() && typeApproval === 'enumeration'" mat-icon-button type="button" aria-label="Clear selected"
class="checkbox-padding cursor-fix" [disabled]="isSelectionDisabled()" (click)="setValue(); $event.stopPropagation();" [disableRipple]="true">
<mat-icon color="warn">close</mat-icon>
</button>
Comment on lines +2 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider making aria-label translatable.

The selection controls correctly implement disabled behavior per past reviews. The hardcoded aria-label="Clear selected" could be replaced with a translation key for internationalization support.

♻️ Optional i18n enhancement
-    <button *ngIf="isInSelectionMode() && typeApproval === 'enumeration'" mat-icon-button  type="button" aria-label="Clear selected"
+    <button *ngIf="isInSelectionMode() && typeApproval === 'enumeration'" mat-icon-button  type="button" [attr.aria-label]="'headers.clearSelection' | translate"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<mat-checkbox *ngIf="isInSelectionMode() && typeApproval === 'multichoice'" [formControl]="approvalFormControl" [indeterminate]="indeterminate"
(click)="$event.stopPropagation();" color='primary' class="checkbox-padding" [disabled]="isSelectionDisabled()"></mat-checkbox>
<button *ngIf="isInSelectionMode() && typeApproval === 'enumeration'" mat-icon-button type="button" aria-label="Clear selected"
class="checkbox-padding cursor-fix" [disabled]="isSelectionDisabled()" (click)="setValue(); $event.stopPropagation();" [disableRipple]="true">
<mat-icon color="warn">close</mat-icon>
</button>
<mat-checkbox *ngIf="isInSelectionMode() && typeApproval === 'multichoice'" [formControl]="approvalFormControl" [indeterminate]="indeterminate"
(click)="$event.stopPropagation();" color='primary' class="checkbox-padding" [disabled]="isSelectionDisabled()"></mat-checkbox>
<button *ngIf="isInSelectionMode() && typeApproval === 'enumeration'" mat-icon-button type="button" [attr.aria-label]="'headers.clearSelection' | translate"
class="checkbox-padding cursor-fix" [disabled]="isSelectionDisabled()" (click)="setValue(); $event.stopPropagation();" [disableRipple]="true">
<mat-icon color="warn">close</mat-icon>
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@projects/netgrif-components/src/lib/header/header-modes/edit-mode/edit-mode.component.html`
around lines 2 - 7, Replace the hardcoded aria-label on the clear-selection
button with a translatable string: remove aria-label="Clear selected" and use
your app's translation mechanism (e.g., a translate pipe or i18n key) so the
label is internationalized; update the template for the button that references
setValue() and isInSelectionMode()/typeApproval to use the translation key
(e.g., aria-label="{{ 'header.clearSelected' | translate }}" or Angular i18n
attribute) and add the corresponding translation entry for that key in the
locale files.

<mat-form-field *ngFor="let header of this.headerService.selectedHeaders$ | async; let i = index" fxLayout="row"
fxLayoutAlign=" center" fxFlex [ngStyle]="{'min-width': getMinWidth()}" appearance="outline"
[fxHide.lt-xl]="i >= 4 && this.headerService.responsiveHeaders && !this.headerService.overflowMode"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<div fxFlex="100" fxLayout="row" fxLayoutAlign=" center">
<mat-checkbox *ngIf="approval && typeApproval === 'multichoice'" [formControl]="approvalFormControl" [indeterminate]="indeterminate"
(click)="$event.stopPropagation();" color='primary' class="checkbox-padding"></mat-checkbox>
<mat-icon *ngIf="approval && typeApproval === 'enumeration'" color="warn" (click)="setValue();$event.stopPropagation();" class="checkbox-padding cursor-fix">close</mat-icon>
<mat-checkbox *ngIf="isInSelectionMode() && typeApproval === 'multichoice'" [formControl]="approvalFormControl" [indeterminate]="indeterminate"
(click)="$event.stopPropagation();" color='primary' class="checkbox-padding" [disabled]="isSelectionDisabled()"></mat-checkbox>
<button *ngIf="isInSelectionMode() && typeApproval === 'enumeration'" mat-icon-button type="button" aria-label="Clear selected"
class="checkbox-padding cursor-fix" [disabled]="isSelectionDisabled()" (click)="setValue(); $event.stopPropagation();" [disableRipple]="true">
<mat-icon color="warn">close</mat-icon>
</button>
Comment on lines +2 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

Consider extracting duplicated selection controls into a shared component.

The selection control markup in lines 2-7 is identical to edit-mode and sort-mode templates. Extracting this into a reusable component would improve maintainability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@projects/netgrif-components/src/lib/header/header-modes/search-mode/search-mode.component.html`
around lines 2 - 7, Extract the duplicated selection controls into a reusable
component: create a new component (e.g., SelectionControlComponent) that accepts
inputs for typeApproval, approvalFormControl, indeterminate, and disabled (bound
to isSelectionDisabled()), and emits click or clear events that call setValue()
or stop propagation; replace the duplicated markup in search-mode, edit-mode and
sort-mode templates with this new component and wire their local
methods/properties (isInSelectionMode(), typeApproval, approvalFormControl,
indeterminate, isSelectionDisabled(), setValue()) to the new component's
inputs/outputs so behavior and accessibility are preserved.

<div *ngFor="let header of this.headerService.selectedHeaders$ | async; let i = index"
[fxHide.lt-xl]="i >= 4 && this.headerService.responsiveHeaders && !this.headerService.overflowMode"
[fxHide.lt-lg]="i >= 3 && this.headerService.responsiveHeaders && !this.headerService.overflowMode"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<div matSort (matSortChange)="sortHeaderChanged($event)" fxFlex="100" fxLayout="row" fxLayoutAlign=" center">
<mat-checkbox *ngIf="approval && typeApproval === 'multichoice'" [formControl]="approvalFormControl" [indeterminate]="indeterminate"
(click)="$event.stopPropagation();" color='primary' class="checkbox-padding"></mat-checkbox>
<mat-icon *ngIf="approval && typeApproval === 'enumeration'" color="warn" (click)="setValue();$event.stopPropagation();" class="checkbox-padding cursor-fix">close</mat-icon>
<mat-checkbox *ngIf="isInSelectionMode() && typeApproval === 'multichoice'" [formControl]="approvalFormControl" [indeterminate]="indeterminate"
(click)="$event.stopPropagation();" color='primary' class="checkbox-padding" [disabled]="isSelectionDisabled()"></mat-checkbox>
<button *ngIf="isInSelectionMode() && typeApproval === 'enumeration'" mat-icon-button type="button" aria-label="Clear selected"
class="checkbox-padding cursor-fix" [disabled]="isSelectionDisabled()" (click)="setValue(); $event.stopPropagation();" [disableRipple]="true">
<mat-icon color="warn">close</mat-icon>
</button>
<div [ngStyle]="{'min-width': getMinWidth()}" *ngFor="let header of headerService.headerState.selectedHeaders$ | async; let i = index" fxFlex
[fxHide.lt-xl]="i >= 4 && this.headerService.responsiveHeaders && !this.headerService.overflowMode"
[fxHide.lt-lg]="i >= 3 && this.headerService.responsiveHeaders && !this.headerService.overflowMode"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@
[overflowWidth]="getMinWidth()"
[approvalFormControl]="approvalFormControl"
[indeterminate]="indeterminate()"
[approval]="approval"
[typeApproval]="typeApproval()"
[showSelection]="showSelection"
></nc-sort-mode>
</div>
<div *ngSwitchCase="headerModeEnum.SEARCH" fxFlex="100" fxLayout="row">
<nc-search-mode fxFlex="100" [headerService]="headerService"
[overflowWidth]="getMinWidth()"
[approvalFormControl]="approvalFormControl"
[indeterminate]="indeterminate()"
[approval]="approval"
[typeApproval]="typeApproval()"
[showSelection]="showSelection"
></nc-search-mode>
</div>
<div *ngSwitchCase="headerModeEnum.EDIT" fxFlex="100" fxLayout="row">
<nc-edit-mode fxFlex="100" [headerService]="headerService"
[overflowWidth]="getMinWidth()"
[approvalFormControl]="approvalFormControl"
[indeterminate]="indeterminate()"
[approval]="approval"
[typeApproval]="typeApproval()"
[showSelection]="showSelection"
></nc-edit-mode>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

<div class="full-height transform-div custom-scrollbar" [ngClass]="{'overflow-div': getOverflowStatus()}" fxLayout="column" fxLayoutAlign="start stretch">
<div class="full-height transform-div max-width-fix" fxLayout="column" fxLayoutAlign="start stretch">
<nc-header #header [type]="headerType" [responsiveHeaders]="true" class="case-header-padding" [ngStyle]="{'width': getWidth()}" [approval]="isApproval()"></nc-header>
<nc-header #header [type]="headerType" [responsiveHeaders]="true" class="case-header-padding" [ngStyle]="{'width': getWidth()}" [showSelection]="resolveSelectionBehavior()"></nc-header>

<nc-case-list-paginator [selectedHeaders$]="selectedHeaders$" [showDeleteMenu]="false" [width]="getWidth()" [approval]="isApproval()" [disabled]="disabled()"
<nc-case-list-paginator [selectedHeaders$]="selectedHeaders$" [showDeleteMenu]="false" [width]="getWidth()" [showSelection]="resolveSelectionBehavior()"
(caseClick)="handleCaseClick($event)" [responsiveBody]="true" fxFlex [textEllipsis]="true"></nc-case-list-paginator>
</div>
</div>
Expand Down
Loading
Loading