Spaces:
Building
Building
Update flare-ui/src/app/components/projects/projects.component.ts
Browse files
flare-ui/src/app/components/projects/projects.component.ts
CHANGED
@@ -252,307 +252,3 @@ export class ProjectsComponent implements OnInit {
|
|
252 |
}, 5000);
|
253 |
}
|
254 |
}
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
import { Component, inject, OnInit } from '@angular/core';
|
260 |
-
import { CommonModule } from '@angular/common';
|
261 |
-
import { FormsModule } from '@angular/forms';
|
262 |
-
import { ApiService, Project } from '../../services/api.service';
|
263 |
-
|
264 |
-
@Component({
|
265 |
-
selector: 'app-projects',
|
266 |
-
standalone: true,
|
267 |
-
imports: [CommonModule, FormsModule],
|
268 |
-
template: `
|
269 |
-
|
270 |
-
`,
|
271 |
-
styles: [`
|
272 |
-
.projects-container {
|
273 |
-
.toolbar {
|
274 |
-
display: flex;
|
275 |
-
justify-content: space-between;
|
276 |
-
align-items: center;
|
277 |
-
margin-bottom: 1.5rem;
|
278 |
-
|
279 |
-
h2 {
|
280 |
-
margin: 0;
|
281 |
-
}
|
282 |
-
|
283 |
-
.toolbar-actions {
|
284 |
-
display: flex;
|
285 |
-
gap: 0.5rem;
|
286 |
-
align-items: center;
|
287 |
-
}
|
288 |
-
}
|
289 |
-
|
290 |
-
.search-input {
|
291 |
-
padding: 0.375rem 0.75rem;
|
292 |
-
border: 1px solid #ced4da;
|
293 |
-
border-radius: 0.25rem;
|
294 |
-
width: 200px;
|
295 |
-
}
|
296 |
-
|
297 |
-
.checkbox-label {
|
298 |
-
display: flex;
|
299 |
-
align-items: center;
|
300 |
-
gap: 0.25rem;
|
301 |
-
cursor: pointer;
|
302 |
-
}
|
303 |
-
|
304 |
-
.view-toggle {
|
305 |
-
display: flex;
|
306 |
-
border: 1px solid #ced4da;
|
307 |
-
border-radius: 0.25rem;
|
308 |
-
overflow: hidden;
|
309 |
-
|
310 |
-
button {
|
311 |
-
background: white;
|
312 |
-
border: none;
|
313 |
-
padding: 0.375rem 0.75rem;
|
314 |
-
cursor: pointer;
|
315 |
-
|
316 |
-
&.active {
|
317 |
-
background-color: #007bff;
|
318 |
-
color: white;
|
319 |
-
}
|
320 |
-
}
|
321 |
-
}
|
322 |
-
|
323 |
-
.loading, .empty-state {
|
324 |
-
text-align: center;
|
325 |
-
padding: 3rem;
|
326 |
-
background-color: white;
|
327 |
-
border-radius: 0.25rem;
|
328 |
-
|
329 |
-
p {
|
330 |
-
margin-bottom: 1rem;
|
331 |
-
color: #6c757d;
|
332 |
-
}
|
333 |
-
}
|
334 |
-
|
335 |
-
.project-cards {
|
336 |
-
display: grid;
|
337 |
-
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
338 |
-
gap: 1rem;
|
339 |
-
}
|
340 |
-
|
341 |
-
.project-card {
|
342 |
-
background: white;
|
343 |
-
border: 1px solid #dee2e6;
|
344 |
-
border-radius: 0.5rem;
|
345 |
-
padding: 1.5rem;
|
346 |
-
|
347 |
-
&:hover {
|
348 |
-
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
349 |
-
}
|
350 |
-
|
351 |
-
&.disabled {
|
352 |
-
opacity: 0.7;
|
353 |
-
}
|
354 |
-
|
355 |
-
&.deleted {
|
356 |
-
background-color: #f8f9fa;
|
357 |
-
}
|
358 |
-
|
359 |
-
.project-icon {
|
360 |
-
font-size: 2rem;
|
361 |
-
margin-bottom: 0.5rem;
|
362 |
-
}
|
363 |
-
|
364 |
-
h3 {
|
365 |
-
margin: 0 0 0.5rem 0;
|
366 |
-
font-size: 1.25rem;
|
367 |
-
}
|
368 |
-
|
369 |
-
p {
|
370 |
-
color: #6c757d;
|
371 |
-
margin-bottom: 1rem;
|
372 |
-
}
|
373 |
-
|
374 |
-
.project-meta {
|
375 |
-
font-size: 0.875rem;
|
376 |
-
color: #6c757d;
|
377 |
-
margin-bottom: 1rem;
|
378 |
-
|
379 |
-
span {
|
380 |
-
display: block;
|
381 |
-
margin-bottom: 0.25rem;
|
382 |
-
}
|
383 |
-
}
|
384 |
-
|
385 |
-
.project-actions {
|
386 |
-
display: flex;
|
387 |
-
gap: 0.5rem;
|
388 |
-
flex-wrap: wrap;
|
389 |
-
|
390 |
-
button {
|
391 |
-
flex: 1;
|
392 |
-
min-width: 80px;
|
393 |
-
font-size: 0.875rem;
|
394 |
-
padding: 0.375rem 0.5rem;
|
395 |
-
}
|
396 |
-
}
|
397 |
-
}
|
398 |
-
|
399 |
-
.status-badge {
|
400 |
-
&.enabled { color: #28a745; }
|
401 |
-
&.deleted { color: #dc3545; }
|
402 |
-
}
|
403 |
-
|
404 |
-
.actions {
|
405 |
-
display: flex;
|
406 |
-
gap: 0.25rem;
|
407 |
-
}
|
408 |
-
|
409 |
-
.action-btn {
|
410 |
-
background: none;
|
411 |
-
border: none;
|
412 |
-
cursor: pointer;
|
413 |
-
font-size: 1.1rem;
|
414 |
-
padding: 0.25rem;
|
415 |
-
border-radius: 0.25rem;
|
416 |
-
|
417 |
-
&:hover {
|
418 |
-
background-color: #f8f9fa;
|
419 |
-
}
|
420 |
-
|
421 |
-
&.danger:hover {
|
422 |
-
background-color: #f8d7da;
|
423 |
-
}
|
424 |
-
}
|
425 |
-
|
426 |
-
tr.deleted {
|
427 |
-
opacity: 0.6;
|
428 |
-
background-color: #f8f9fa;
|
429 |
-
}
|
430 |
-
}
|
431 |
-
`]
|
432 |
-
})
|
433 |
-
export class ProjectsComponent implements OnInit {
|
434 |
-
private apiService = inject(ApiService);
|
435 |
-
|
436 |
-
projects: Project[] = [];
|
437 |
-
filteredProjects: Project[] = [];
|
438 |
-
loading = true;
|
439 |
-
showDeleted = false;
|
440 |
-
searchTerm = '';
|
441 |
-
viewMode: 'card' | 'list' = 'card';
|
442 |
-
message = '';
|
443 |
-
isError = false;
|
444 |
-
|
445 |
-
ngOnInit() {
|
446 |
-
this.loadProjects();
|
447 |
-
}
|
448 |
-
|
449 |
-
loadProjects() {
|
450 |
-
this.loading = true;
|
451 |
-
this.apiService.getProjects(this.showDeleted).subscribe({
|
452 |
-
next: (projects) => {
|
453 |
-
this.projects = projects;
|
454 |
-
this.filterProjects();
|
455 |
-
this.loading = false;
|
456 |
-
},
|
457 |
-
error: (err) => {
|
458 |
-
this.showMessage('Failed to load projects', true);
|
459 |
-
this.loading = false;
|
460 |
-
}
|
461 |
-
});
|
462 |
-
}
|
463 |
-
|
464 |
-
filterProjects() {
|
465 |
-
const term = this.searchTerm.toLowerCase();
|
466 |
-
this.filteredProjects = this.projects.filter(project =>
|
467 |
-
project.name.toLowerCase().includes(term) ||
|
468 |
-
(project.caption || '').toLowerCase().includes(term)
|
469 |
-
);
|
470 |
-
}
|
471 |
-
|
472 |
-
getPublishedCount(project: Project): number {
|
473 |
-
return project.versions.filter(v => v.published).length || 0;
|
474 |
-
}
|
475 |
-
|
476 |
-
getRelativeTime(timestamp: string): string {
|
477 |
-
const date = new Date(timestamp);
|
478 |
-
const now = new Date();
|
479 |
-
const diff = now.getTime() - date.getTime();
|
480 |
-
|
481 |
-
const minutes = Math.floor(diff / 60000);
|
482 |
-
const hours = Math.floor(diff / 3600000);
|
483 |
-
const days = Math.floor(diff / 86400000);
|
484 |
-
|
485 |
-
if (minutes < 1) return 'just now';
|
486 |
-
if (minutes < 60) return `${minutes} min ago`;
|
487 |
-
if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
488 |
-
return `${days} day${days > 1 ? 's' : ''} ago`;
|
489 |
-
}
|
490 |
-
|
491 |
-
createProject() {
|
492 |
-
// TODO: Open create dialog
|
493 |
-
console.log('Create project - not implemented yet');
|
494 |
-
}
|
495 |
-
|
496 |
-
editProject(project: Project) {
|
497 |
-
// TODO: Open edit dialog
|
498 |
-
console.log('Edit project:', project.name);
|
499 |
-
}
|
500 |
-
|
501 |
-
manageVersions(project: Project) {
|
502 |
-
// TODO: Open versions dialog
|
503 |
-
console.log('Manage versions:', project.name);
|
504 |
-
}
|
505 |
-
|
506 |
-
exportProject(project: Project) {
|
507 |
-
this.apiService.exportProject(project.id).subscribe({
|
508 |
-
next: (data) => {
|
509 |
-
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
510 |
-
const url = window.URL.createObjectURL(blob);
|
511 |
-
const a = document.createElement('a');
|
512 |
-
a.href = url;
|
513 |
-
a.download = `${project.name}_export.json`;
|
514 |
-
a.click();
|
515 |
-
window.URL.revokeObjectURL(url);
|
516 |
-
this.showMessage(`Project "${project.name}" exported successfully`, false);
|
517 |
-
},
|
518 |
-
error: (err) => {
|
519 |
-
this.showMessage('Failed to export project', true);
|
520 |
-
}
|
521 |
-
});
|
522 |
-
}
|
523 |
-
|
524 |
-
toggleProject(project: Project) {
|
525 |
-
this.apiService.toggleProject(project.id).subscribe({
|
526 |
-
next: (result) => {
|
527 |
-
project.enabled = result.enabled;
|
528 |
-
this.showMessage(`Project "${project.name}" ${result.enabled ? 'enabled' : 'disabled'}`, false);
|
529 |
-
},
|
530 |
-
error: (err) => {
|
531 |
-
this.showMessage('Failed to toggle project', true);
|
532 |
-
}
|
533 |
-
});
|
534 |
-
}
|
535 |
-
|
536 |
-
deleteProject(project: Project) {
|
537 |
-
if (confirm(`Are you sure you want to delete "${project.name}"?`)) {
|
538 |
-
this.apiService.deleteProject(project.id).subscribe({
|
539 |
-
next: () => {
|
540 |
-
this.showMessage(`Project "${project.name}" deleted successfully`, false);
|
541 |
-
this.loadProjects();
|
542 |
-
},
|
543 |
-
error: (err) => {
|
544 |
-
this.showMessage(err.error?.detail || 'Failed to delete project', true);
|
545 |
-
}
|
546 |
-
});
|
547 |
-
}
|
548 |
-
}
|
549 |
-
|
550 |
-
private showMessage(message: string, isError: boolean) {
|
551 |
-
this.message = message;
|
552 |
-
this.isError = isError;
|
553 |
-
|
554 |
-
setTimeout(() => {
|
555 |
-
this.message = '';
|
556 |
-
}, 5000);
|
557 |
-
}
|
558 |
-
}
|
|
|
252 |
}, 5000);
|
253 |
}
|
254 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|