Compare commits

...

8 Commits

Author SHA1 Message Date
Nicoleta Panaghiu
3909614988 RED-10509: added disableStopPropagation option for action config. 2024-11-24 14:02:13 +02:00
Nicoleta Panaghiu
b8ba2191f7 RED-9372: always include scroll-bar mixin. 2024-09-10 11:45:59 +03:00
Nicoleta Panaghiu
25f63ef7c6 RED-9372: fixed table-items moving on hover. 2024-09-05 17:21:20 +03:00
Nicoleta Panaghiu
efd7e2a085 RED-9987: added sendSetPasswordMail flag. 2024-09-03 14:44:45 +03:00
Nicoleta Panaghiu
f685955f8f RED-9504: use translate pipe & clean imports. 2024-08-02 10:53:42 +03:00
Nicoleta Panaghiu
7edde2a01f RED-9731: fixed help button position on resize. 2024-08-01 14:41:40 +03:00
Nicoleta Panaghiu
e6e0686794 RED-9516: fixed filter options alignment. 2024-08-01 13:15:50 +03:00
Nicoleta Panaghiu
43bfeb4e1e RED-9571: fixed initials-avatar not updating in some cases. 2024-07-31 12:48:08 +03:00
12 changed files with 147 additions and 162 deletions

View File

@ -159,10 +159,10 @@ section.settings {
transition: transition:
width ease-in-out 0.2s, width ease-in-out 0.2s,
min-width ease-in-out 0.2s; min-width ease-in-out 0.2s;
@include common-mixins.scroll-bar;
&:hover { &:hover {
overflow-y: auto; overflow-y: auto;
@include common-mixins.scroll-bar;
} }
.collapsed-wrapper { .collapsed-wrapper {

View File

@ -25,7 +25,7 @@
.mat-mdc-menu-item { .mat-mdc-menu-item {
font-size: var(--iqser-font-size); font-size: var(--iqser-font-size);
color: var(--iqser-text); color: var(--iqser-text);
padding: 0 26px 0 8px; padding: 0 26px 0 8px !important;
margin: var(--iqser-menu-item-margin); margin: var(--iqser-menu-item-margin);
border-radius: 4px; border-radius: 4px;
width: -webkit-fill-available; width: -webkit-fill-available;
@ -58,7 +58,7 @@
} }
&.padding-left { &.padding-left {
padding-left: 56px; padding-left: 56px !important;
} }
&:last-of-type { &:last-of-type {

View File

@ -1,5 +1,5 @@
/* eslint-disable @angular-eslint/prefer-on-push-component-change-detection */ /* eslint-disable @angular-eslint/prefer-on-push-component-change-detection */
import { Component, effect, ElementRef, Input, OnDestroy, OnInit, viewChild } from '@angular/core'; import { Component, effect, ElementRef, HostListener, Input, OnDestroy, OnInit, viewChild } from '@angular/core';
import { MatIcon } from '@angular/material/icon'; import { MatIcon } from '@angular/material/icon';
import { HelpModeService } from '../help-mode.service'; import { HelpModeService } from '../help-mode.service';
import { MatTooltip } from '@angular/material/tooltip'; import { MatTooltip } from '@angular/material/tooltip';
@ -26,9 +26,9 @@ export class HelpButtonComponent implements OnInit, OnDestroy {
effect(() => { effect(() => {
if (this.helpModeService.isHelpModeActive()) { if (this.helpModeService.isHelpModeActive()) {
this.#helpModeHasBeenActivated = true; this.#helpModeHasBeenActivated = true;
this.#applyActiveButtonStyles(); setTimeout(() => this.#applyActiveButtonStyles(), 300);
} else if (this.#helpModeHasBeenActivated) { } else if (this.#helpModeHasBeenActivated) {
this.#applyInactiveButtonStyles(); setTimeout(() => this.#applyInactiveButtonStyles(), 300);
} }
}); });
} }
@ -42,6 +42,15 @@ export class HelpButtonComponent implements OnInit, OnDestroy {
return `help-mode-button${this.dialogButton ? '-dialog' : ''}`; return `help-mode-button${this.dialogButton ? '-dialog' : ''}`;
} }
get currentComponentRect() {
return this._elementRef.nativeElement.getBoundingClientRect();
}
@HostListener('window:resize', ['$event'])
onResize() {
if (this.helpModeService.isHelpModeActive()) this.#applyActiveButtonStyles();
}
ngOnInit() { ngOnInit() {
if (this.dialogButton) { if (this.dialogButton) {
const defaultButton = document.getElementById('help-mode-button') as HTMLElement; const defaultButton = document.getElementById('help-mode-button') as HTMLElement;
@ -53,8 +62,11 @@ export class HelpButtonComponent implements OnInit, OnDestroy {
if (this.dialogButton) { if (this.dialogButton) {
const defaultButton = document.getElementById('help-mode-button') as HTMLElement; const defaultButton = document.getElementById('help-mode-button') as HTMLElement;
defaultButton.style.removeProperty('z-index'); defaultButton.style.removeProperty('z-index');
if (!this.helpModeService.isHelpModeActive()) { if (!this.helpModeService.isHelpModeActive()) {
document.querySelectorAll('iqser-help-button')[this.dialogButton ? 1 : 0]?.removeChild(this.helpModeButton().nativeElement); const helpButtonElement = document.querySelectorAll('iqser-help-button')[this.dialogButton ? 1 : 0];
if (helpButtonElement.contains(this.helpModeButton().nativeElement))
helpButtonElement?.removeChild(this.helpModeButton().nativeElement);
} }
} }
} }
@ -68,23 +80,17 @@ export class HelpButtonComponent implements OnInit, OnDestroy {
} }
#applyActiveButtonStyles() { #applyActiveButtonStyles() {
const currentComponent = this._elementRef.nativeElement; this.helpModeButton().nativeElement.style.setProperty('position', 'absolute');
const currentComponentRect = currentComponent.getBoundingClientRect(); this.helpModeButton().nativeElement.style.setProperty('top', `${this.currentComponentRect.top}px`);
setTimeout(() => { this.helpModeButton().nativeElement.style.setProperty('left', `${this.currentComponentRect.left}px`);
this.helpModeButton().nativeElement.style.setProperty('position', 'absolute'); document.body.appendChild(this.helpModeButton().nativeElement);
this.helpModeButton().nativeElement.style.setProperty('top', `${currentComponentRect.top}px`);
this.helpModeButton().nativeElement.style.setProperty('left', `${currentComponentRect.left}px`);
document.body.appendChild(this.helpModeButton().nativeElement);
}, 500);
} }
#applyInactiveButtonStyles() { #applyInactiveButtonStyles() {
setTimeout(() => { this.helpModeButton().nativeElement.style.setProperty('position', 'relative');
this.helpModeButton().nativeElement.style.setProperty('position', 'relative'); this.helpModeButton().nativeElement.style.setProperty('top', 'unset');
this.helpModeButton().nativeElement.style.setProperty('top', 'unset'); this.helpModeButton().nativeElement.style.setProperty('left', 'unset');
this.helpModeButton().nativeElement.style.setProperty('left', 'unset'); document.body.removeChild(this.helpModeButton().nativeElement);
document.body.removeChild(this.helpModeButton().nativeElement); document.querySelectorAll('iqser-help-button')[this.dialogButton ? 1 : 0]?.appendChild(this.helpModeButton().nativeElement);
document.querySelectorAll('iqser-help-button')[this.dialogButton ? 1 : 0]?.appendChild(this.helpModeButton().nativeElement);
}, 500);
} }
} }

View File

@ -1,9 +1,10 @@
import { BaseHeaderConfig } from './base-config.model'; import { BaseHeaderConfig } from './base-config.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { OverlappingElement } from '../../../help-mode';
export interface ActionConfig extends BaseHeaderConfig { export interface ActionConfig extends BaseHeaderConfig {
readonly action: ($event: MouseEvent) => void; readonly action: ($event: MouseEvent) => void;
readonly helpModeKey?: string; readonly helpModeKey?: string;
readonly disabled$?: Observable<boolean>; readonly disabled$?: Observable<boolean>;
readonly disabled?: boolean;
readonly disableStopPropagation?: boolean;
} }

View File

@ -1,20 +1,20 @@
<div class="page-header"> <div class="page-header">
@if (pageLabel) { @if (pageLabel()) {
<div class="breadcrumb">{{ pageLabel }}</div> <div class="breadcrumb">{{ pageLabel() }}</div>
} }
@if (filters$ | async; as filters) { @if (filters$ | async; as filters) {
<div class="filters"> <div class="filters">
<ng-content select="[slot=beforeFilters]"></ng-content> <ng-content select="[slot=beforeFilters]"></ng-content>
@if (filters.length && searchPosition !== searchPositions.beforeFilters) { @if (filters.length && searchPosition() !== searchPositions.beforeFilters) {
<div class="text-muted" translate="filters.filter-by"></div> <div class="text-muted" translate="filters.filter-by"></div>
} }
@if (searchPosition === searchPositions.beforeFilters) { @if (searchPosition() === searchPositions.beforeFilters) {
<ng-container [ngTemplateOutlet]="searchBar"></ng-container> <ng-container [ngTemplateOutlet]="searchBar"></ng-container>
} }
@for (filter of filters; track trackByLabel($index, filter)) { @for (filter of filters; track trackByLabel($index, filter)) {
@if (!filter.hide) { @if (!filter.hide) {
<iqser-popup-filter [primaryFiltersSlug]="filter.slug" [attr.help-mode-key]="filterHelpModeKey"></iqser-popup-filter> <iqser-popup-filter [primaryFiltersSlug]="filter.slug" [attr.help-mode-key]="filterHelpModeKey()"></iqser-popup-filter>
} }
} }
@for (filter$ of filterService.singleFilters; track filter$) { @for (filter$ of filterService.singleFilters; track filter$) {
@ -22,13 +22,13 @@
<iqser-single-filter [filter]="filter"></iqser-single-filter> <iqser-single-filter [filter]="filter"></iqser-single-filter>
} }
} }
@if (searchPosition === searchPositions.afterFilters) { @if (searchPosition() === searchPositions.afterFilters) {
<ng-container [ngTemplateOutlet]="searchBar"></ng-container> <ng-container [ngTemplateOutlet]="searchBar"></ng-container>
} }
@if (!hideResetButton && (showResetFilters$ | async) === true) { @if (!hideResetButton() && (showResetFilters$ | async) === true) {
<div <div
(click)="resetFilters()" (click)="resetFilters()"
[attr.help-mode-key]="'filter_' + helpModeKey + '_list'" [attr.help-mode-key]="'filter_' + helpModeKey() + '_list'"
class="reset-filters" class="reset-filters"
translate="reset-filters" translate="reset-filters"
></div> ></div>
@ -36,17 +36,17 @@
</div> </div>
} }
@if (showCloseButton || actionConfigs || buttonConfigs || viewModeSelection) { @if (showCloseButton() || actionConfigs() || buttonConfigs() || viewModeSelection()) {
<div class="actions"> <div class="actions">
@if (searchPosition === searchPositions.withActions) { @if (searchPosition() === searchPositions.withActions) {
<ng-container [ngTemplateOutlet]="searchBar"></ng-container> <ng-container [ngTemplateOutlet]="searchBar"></ng-container>
} }
<ng-container [ngTemplateOutlet]="viewModeSelection"></ng-container> <ng-container [ngTemplateOutlet]="viewModeSelection()"></ng-container>
@for (config of buttonConfigs; track trackByLabel($index, config)) { @for (config of buttonConfigs(); track trackByLabel($index, config)) {
@if (!config.hide) { @if (!config.hide) {
<iqser-icon-button <iqser-icon-button
(action)="config.action($event)" (action)="config.action($event)"
[buttonId]="config.label.replace('.', '-')" [buttonId]="(config.label | translate).replace('.', '-')"
[icon]="config.icon" [icon]="config.icon"
[label]="config.label | translate" [label]="config.label | translate"
[type]="config.type" [type]="config.type"
@ -55,25 +55,26 @@
} }
} }
<div class="actions"> <div class="actions">
@for (config of actionConfigs; track trackByLabel($index, config)) { @for (config of actionConfigs(); track trackByLabel($index, config)) {
@if (!config.hide) { @if (!config.hide) {
<iqser-circle-button <iqser-circle-button
(action)="config.action($event)" (action)="config.action($event)"
[buttonId]="config.id" [buttonId]="config.id"
[disabled]="config.disabled$ && (config.disabled$ | async)" [disabled]="config.disabled"
[icon]="config.icon" [icon]="config.icon"
[tooltip]="config.label" [tooltip]="config.label | translate"
[attr.help-mode-key]="config.helpModeKey" [attr.help-mode-key]="config.helpModeKey"
[iqserDisableStopPropagation]="config.disableStopPropagation"
></iqser-circle-button> ></iqser-circle-button>
} }
} }
<!-- Extra custom actions here --> <!-- Extra custom actions here -->
<ng-content select="[slot=right]"></ng-content> <ng-content select="[slot=right]"></ng-content>
@if (showCloseButton) { @if (showCloseButton()) {
<iqser-circle-button <iqser-circle-button
buttonId="close-view-btn" buttonId="close-view-btn"
(action)="closeAction.emit()" (action)="closeAction.emit()"
[class.ml-6]="actionConfigs" [class.ml-6]="actionConfigs()"
[icon]="'iqser:close'" [icon]="'iqser:close'"
[attr.help-mode-key]="'close_dossier'" [attr.help-mode-key]="'close_dossier'"
[tooltip]="'common.close' | translate" [tooltip]="'common.close' | translate"
@ -85,14 +86,14 @@
</div> </div>
<ng-template #searchBar> <ng-template #searchBar>
@if (searchPlaceholder && searchService) { @if (searchPlaceholder() && searchService) {
<iqser-input-with-action <iqser-input-with-action
[inputId]="searchInputId" [inputId]="searchInputId()"
(valueChange)="searchService.searchValue = $event" (valueChange)="searchService.searchValue = $event"
[class.mr-8]="searchPosition === searchPositions.beforeFilters" [class.mr-8]="searchPosition() === searchPositions.beforeFilters"
[placeholder]="searchPlaceholder" [placeholder]="searchPlaceholder()"
[value]="searchService.valueChanges$ | async" [value]="searchService.valueChanges$ | async"
[width]="searchWidth" [width]="searchWidth()"
></iqser-input-with-action> ></iqser-input-with-action>
} }
</ng-template> </ng-template>

View File

@ -1,20 +1,21 @@
import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output, TemplateRef } from '@angular/core'; import { ChangeDetectionStrategy, Component, computed, inject, input, output, TemplateRef } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { combineLatest, Observable, of } from 'rxjs'; import { combineLatest, Observable, of } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { CircleButtonComponent } from '../../buttons/circle-button/circle-button.component'; import { CircleButtonComponent } from '../../buttons';
import { IconButtonComponent } from '../../buttons/icon-button/icon-button.component'; import { IconButtonComponent } from '../../buttons';
import { IconButtonTypes } from '../../buttons/types/icon-button.type'; import { IconButtonTypes } from '../../buttons';
import { FilterService } from '../../filtering/filter.service'; import { FilterService } from '../../filtering';
import { IqserFiltersModule } from '../../filtering/filters.module'; import { IqserFiltersModule } from '../../filtering';
import { PopupFilterComponent } from '../../filtering/popup-filter/popup-filter.component'; import { PopupFilterComponent } from '../../filtering';
import { InputWithActionComponent } from '../../inputs/input-with-action/input-with-action.component'; import { InputWithActionComponent } from '../../inputs/input-with-action/input-with-action.component';
import { SearchService } from '../../search/search.service'; import { SearchService } from '../../search';
import { filterEach } from '../../utils/operators'; import { filterEach } from '../../utils';
import { List } from '../../utils/types/iqser-types'; import { List } from '../../utils';
import { IListable } from '../models/listable'; import { IListable } from '../models';
import { ActionConfig, ButtonConfig, SearchPosition, SearchPositions } from './models'; import { ActionConfig, ButtonConfig, SearchPosition, SearchPositions } from './models';
import { DisableStopPropagationDirective } from '../../directives';
@Component({ @Component({
selector: 'iqser-page-header', selector: 'iqser-page-header',
@ -31,33 +32,35 @@ import { ActionConfig, ButtonConfig, SearchPosition, SearchPositions } from './m
CircleButtonComponent, CircleButtonComponent,
TranslateModule, TranslateModule,
InputWithActionComponent, InputWithActionComponent,
DisableStopPropagationDirective,
], ],
}) })
export class PageHeaderComponent<T extends IListable> { export class PageHeaderComponent<T extends IListable> {
readonly searchPositions = SearchPositions; readonly searchPositions = SearchPositions;
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
@Input() pageLabel?: string;
@Input() searchInputId?: string;
@Input() showCloseButton = false;
@Input() hideResetButton = false;
@Input() actionConfigs?: List<ActionConfig>;
@Input() buttonConfigs?: List<ButtonConfig>;
@Input() viewModeSelection?: TemplateRef<unknown>;
@Input() searchPlaceholder?: string;
@Input() searchWidth?: number | 'full';
@Input() helpModeKey?: 'dossier' | 'document';
@Input() searchPosition: SearchPosition = SearchPositions.afterFilters;
@Output() readonly closeAction = new EventEmitter();
readonly filterService = inject(FilterService, { optional: true }); readonly filterService = inject(FilterService, { optional: true });
readonly searchService = inject<SearchService<T>>(SearchService<T>, { optional: true }); readonly searchService = inject<SearchService<T>>(SearchService<T>, { optional: true });
readonly pageLabel = input<string>();
readonly searchInputId = input<string>();
readonly showCloseButton = input(false);
readonly hideResetButton = input(false);
readonly actionConfigs = input<List<ActionConfig>>();
readonly buttonConfigs = input<List<ButtonConfig>>();
readonly viewModeSelection = input<TemplateRef<unknown>>();
readonly searchPlaceholder = input<string>();
readonly searchWidth = input<number | 'full'>();
readonly helpModeKey = input<'dossier' | 'document'>();
readonly searchPosition = input<SearchPosition>(SearchPositions.afterFilters);
readonly closeAction = output();
readonly filterHelpModeKey = computed(() =>
this.helpModeKey() ? (this.helpModeKey() === 'dossier' ? 'filter_dossier_list' : 'filter_documents') : '',
);
readonly filters$ = this.filterService?.filterGroups$.pipe(filterEach(f => !!f.icon)); readonly filters$ = this.filterService?.filterGroups$.pipe(filterEach(f => !!f.icon));
readonly showResetFilters$ = this.#showResetFilters$; readonly showResetFilters$ = this.#showResetFilters$;
get filterHelpModeKey() {
return this.helpModeKey ? (this.helpModeKey === 'dossier' ? 'filter_dossier_list' : 'filter_documents') : '';
}
get #showResetFilters$(): Observable<boolean> { get #showResetFilters$(): Observable<boolean> {
if (!this.filterService) { if (!this.filterService) {
return of(false); return of(false);

View File

@ -2,7 +2,7 @@
:host cdk-virtual-scroll-viewport { :host cdk-virtual-scroll-viewport {
height: calc(100vh - 50px - 31px - var(--iqser-top-bar-height) - 50px); height: calc(100vh - 50px - 31px - var(--iqser-top-bar-height) - 50px);
overflow-y: hidden !important; overflow-y: auto !important;
background-color: var(--iqser-background); background-color: var(--iqser-background);
@include mixins.scroll-bar; @include mixins.scroll-bar;

View File

@ -87,18 +87,17 @@ export class TableComponent<Class extends IListable<PrimaryKey>, PrimaryKey exte
} }
private _setColumnsWidth(element: HTMLElement) { private _setColumnsWidth(element: HTMLElement) {
let gridTemplateColumnsHover = ''; let gridTemplateColumns = '';
if (this.selectionEnabled) { if (this.selectionEnabled) {
gridTemplateColumnsHover += '30px '; gridTemplateColumns += '30px ';
} }
for (const config of this.tableColumnConfigs) { for (const config of this.tableColumnConfigs) {
gridTemplateColumnsHover += `${config.width || '1fr'} `; gridTemplateColumns += `${config.width || '1fr'} `;
} }
gridTemplateColumnsHover += this.emptyColumnWidth || ''; gridTemplateColumns += this.emptyColumnWidth || '';
const gridTemplateColumns = `${gridTemplateColumnsHover} ${SCROLLBAR_WIDTH}px`; gridTemplateColumns = `${gridTemplateColumns} ${SCROLLBAR_WIDTH}px`;
element.style.setProperty('--gridTemplateColumns', gridTemplateColumns); element.style.setProperty('--gridTemplateColumns', gridTemplateColumns);
element.style.setProperty('--gridTemplateColumnsHover', gridTemplateColumnsHover);
} }
private _setItemSize(element: HTMLElement) { private _setItemSize(element: HTMLElement) {

View File

@ -1,14 +1,14 @@
@if (_user && _user | name: namePipeOptions; as userName) { @if (_user() && _user() | name: namePipeOptions(); as userName) {
<div class="wrapper"> <div class="wrapper">
<div <div
[className]="colorClass + ' oval ' + size + (hasBorder ? ' border' : '')" [className]="colorClass() + ' oval ' + size() + (hasBorder() ? ' border' : '')"
[matTooltipPosition]="tooltipPosition" [matTooltipPosition]="tooltipPosition()"
[matTooltip]="showTooltip ? userName : ''" [matTooltip]="showTooltip() ? userName : ''"
> >
{{ _user | name: { showInitials: true } }} {{ _user() | name: { showInitials: true } }}
</div> </div>
@if (withName) { @if (withName()) {
<div [class.disabled]="disabled" class="clamp-1 username" id="avatarUsername"> <div [class.disabled]="disabled()" class="clamp-1 username" id="avatarUsername">
{{ userName }} {{ userName }}
</div> </div>
} }

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, inject, Input, OnChanges, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { IqserUser } from '../../iqser-user.model'; import { IqserUser } from '../../iqser-user.model';
import { IqserUserService } from '../../services/iqser-user.service'; import { IqserUserService } from '../../services/iqser-user.service';
@ -7,6 +7,7 @@ import { IIqserUser } from '../../types/user.response';
import { NgIf } from '@angular/common'; import { NgIf } from '@angular/common';
import { NamePipe } from '../../name.pipe'; import { NamePipe } from '../../name.pipe';
import { MatTooltip } from '@angular/material/tooltip'; import { MatTooltip } from '@angular/material/tooltip';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({ @Component({
selector: 'iqser-initials-avatar', selector: 'iqser-initials-avatar',
@ -16,80 +17,45 @@ import { MatTooltip } from '@angular/material/tooltip';
standalone: true, standalone: true,
imports: [NgIf, NamePipe, MatTooltip], imports: [NgIf, NamePipe, MatTooltip],
}) })
export class InitialsAvatarComponent<Interface extends IIqserUser = IIqserUser, Class extends IqserUser & Interface = IqserUser & Interface> export class InitialsAvatarComponent<
implements OnInit, OnChanges Interface extends IIqserUser = IIqserUser,
{ Class extends IqserUser & Interface = IqserUser & Interface,
> {
readonly #userService = inject<IqserUserService<Interface, Class>>(IqserUserService);
readonly #translateService = inject(TranslateService); readonly #translateService = inject(TranslateService);
@Input() color = 'lightgray'; readonly #isSystemUser = computed(() => this._user()?.id?.toLowerCase() === 'system');
@Input() size: 'small' | 'large' = 'small'; readonly #users = toSignal(this.#userService.all$);
@Input() withName = false;
@Input() showYou = false;
@Input() tooltipPosition: 'below' | 'above' = 'above';
@Input() defaultValue: string = this.#translateService.instant('initials-avatar.unassigned');
@Input() showTooltip = true;
colorClass?: string;
namePipeOptions?: NamePipeOptions;
constructor(private readonly _userService: IqserUserService<Interface, Class>) {} readonly color = input('lightgray');
readonly size = input<'small' | 'large'>('small');
readonly withName = input(false);
readonly showYou = input(false);
readonly tooltipPosition = input<'below' | 'above'>('above');
readonly defaultValue = input<string>(this.#translateService.instant('initials-avatar.unassigned'));
readonly showTooltip = input(true);
readonly user = input.required<Class | string>();
readonly showBorderCondition = input<<T extends Class = Class>(user: T) => boolean>(user => user.isSpecial);
_user?: Class; readonly _user = computed(() => {
const user = this.user();
@Input()
set user(user: Class | string) {
if (typeof user === 'string') { if (typeof user === 'string') {
this._user = this._userService.find(user); if (user?.toLowerCase() === 'system') return this.#userService.newSystemUser();
} else { if (!user) return undefined;
this._user = user; return this.#users()?.find(u => u.id === user) ?? this.#userService.newDeletedUser();
} }
} return user;
});
get hasBorder(): boolean { readonly isCurrentUser = computed(() => this.#userService.currentUser?.id === this._user()?.id);
return !!this._user && !this.isCurrentUser && this.showBorderCondition(this._user); readonly hasBorder = computed(() => !!this._user() && !this.isCurrentUser() && this.showBorderCondition()(this._user()!));
} readonly disabled = computed(() => !!this._user() && !this.#isSystemUser() && !this._user()?.hasAnyRole);
readonly colorClass = computed(() => {
get disabled(): boolean { if (this.isCurrentUser()) return 'primary-white';
return !!this._user && !this.#isSystemUser && !this._user.hasAnyRole; if (this.disabled()) return 'inactive';
} if (this.color().includes('-')) return this.color();
if (this.#isSystemUser()) return 'primary-white primary';
get isCurrentUser(): boolean { return `${this.color()}-dark`;
return this._userService.currentUser?.id === this._user?.id; });
} readonly namePipeOptions = computed<NamePipeOptions>(
() => ({ showYou: this.showYou(), defaultValue: this.defaultValue() }) as NamePipeOptions,
get #colorClass() { );
if (this.isCurrentUser) {
return 'primary-white';
}
if (this.disabled) {
return 'inactive';
}
if (this.color.includes('-')) {
return this.color;
}
return `${this.color}-dark`;
}
get #isSystemUser() {
return this._user?.id?.toLowerCase() === 'system';
}
@Input() showBorderCondition: <T extends Class = Class>(user: T) => boolean = user => user.isSpecial;
ngOnChanges(): void {
if (this.#isSystemUser) {
this.colorClass = 'primary-white primary';
return;
}
this.colorClass = this.#colorClass;
}
ngOnInit(): void {
this.namePipeOptions = {
showYou: this.showYou,
defaultValue: this.defaultValue,
};
}
} }

View File

@ -164,14 +164,22 @@ export abstract class IqserUserService<
find(id: string): Class | undefined { find(id: string): Class | undefined {
if (id?.toLowerCase() === 'system') { if (id?.toLowerCase() === 'system') {
return new this._entityClass({ username: 'System' }, [], 'system'); return this.newSystemUser();
} }
if (!id) { if (!id) {
return undefined; return undefined;
} }
return super.find(id) ?? new this._entityClass({ username: 'Deleted User' }, [], 'deleted'); return super.find(id) ?? this.newDeletedUser();
}
newSystemUser() {
return new this._entityClass({ username: 'System' }, [], 'system');
}
newDeletedUser() {
return new this._entityClass({ username: 'Deleted User' }, [], 'deleted');
} }
} }

View File

@ -5,4 +5,5 @@ export interface ICreateUserRequest {
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
roles?: List; roles?: List;
sendSetPasswordMail?: boolean;
} }