flare / flare-ui /src /app /components /activity-log /activity-log.component.ts
ciyidogan's picture
Update flare-ui/src/app/components/activity-log/activity-log.component.ts
66d5a73 verified
raw
history blame
7.83 kB
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: `
<div class="activity-log-dropdown" (click)="$event.stopPropagation()">
<div class="activity-header">
<h3>πŸ”” Recent Activities</h3>
<button class="close-btn" (click)="close.emit()">Γ—</button>
</div>
<div class="activity-content">
@if (loading && activities.length === 0) {
<div class="loading">
<mat-spinner diameter="30"></mat-spinner>
</div>
} @else if (activities.length === 0) {
<div class="empty">No activities found</div>
} @else {
<div class="activity-list">
@for (activity of activities; track activity.id) {
<div class="activity-item">
<div class="activity-time">{{ getRelativeTime(activity.timestamp) }}</div>
<div class="activity-content">
<strong>{{ activity.user }}</strong> {{ getActionText(activity) }}
<em>{{ activity.entity_name }}</em>
@if (activity.details) {
<span class="details">β€’ {{ activity.details }}</span>
}
</div>
</div>
}
</div>
}
</div>
<div class="activity-footer">
@if (totalItems > pageSize) {
<mat-paginator
[length]="totalItems"
[pageSize]="pageSize"
[pageIndex]="currentPage - 1"
[pageSizeOptions]="[10, 25, 50]"
(page)="onPageChange($event)"
showFirstLastButtons>
</mat-paginator>
} @else {
<button mat-button (click)="openFullView()">
<mat-icon>open_in_new</mat-icon>
View All Activities
</button>
}
</div>
</div>
`,
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<void>();
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<ActivityLogResponse>(
`/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<string, string> = {
'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, ' ');
}
}