flare / flare-ui /src /app /components /projects /projects.component.ts
ciyidogan's picture
Upload 22 files
ac91331 verified
raw
history blame
12.6 kB
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: `
<div class="projects-container">
<div class="toolbar">
<h2>Projects</h2>
<div class="toolbar-actions">
<button class="btn btn-primary" (click)="createProject()">
New Project
</button>
<button class="btn btn-secondary" disabled>
Import Project
</button>
<input
type="text"
placeholder="Search projects..."
[(ngModel)]="searchTerm"
(input)="filterProjects()"
class="search-input"
>
<label class="checkbox-label">
<input
type="checkbox"
[(ngModel)]="showDeleted"
(change)="loadProjects()"
>
Display Deleted
</label>
<div class="view-toggle">
<button [class.active]="viewMode === 'card'" (click)="viewMode = 'card'">
Card
</button>
<button [class.active]="viewMode === 'list'" (click)="viewMode = 'list'">
List
</button>
</div>
</div>
</div>
@if (loading) {
<div class="loading">
<span class="spinner"></span> Loading projects...
</div>
} @else if (filteredProjects.length === 0) {
<div class="empty-state">
<p>No projects found.</p>
<button class="btn btn-primary" (click)="createProject()">
Create your first project
</button>
</div>
} @else {
@if (viewMode === 'card') {
<div class="project-cards">
@for (project of filteredProjects; track project.id) {
<div class="project-card" [class.disabled]="!project.enabled" [class.deleted]="project.deleted">
<div class="project-icon">πŸ›©οΈ</div>
<h3>{{ project.name }}</h3>
<p>{{ project.caption || 'No description' }}</p>
<div class="project-meta">
<span>Versions: {{ project.versions.length || 0 }} ({{ getPublishedCount(project) }} published)</span>
<span>Status: {{ project.enabled ? 'βœ“ Enabled' : 'βœ— Disabled' }}</span>
<span>Last update: {{ getRelativeTime(project.last_update_date) }}</span>
</div>
<div class="project-actions">
<button class="btn btn-secondary" (click)="editProject(project)">Edit</button>
<button class="btn btn-secondary" (click)="manageVersions(project)">Versions</button>
<button class="btn btn-secondary" (click)="exportProject(project)">Export</button>
<button class="btn btn-secondary" (click)="toggleProject(project)">
{{ project.enabled ? 'Disable' : 'Enable' }}
</button>
</div>
</div>
}
</div>
} @else {
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Caption</th>
<th>Versions</th>
<th>Enabled</th>
<th>Deleted</th>
<th>Last Update</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@for (project of filteredProjects; track project.id) {
<tr [class.deleted]="project.deleted">
<td>{{ project.name }}</td>
<td>{{ project.caption || '-' }}</td>
<td>{{ project.versions.length || 0 }} ({{ getPublishedCount(project) }} published)</td>
<td>
@if (project.enabled) {
<span class="status-badge enabled">βœ“</span>
} @else {
<span class="status-badge">βœ—</span>
}
</td>
<td>
@if (project.deleted) {
<span class="status-badge deleted">βœ“</span>
} @else {
<span class="status-badge">βœ—</span>
}
</td>
<td>{{ getRelativeTime(project.last_update_date) }}</td>
<td class="actions">
<button class="action-btn" title="Edit" (click)="editProject(project)">πŸ–ŠοΈ</button>
<button class="action-btn" title="Versions" (click)="manageVersions(project)">πŸ“‹</button>
<button class="action-btn" title="Export" (click)="exportProject(project)">πŸ“€</button>
@if (!project.deleted) {
<button class="action-btn danger" title="Delete" (click)="deleteProject(project)">πŸ—‘οΈ</button>
}
</td>
</tr>
}
</tbody>
</table>
}
}
@if (message) {
<div class="alert" [class.alert-success]="!isError" [class.alert-danger]="isError">
{{ message }}
</div>
}
</div>
`,
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);
}
}