import { Component, EventEmitter, Input, Output, ViewChild, ViewChildren, QueryList, ElementRef, AfterViewInit, OnDestroy, HostListener, Renderer2, TemplateRef, OnInit, } from '@angular/core'; import { CommonModule } from '@angular/common'; // components import { CaDropdownMenuComponent } from '../ca-dropdown-menu/ca-dropdown-menu.component'; import { CaShowMoreComponent } from '../ca-show-more/ca-show-more.component'; import { CaAppTooltipV2Component } from '../ca-app-tooltip-v2/ca-app-tooltip-v2.component'; import { CaCheckboxComponent } from '../ca-checkbox/ca-checkbox.component'; // models import { CardDetails } from '../../models/card-models/card-table-data.model'; // interfaces import { IDropdownMenuItem, IDropdownMenuOptionEmit, } from '../ca-dropdown-menu/interfaces'; import { TableCardBodyActions } from '../../interfaces'; import { ISearchQueryItem } from '../ca-search-multiple-states-2/interfaces'; // modules import { AngularSvgIconModule } from 'angular-svg-icon'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; // svg-routes import { TableCardViewSvgRoutes } from './utils/svg-routes'; // helpers import { TableCardViewHelper, TableCardSkeletonHelper } from './utils/helpers'; // pipes import { IsCardFlippedPipe } from './pipes'; import { TableHighlightSearchTextPipe } from '../../pipes'; // enums import { eTableCardTypes, eTableCardGeneral } from './enums'; import { ePosition, eColor, eStringPlaceholder } from '../../enums'; // animations import { fadeInAnimation } from '../../animations'; // additional components import { CaTableCardViewSkeletonComponent } from './components/additional-components/ca-table-card-view-skeleton/ca-table-card-view-skeleton.component'; @Component({ selector: 'ca-table-card-view', templateUrl: './ca-table-card-view.component.html', styleUrl: './ca-table-card-view.component.scss', animations: [fadeInAnimation], imports: [ // modules AngularSvgIconModule, CommonModule, NgbTooltip, // components CaDropdownMenuComponent, CaShowMoreComponent, CaAppTooltipV2Component, CaCheckboxComponent, CaTableCardViewSkeletonComponent, // pipes IsCardFlippedPipe, TableHighlightSearchTextPipe, ], }) export class CaTableCardViewComponent implements OnInit, AfterViewInit, OnDestroy { @HostListener('window:resize') onResize() { this.adjustShowMoreWidth(); requestAnimationFrame(() => { TableCardViewHelper.calculateAndSetFlipVariables( this.cardsContainer, this.renderer, true ); }); } @ViewChildren('cardBodyContent') cardBodyContents!: QueryList; @ViewChild('showMoreContainer') showMoreContainer!: ElementRef; @ViewChild('cardsContainer') cardsContainer!: ElementRef; @ViewChild('skeletonLoader') skeletonLoader!: CaTableCardViewSkeletonComponent; @Input() set frontSide(value: TemplateRef<{ data: T }>) { this._frontSide = value; } @Input() set backSide(value: TemplateRef<{ data: T }>) { this._backSide = value; } @Input() set titleIcon(value: TemplateRef<{ data: T }>) { this._titleIcon = value; } @Input() set rightSideTemplate(value: TemplateRef<{ data: T }>) { this._rightSideTemplate = value; } @Input() set viewData(value: T[]) { this._viewData = value; this.waitForCardsContainerAndCalculate(); } @Input() set cardTitle(value: string) { this._cardTitle = value; } @Input() set isLoading(value: boolean) { this._isLoading = value; } @Input() set searchStrings(value: ISearchQueryItem[]) { this._searchStrings = value; } @Input() totalDataCount!: number; @Input() cardsFlipType!: string; @Input() isDropdownMenuHidden: boolean = false; @Input() isSelectHidden: boolean = false; @Input() isBannerVisible: boolean = false; @Input() showBackSide: boolean = true; @Input() dropdownMenuOptions: IDropdownMenuItem[] = []; @Input() hasDetailsPage: boolean = true; @Input() isCardSortingActive: boolean = false; // show more @Output() showMore: EventEmitter = new EventEmitter(); // select @Output() selectCard: EventEmitter = new EventEmitter(); // title click @Output() cardTitleClick: EventEmitter = new EventEmitter(); @Output() finishOrder: EventEmitter = new EventEmitter(); @Output() dropdownOptionEmitter: EventEmitter< TableCardBodyActions > = new EventEmitter>(); public _searchStrings: ISearchQueryItem[] = []; public _cardTitle!: string; public _isLoading!: boolean; public _viewData!: T[]; public _frontSide!: TemplateRef<{ data: T }>; public _backSide!: TemplateRef<{ data: T }>; public _titleIcon!: TemplateRef<{ data: T }>; public _rightSideTemplate!: TemplateRef<{ data: T }>; // flip cards public isCardFlippedCheckInCards: number[] = []; // MutationObserver private deferObserver?: MutationObserver; // svg-routes public tableCardViewSvgRoutes = TableCardViewSvgRoutes; // enums public tableCardTypes = eTableCardTypes; public ePosition = ePosition; public eColor = eColor; public eTableCardGeneral = eTableCardGeneral; public eStringPlaceholder = eStringPlaceholder; private copyHandler!: (e: ClipboardEvent) => void; constructor(private renderer: Renderer2) {} ngOnInit() { this.isCardFlippedCheckInCards = [ ...TableCardViewHelper.isCardFlippedCheckInCards, ]; this.copyHandler = this.handleCopyEvent.bind(this); document.addEventListener('copy', this.copyHandler); } ngAfterViewInit() { this.waitForCardsContainerAndCalculate(); } private handleCopyEvent(clipboardEvent: ClipboardEvent): void { const plainText = window.getSelection()?.toString() || ''; clipboardEvent.clipboardData?.setData('text/plain', plainText); clipboardEvent.preventDefault(); } private adjustShowMoreWidth(): void { if (this.cardsContainer && this.showMoreContainer) { const firstCard = this.cardsContainer.nativeElement.querySelector('.flip-card'); if (firstCard) { const cardWidth = firstCard.offsetWidth; this.renderer.setStyle( this.showMoreContainer.nativeElement, 'width', cardWidth + 'px' ); } } } private waitForCardsContainerAndCalculate(): void { if (this.cardsContainer?.nativeElement) { this.disconnectDeferObserver(); this.calculateAndSetup(); return; } this.disconnectDeferObserver(); this.deferObserver = new MutationObserver(() => { if (this.cardsContainer?.nativeElement) { this.adjustShowMoreWidth(); this.calculateAndSetup(); this.disconnectDeferObserver(); } }); this.deferObserver.observe(document.body, { childList: true, subtree: true, }); } private disconnectDeferObserver(): void { if (this.deferObserver) { this.deferObserver.disconnect(); this.deferObserver = undefined; } } private calculateAndSetup(): void { requestAnimationFrame(() => { TableCardViewHelper.calculateAndSetFlipVariables( this.cardsContainer, this.renderer ); }); } public handleToggleDropdownMenuActions( event: IDropdownMenuOptionEmit, card: CardDetails ) { const { type } = event; const { id } = card; const emitAction = { type, id, data: card, }; this.dropdownOptionEmitter.emit(emitAction); } public flipCard(index: number): void { this.isCardFlippedCheckInCards = TableCardViewHelper.flipCard(index); } public onCheckboxSelect(id: number): void { this.selectCard.emit(id); } public cardTitleClicked(id: number): void { this.cardTitleClick.emit(id); } public onShowMoreClick(): void { this.showMore.emit(); requestAnimationFrame(() => { if (this.skeletonLoader?.placeholders) { TableCardSkeletonHelper.adjustPlaceholderHeights( this.renderer, this.skeletonLoader.placeholders, this.cardsContainer ); const firstPlaceholder = this.skeletonLoader.placeholders.first; if (firstPlaceholder) { TableCardSkeletonHelper.scrollToBottom( firstPlaceholder, this.isBannerVisible, this.cardsContainer ); } } }); } public repairFinishOrder(id: number): void { this.finishOrder.emit(id); } ngOnDestroy() { this.disconnectDeferObserver(); document.removeEventListener('copy', this.copyHandler); } }