import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTableModule } from '@angular/material/table'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatCardModule } from '@angular/material/card'; import { MatChipsModule } from '@angular/material/chips'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatDividerModule } from '@angular/material/divider'; import { ApiService, Project } from '../../services/api.service'; import ProjectEditDialogComponent from '../../dialogs/project-edit-dialog/project-edit-dialog.component'; import VersionEditDialogComponent from '../../dialogs/version-edit-dialog/version-edit-dialog.component'; import ConfirmDialogComponent from '../../dialogs/confirm-dialog/confirm-dialog.component'; @Component({ selector: 'app-projects', standalone: true, imports: [ CommonModule, FormsModule, MatTableModule, MatProgressBarModule, MatButtonModule, MatCheckboxModule, MatFormFieldModule, MatInputModule, MatButtonToggleModule, MatCardModule, MatChipsModule, MatIconModule, MatMenuModule, MatDividerModule, MatDialogModule, MatSnackBarModule ], templateUrl: './projects.component.html', styleUrls: ['./projects.component.scss'] }) export class ProjectsComponent implements OnInit { projects: Project[] = []; filteredProjects: Project[] = []; searchTerm = ''; showDeleted = false; viewMode: 'list' | 'card' = 'card'; loading = false; message = ''; isError = false; // For table view displayedColumns: string[] = ['name', 'caption', 'versions', 'status', 'lastUpdate', 'actions']; constructor( private apiService: ApiService, private dialog: MatDialog, private snackBar: MatSnackBar ) {} ngOnInit() { this.loadProjects(); } async loadProjects() { this.loading = true; try { this.projects = await this.apiService.getProjects(this.showDeleted).toPromise() || []; this.applyFilter(); } catch (error) { this.showMessage('Failed to load projects', true); } finally { this.loading = false; } } applyFilter() { this.filteredProjects = this.projects.filter(project => { const matchesSearch = !this.searchTerm || project.name.toLowerCase().includes(this.searchTerm.toLowerCase()) || (project.caption || '').toLowerCase().includes(this.searchTerm.toLowerCase()); const matchesDeleted = this.showDeleted || !project.deleted; return matchesSearch && matchesDeleted; }); } filterProjects() { this.applyFilter(); } onSearchChange() { this.applyFilter(); } onShowDeletedChange() { this.loadProjects(); } createProject() { const dialogRef = this.dialog.open(ProjectEditDialogComponent, { width: '500px', data: { mode: 'create' } }); dialogRef.afterClosed().subscribe(result => { if (result) { this.loadProjects(); } }); } editProject(project: Project) { const dialogRef = this.dialog.open(ProjectEditDialogComponent, { width: '500px', data: { mode: 'edit', project: { ...project } } }); dialogRef.afterClosed().subscribe(result => { if (result) { this.loadProjects(); } }); } async toggleProject(project: Project) { try { const result = await this.apiService.toggleProject(project.id).toPromise(); project.enabled = result.enabled; this.showMessage( `Project "${project.name}" ${project.enabled ? 'enabled' : 'disabled'} successfully`, false ); } catch (error) { this.showMessage('Failed to toggle project', true); } } manageVersions(project: Project) { const dialogRef = this.dialog.open(VersionEditDialogComponent, { width: '90vw', maxWidth: '1200px', height: '90vh', data: { project } }); dialogRef.afterClosed().subscribe(result => { if (result) { this.loadProjects(); } }); } async deleteProject(project: Project) { const hasVersions = project.versions && project.versions.length > 0; const message = hasVersions ? `Project "${project.name}" has ${project.versions.length} version(s). Are you sure you want to delete it?` : `Are you sure you want to delete project "${project.name}"?`; const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '400px', data: { title: 'Delete Project', message: message, confirmText: 'Delete', confirmColor: 'warn' } }); dialogRef.afterClosed().subscribe(async confirmed => { if (confirmed) { try { await this.apiService.deleteProject(project.id).toPromise(); this.showMessage('Project deleted successfully', false); this.loadProjects(); } catch (error: any) { const message = error.error?.detail || 'Failed to delete project'; this.showMessage(message, true); } } }); } async exportProject(project: Project) { try { const data = await this.apiService.exportProject(project.id).toPromise(); // Create and download file const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${project.name}_export_${new Date().getTime()}.json`; link.click(); window.URL.revokeObjectURL(url); this.showMessage('Project exported successfully', false); } catch (error) { this.showMessage('Failed to export project', true); } } importProject() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async (event: any) => { const file = event.target.files[0]; if (!file) return; try { const text = await file.text(); const data = JSON.parse(text); await this.apiService.importProject(data).toPromise(); this.showMessage('Project imported successfully', false); this.loadProjects(); } catch (error: any) { const message = error.error?.detail || 'Failed to import project'; this.showMessage(message, true); } }; input.click(); } getPublishedCount(project: Project): number { return project.versions?.filter(v => v.published).length || 0; } getRelativeTime(timestamp: string | undefined): string { if (!timestamp) return 'Never'; const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 60) return `${diffMins} minutes ago`; if (diffHours < 24) return `${diffHours} hours ago`; if (diffDays < 7) return `${diffDays} days ago`; return date.toLocaleDateString(); } trackByProjectId(index: number, project: Project): number { return project.id; } private showMessage(message: string, isError: boolean) { this.message = message; this.isError = isError; setTimeout(() => { this.message = ''; }, 5000); } }