import { Component, EventEmitter, Output, inject, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatPaginatorModule, PageEvent } from '@angular/material/paginator'; interface ActivityLog { id: number; timestamp: string; user: string; action: string; entity_type: string; entity_id: any; entity_name: string; details?: string; } interface ActivityLogResponse { items: ActivityLog[]; total: number; page: number; limit: number; pages: number; } @Component({ selector: 'app-activity-log', standalone: true, imports: [ CommonModule, MatProgressSpinnerModule, MatButtonModule, MatIconModule, MatPaginatorModule ], template: `

🔔 Recent Activities

@if (loading && activities.length === 0) {
} @else if (activities.length === 0) {
No activities found
} @else {
@for (activity of activities; track activity.id) {
{{ getRelativeTime(activity.timestamp) }}
{{ activity.user }} {{ getActionText(activity) }} {{ activity.entity_name }} @if (activity.details) { • {{ activity.details }} }
}
}
`, styles: [` .activity-log-dropdown { position: absolute; top: 100%; right: 0; width: 400px; background: white; border: 1px solid #dee2e6; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 1000; margin-top: 0.5rem; display: flex; flex-direction: column; max-height: 600px; } .activity-header { padding: 1rem; border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; h3 { margin: 0; font-size: 1.1rem; } .close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: #6c757d; line-height: 1; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; &:hover { color: #333; } } } .activity-content { flex: 1; overflow-y: auto; min-height: 200px; max-height: 400px; } .activity-list { .activity-item { padding: 0.75rem 1rem; border-bottom: 1px solid #f0f0f0; transition: background-color 0.2s; &:hover { background-color: #f8f9fa; } &:last-child { border-bottom: none; } .activity-time { font-size: 0.85rem; color: #6c757d; margin-bottom: 0.25rem; } .activity-content { font-size: 0.9rem; em { color: #007bff; font-style: normal; font-weight: 500; } .details { color: #6c757d; font-size: 0.85rem; } } } } .activity-footer { padding: 0.5rem; border-top: 1px solid #dee2e6; display: flex; justify-content: center; align-items: center; button { width: 100%; } ::ng-deep .mat-paginator { background: transparent; width: 100%; .mat-paginator-container { padding: 0; justify-content: center; } .mat-paginator-range-label { margin: 0 8px; } } } .loading { padding: 3rem; display: flex; justify-content: center; align-items: center; } .empty { padding: 3rem; text-align: center; color: #6c757d; } `] }) export class ActivityLogComponent implements OnInit { @Output() close = new EventEmitter(); private http = inject(HttpClient); activities: ActivityLog[] = []; loading = false; currentPage = 1; pageSize = 10; totalItems = 0; totalPages = 0; ngOnInit() { this.loadActivities(); } loadActivities(page: number = 1) { this.loading = true; this.currentPage = page; this.http.get( `/api/activity-log?page=${page}&limit=${this.pageSize}` ).subscribe({ next: (response) => { this.activities = response.items; this.totalItems = response.total; this.totalPages = response.pages; this.loading = false; }, error: (error) => { console.error('Failed to load activities:', error); this.loading = false; } }); } onPageChange(event: PageEvent) { this.pageSize = event.pageSize; this.loadActivities(event.pageIndex + 1); } openFullView() { // TODO: Implement full activity log view console.log('Open full activity log view'); this.close.emit(); } getRelativeTime(timestamp: string): string { const date = new Date(timestamp); const now = new Date(); const diff = now.getTime() - date.getTime(); const minutes = Math.floor(diff / 60000); const hours = Math.floor(diff / 3600000); const days = Math.floor(diff / 86400000); if (minutes < 1) return 'just now'; if (minutes < 60) return `${minutes} min ago`; if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`; if (days < 7) return `${days} day${days > 1 ? 's' : ''} ago`; return date.toLocaleDateString(); } getActionText(activity: ActivityLog): string { const actions: Record = { 'CREATE_PROJECT': 'created project', 'UPDATE_PROJECT': 'updated project', 'DELETE_PROJECT': 'deleted project', 'ENABLE_PROJECT': 'enabled project', 'DISABLE_PROJECT': 'disabled project', 'PUBLISH_VERSION': 'published version of', 'CREATE_VERSION': 'created version for', 'UPDATE_VERSION': 'updated version of', 'DELETE_VERSION': 'deleted version from', 'CREATE_API': 'created API', 'UPDATE_API': 'updated API', 'DELETE_API': 'deleted API', 'UPDATE_ENVIRONMENT': 'updated environment', 'IMPORT_PROJECT': 'imported project', 'CHANGE_PASSWORD': 'changed password' }; return actions[activity.action] || activity.action.toLowerCase().replace(/_/g, ' '); } }