import { Component, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ApiService, Project } from '../../services/api.service';
@Component({
selector: 'app-projects',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
@if (loading) {
Loading projects...
} @else if (filteredProjects.length === 0) {
No projects found.
} @else {
@if (viewMode === 'card') {
@for (project of filteredProjects; track project.id) {
đŠī¸
{{ project.name }}
{{ project.caption || 'No description' }}
Versions: {{ project.versions.length || 0 }} ({{ getPublishedCount(project) }} published)
Status: {{ project.enabled ? 'â Enabled' : 'â Disabled' }}
Last update: {{ getRelativeTime(project.last_update_date) }}
}
} @else {
Name |
Caption |
Versions |
Enabled |
Deleted |
Last Update |
Actions |
@for (project of filteredProjects; track project.id) {
{{ project.name }} |
{{ project.caption || '-' }} |
{{ project.versions.length || 0 }} ({{ getPublishedCount(project) }} published) |
@if (project.enabled) {
â
} @else {
â
}
|
@if (project.deleted) {
â
} @else {
â
}
|
{{ getRelativeTime(project.last_update_date) }} |
@if (!project.deleted) {
}
|
}
}
}
@if (message) {
{{ message }}
}
`,
styles: [`
.projects-container {
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
h2 {
margin: 0;
}
.toolbar-actions {
display: flex;
gap: 0.5rem;
align-items: center;
}
}
.search-input {
padding: 0.375rem 0.75rem;
border: 1px solid #ced4da;
border-radius: 0.25rem;
width: 200px;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.25rem;
cursor: pointer;
}
.view-toggle {
display: flex;
border: 1px solid #ced4da;
border-radius: 0.25rem;
overflow: hidden;
button {
background: white;
border: none;
padding: 0.375rem 0.75rem;
cursor: pointer;
&.active {
background-color: #007bff;
color: white;
}
}
}
.loading, .empty-state {
text-align: center;
padding: 3rem;
background-color: white;
border-radius: 0.25rem;
p {
margin-bottom: 1rem;
color: #6c757d;
}
}
.project-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.project-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
&:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
&.disabled {
opacity: 0.7;
}
&.deleted {
background-color: #f8f9fa;
}
.project-icon {
font-size: 2rem;
margin-bottom: 0.5rem;
}
h3 {
margin: 0 0 0.5rem 0;
font-size: 1.25rem;
}
p {
color: #6c757d;
margin-bottom: 1rem;
}
.project-meta {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 1rem;
span {
display: block;
margin-bottom: 0.25rem;
}
}
.project-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
button {
flex: 1;
min-width: 80px;
font-size: 0.875rem;
padding: 0.375rem 0.5rem;
}
}
}
.status-badge {
&.enabled { color: #28a745; }
&.deleted { color: #dc3545; }
}
.actions {
display: flex;
gap: 0.25rem;
}
.action-btn {
background: none;
border: none;
cursor: pointer;
font-size: 1.1rem;
padding: 0.25rem;
border-radius: 0.25rem;
&:hover {
background-color: #f8f9fa;
}
&.danger:hover {
background-color: #f8d7da;
}
}
tr.deleted {
opacity: 0.6;
background-color: #f8f9fa;
}
}
`]
})
export class ProjectsComponent implements OnInit {
private apiService = inject(ApiService);
projects: Project[] = [];
filteredProjects: Project[] = [];
loading = true;
showDeleted = false;
searchTerm = '';
viewMode: 'card' | 'list' = 'card';
message = '';
isError = false;
ngOnInit() {
this.loadProjects();
}
loadProjects() {
this.loading = true;
this.apiService.getProjects(this.showDeleted).subscribe({
next: (projects) => {
this.projects = projects;
this.filterProjects();
this.loading = false;
},
error: (err) => {
this.showMessage('Failed to load projects', true);
this.loading = false;
}
});
}
filterProjects() {
const term = this.searchTerm.toLowerCase();
this.filteredProjects = this.projects.filter(project =>
project.name.toLowerCase().includes(term) ||
(project.caption || '').toLowerCase().includes(term)
);
}
getPublishedCount(project: Project): number {
return project.versions.filter(v => v.published).length || 0;
}
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`;
return `${days} day${days > 1 ? 's' : ''} ago`;
}
createProject() {
// TODO: Open create dialog
console.log('Create project - not implemented yet');
}
editProject(project: Project) {
// TODO: Open edit dialog
console.log('Edit project:', project.name);
}
manageVersions(project: Project) {
// TODO: Open versions dialog
console.log('Manage versions:', project.name);
}
exportProject(project: Project) {
this.apiService.exportProject(project.id).subscribe({
next: (data) => {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${project.name}_export.json`;
a.click();
window.URL.revokeObjectURL(url);
this.showMessage(`Project "${project.name}" exported successfully`, false);
},
error: (err) => {
this.showMessage('Failed to export project', true);
}
});
}
toggleProject(project: Project) {
this.apiService.toggleProject(project.id).subscribe({
next: (result) => {
project.enabled = result.enabled;
this.showMessage(`Project "${project.name}" ${result.enabled ? 'enabled' : 'disabled'}`, false);
},
error: (err) => {
this.showMessage('Failed to toggle project', true);
}
});
}
deleteProject(project: Project) {
if (confirm(`Are you sure you want to delete "${project.name}"?`)) {
this.apiService.deleteProject(project.id).subscribe({
next: () => {
this.showMessage(`Project "${project.name}" deleted successfully`, false);
this.loadProjects();
},
error: (err) => {
this.showMessage(err.error?.detail || 'Failed to delete project', true);
}
});
}
}
private showMessage(message: string, isError: boolean) {
this.message = message;
this.isError = isError;
setTimeout(() => {
this.message = '';
}, 5000);
}
}