import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ButtonComponent, IconComponent, SkeletonComponent, TooltipDirective } from '@sda/base-ui'; import type { TlTimelineItem } from '../../types/timeline.types'; import type { TlMarkerSize, TlConnectorStyle, TlSortDirection } from '../../types/config.types'; import type { TlTimelineItemClickEvent } from '../../types/event.types'; import { formatSmartTime } from '../../utils/date.utils'; @Component({ selector: 'tl-horizontal-timeline', standalone: true, imports: [CommonModule, ButtonComponent, IconComponent, SkeletonComponent, TooltipDirective], templateUrl: './tl-horizontal-timeline.component.html', styleUrl: './tl-horizontal-timeline.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class TlHorizontalTimelineComponent implements AfterViewInit { @ViewChild('scrollContainer') scrollContainer?: ElementRef; @Input({ required: true }) items!: TlTimelineItem[]; @Input() markerSize: TlMarkerSize = 'md'; @Input() connectorStyle: TlConnectorStyle = 'solid'; @Input() sortDirection: TlSortDirection = 'desc'; @Input() scrollable = true; @Input() showNavArrows = true; @Input() loading = false; @Input() emptyMessage = 'No timeline items'; @Output() itemClick = new EventEmitter(); protected canScrollLeft = false; protected canScrollRight = false; protected get sortedItems(): TlTimelineItem[] { const items = [...this.items]; const direction = this.sortDirection; items.sort((a, b) => { const aTime = new Date(a.date).getTime(); const bTime = new Date(b.date).getTime(); return direction === 'asc' ? aTime - bTime : bTime - aTime; }); return items; } ngAfterViewInit(): void { this.updateScrollState(); } protected formatTime(item: TlTimelineItem): string { return formatSmartTime(item.date); } protected getMarkerColor(item: TlTimelineItem): string { if (item.iconColor) return item.iconColor; if (item.status) return `var(--tl-status-${item.status})`; return 'var(--tl-marker-bg)'; } protected onItemClick(item: TlTimelineItem, index: number): void { this.itemClick.emit({ item, index }); } protected scrollLeft(): void { const container = this.scrollContainer?.nativeElement; if (container) { container.scrollBy({ left: -200, behavior: 'smooth' }); setTimeout(() => this.updateScrollState(), 300); } } protected scrollRight(): void { const container = this.scrollContainer?.nativeElement; if (container) { container.scrollBy({ left: 200, behavior: 'smooth' }); setTimeout(() => this.updateScrollState(), 300); } } protected updateScrollState(): void { const container = this.scrollContainer?.nativeElement; if (container) { this.canScrollLeft = container.scrollLeft > 0; this.canScrollRight = container.scrollLeft < container.scrollWidth - container.clientWidth - 1; } } protected getLoadingItems(): number[] { return Array.from({ length: 5 }, (_, i) => i); } }