Spaces:
Running
Running
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'; | |
({ | |
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); | |
} | |
} |