import { Component, ElementRef, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, ViewChild, } from '@angular/core'; import { NgClass } from '@angular/common'; // Modules import { AngularSvgIconModule } from 'angular-svg-icon'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { FormsModule } from '@angular/forms'; // SvgRoutes import { SearchMultipleStatesRoutes } from './utils/svg-routes'; // Components import { CaAppTooltipV2Component } from '../ca-app-tooltip-v2/ca-app-tooltip-v2.component'; // Animation import { toggleAnimation } from '../../animations/expand-search-button.animation'; //Service import { CaSearchMultipleStatesService } from './utils/services'; //Pipes import { ChipClassPipe } from './utils/pipes/'; //Enums import { ChipsColorsEnum, KeyboardEventCommands, SearchCountEnum, } from './utils/enums'; //Model import { ChipsModel, ITabData } from './models'; import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-ca-search-multiple-states', animations: [toggleAnimation], imports: [ // Angular NgClass, AngularSvgIconModule, // Modules FormsModule, NgbTooltipModule, // Components CaAppTooltipV2Component, // Pipes ChipClassPipe, ], templateUrl: './ca-search-multiple-states.component.html', styleUrls: ['./ca-search-multiple-states.component.scss'], }) export class CaSearchMultipleStatesComponent implements OnInit, OnDestroy { @HostListener('document:click', ['$event']) public onDocumentClick(event: MouseEvent): void { if ( !this.elementRef.nativeElement.contains(event.target) && this.openSearch && !this.isAlwaysOpenSearch ) { setTimeout(() => { this.toggleSearch(); }, 0); } } @ViewChild('tableSearchInput', { static: false }) tableSearchInput!: ElementRef; @Input() toolbarSearch?: boolean = true; @Input() selectedTabData!: ITabData; @Input() searchType!: string; @Input() isAlwaysOpenSearch: boolean = false; @Input() set isDetailsPageSearch(value: boolean) { this._isDetailsPageSearch = value; this.openSearch = value; } @Output() close = new EventEmitter(); @Output() onSearch: EventEmitter = new EventEmitter(); public _isDetailsPageSearch!: boolean; public chips: ChipsModel[] = []; public openSearch!: boolean; public searchText: string = ''; public searchIsActive: boolean = false; public isInputFocused: boolean = false; public chipToDelete: number = -1; private chipsForHighlightSearch!: string[]; private typingTimeout!: ReturnType; private destroy$ = new Subject(); constructor( private searchMultipleStatesService: CaSearchMultipleStatesService, private elementRef: ElementRef ) {} ngOnInit(): void { this.initDeleteAllChipsListener(); } public initDeleteAllChipsListener(): void { this.searchMultipleStatesService.deleteAllChips$ .pipe(takeUntil(this.destroy$)) .subscribe((value) => { this.deleteAllChips(); }); } public toggleSearch() { if (this.chips.length === 3) this.openSearch = false; else { this.openSearch = !this.openSearch; if (!this.openSearch) this.close.emit(); if (this.openSearch && this.chips.length < 3) setTimeout(() => { this.tableSearchInput.nativeElement.focus(); }, 100); } } public onTyping(event: MouseEvent | KeyboardEvent): void { clearTimeout(this.typingTimeout); const searchNumber = !this.chips.length ? SearchCountEnum.SEARCH_ONE : this.chips.length === 1 ? SearchCountEnum.SEARCH_TWO : SearchCountEnum.SEARCH_THREE; const keyboardEvent = this.isKeyboardEvent(event) ? (event as KeyboardEvent) : null; if (!keyboardEvent || !keyboardEvent.key) { this.searchIsActive = true; this.searchText = SearchCountEnum.EMPTY_PLACE_HOLDER; this.sendHighlightSearchOnTyping(); this.emitSearchEvent(); this.searchMultipleStatesService.sendCurrentSearchTableData({ chip: searchNumber, search: this.searchText, searchType: this.searchType, }); } if ( keyboardEvent && keyboardEvent.key !== KeyboardEventCommands.ENTER ) { this.typingTimeout = setTimeout(() => { if (this.searchText.length >= 3) { this.searchIsActive = true; this.sendHighlightSearchOnTyping(); this.searchMultipleStatesService.sendCurrentSearchTableData( { chip: searchNumber, search: this.searchText, searchType: this.searchType, } ); } else if (this.searchIsActive && this.searchText.length < 3) { this.searchIsActive = false; this.sendHighlightSearchOnEnter(); this.searchMultipleStatesService.sendCurrentSearchTableData( { chip: searchNumber, doReset: true, all: searchNumber === SearchCountEnum.SEARCH_ONE, searchType: this.searchType, } ); } this.emitSearchEvent(); }, 500); } } private isKeyboardEvent(event: Event): event is KeyboardEvent { return (event as KeyboardEvent).key !== undefined; } public onEnter(): void { if (!this.toolbarSearch) return; if (this.chips.length < 3 && !this.checkChips()) { this.chips.push({ searchText: this.searchText, color: this.getChipColor(this.chips.length), canDoAnimation: true, query: this.getChipQuery(this.chips.length), }); this.sendHighlightSearchOnEnter(); this.emitSearchEvent(); this.searchMultipleStatesService.sendCurrentSearchTableData({ chipAdded: true, search: this.searchText, query: this.getChipQuery(this.chips.length - 1), searchType: this.searchType, }); this.chips.length === 3 && !this._isDetailsPageSearch ? this.toggleSearch() : null; this.chipToDelete = -1; this.searchText = ''; this.searchIsActive = false; } } private sendHighlightSearchOnTyping(): void { this.chipsForHighlightSearch = []; this.chips.map((chip) => { this.chipsForHighlightSearch.push(chip.searchText); }); if (this.chips.length <= 2) this.chipsForHighlightSearch.push(this.searchText); this.searchMultipleStatesService.sendChipsForHighlightSearchToTable( this.chipsForHighlightSearch ); } private sendHighlightSearchOnEnter() { this.chipsForHighlightSearch = []; this.chips.map((chip) => { this.chipsForHighlightSearch.push(chip.searchText); }); this.searchMultipleStatesService.sendChipsForHighlightSearchToTable( this.chipsForHighlightSearch ); this.searchMultipleStatesService.emitSelectedChips( this.chipsForHighlightSearch ); } public handleClearClick(): void { if (this.searchText) this.searchText = ''; } private checkChips(): boolean { let hasSearchText = false; this.chips.map((chip) => { if (chip.searchText === this.searchText) hasSearchText = true; }); return hasSearchText; } public onDeleteChip(index: number) { this.chips.splice(index, 1); this.sendHighlightSearchOnEnter(); if (this.chips.length) { this.chips = this.chips.map((chip, i) => { chip = { searchText: chip.searchText, color: this.getChipColor(i), canDoAnimation: false, query: this.getChipQuery(i), }; return chip; }); } if (this.openSearch) setTimeout(() => { this.tableSearchInput.nativeElement.focus(); }, 100); this.searchMultipleStatesService.sendCurrentSearchTableData({ isChipDelete: true, search: this.searchText?.length >= 3 ? this.searchText : undefined, addToQuery: this.getChipQuery(this.chips.length), querys: [ SearchCountEnum.SEARCH_ONE, SearchCountEnum.SEARCH_THREE, SearchCountEnum.SEARCH_THREE, ], chips: this.chips, searchType: this.searchType, }); } public deleteAllChips(): void { this.chips = []; this.chipToDelete = -1; this.searchMultipleStatesService.sendChipsForHighlightSearchToTable([]); this.searchMultipleStatesService.emitSelectedChips([]); if (this.openSearch) setTimeout(() => { this.tableSearchInput.nativeElement.focus(); }, 100); this.searchMultipleStatesService.sendCurrentSearchTableData({ isChipDelete: true, search: this.searchText?.length >= 3 ? this.searchText : undefined, addToQuery: this.getChipQuery(this.chips.length), querys: [ SearchCountEnum.SEARCH_ONE, SearchCountEnum.SEARCH_THREE, SearchCountEnum.SEARCH_THREE, ], chips: this.chips, searchType: this.searchType, }); } private getChipColor(index: number): string { const chipsColors: string[] = Object.values(ChipsColorsEnum); return chipsColors[index]; } private getChipQuery(index: number): string { const chipsQuery = [ SearchCountEnum.SEARCH_ONE, SearchCountEnum.SEARCH_THREE, SearchCountEnum.SEARCH_THREE, ]; return chipsQuery[index]; } public getSvgPath( propertyName: keyof typeof SearchMultipleStatesRoutes ): string { return SearchMultipleStatesRoutes[propertyName] as string; } public capitalizeInput(text: string): void { this.searchText = text.charAt(0).toUpperCase() + text.slice(1); } public emitSearchEvent(): void { if (this._isDetailsPageSearch) this.onSearch.emit(this.searchText); } public onInputFocus(isFocused: boolean): void { this.isInputFocused = isFocused; } ngOnDestroy(): void { this.searchMultipleStatesService.sendCurrentSearchTableData(null); this.destroy$.next(); this.destroy$.complete(); } }