import { Component, OnInit, signal, computed } from '@angular/core'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { CommonModule } from '@angular/common'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatTreeModule } from '@angular/material/tree'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { ApiService } from '../../services'; import { Category, Subcategory } from '../../models'; import { CreateDialogComponent } from '../../components/create-dialog/create-dialog.component'; import { ConfirmDialogComponent } from '../../components/confirm-dialog/confirm-dialog.component'; interface CategoryNode { id: string; name: string; type: 'category' | 'subcategory'; visible: boolean; expanded?: boolean; children?: CategoryNode[]; categoryId?: string; } @Component({ selector: 'app-project-view', standalone: true, imports: [ CommonModule, RouterOutlet, MatSidenavModule, MatTreeModule, MatIconModule, MatButtonModule, MatSlideToggleModule, MatToolbarModule, MatProgressSpinnerModule, MatDialogModule, MatSnackBarModule ], templateUrl: './project-view.component.html', styleUrls: ['./project-view.component.scss'] }) export class ProjectViewComponent implements OnInit { projectId = signal(''); project = signal(null); categories = signal([]); loading = signal(true); treeData = signal([]); constructor( private route: ActivatedRoute, private router: Router, private apiService: ApiService, private dialog: MatDialog, private snackBar: MatSnackBar ) {} ngOnInit() { this.route.params.subscribe(params => { this.projectId.set(params['projectId']); this.loadProject(); this.loadCategories(); }); } hasActiveRoute(): boolean { return this.route.children.length > 0; } loadProject() { // Load project details this.apiService.getProjects().subscribe({ next: (projects) => { const project = projects.find(p => p.id === this.projectId()); this.project.set(project); }, error: (err) => { console.error('Failed to load project', err); } }); } loadCategories() { this.loading.set(true); this.apiService.getCategories(this.projectId()).subscribe({ next: (categories) => { this.categories.set(categories); this.buildTree(); this.loading.set(false); }, error: (err) => { console.error('Failed to load categories', err); this.loading.set(false); } }); } buildTree() { const tree: CategoryNode[] = this.categories().map(cat => ({ id: cat.id, name: cat.name, type: 'category' as const, visible: cat.visible, expanded: false, children: (cat.subcategories || []).map(sub => ({ id: sub.id, name: sub.name, type: 'subcategory' as const, visible: sub.visible, categoryId: cat.id })) })); this.treeData.set(tree); } toggleNode(node: CategoryNode) { node.expanded = !node.expanded; } editNode(node: CategoryNode, event: Event) { event.stopPropagation(); if (node.type === 'category') { this.router.navigate(['category', node.id], { relativeTo: this.route }); } else { this.router.navigate(['subcategory', node.id], { relativeTo: this.route }); } } toggleVisibility(node: CategoryNode, event: any) { event.stopPropagation(); node.visible = !node.visible; if (node.type === 'category') { this.apiService.updateCategory(node.id, { visible: node.visible }).subscribe(); } else { this.apiService.updateSubcategory(node.id, { visible: node.visible }).subscribe(); } } viewItems(node: CategoryNode, event: Event) { event.stopPropagation(); if (node.type === 'subcategory') { console.log('Navigating to items for subcategory:', node.id); this.router.navigate(['/project', this.projectId(), 'items', node.id]); } } goBack() { this.router.navigate(['/']); } addCategory() { const dialogRef = this.dialog.open(CreateDialogComponent, { data: { title: 'Create New Category', type: 'category', fields: [ { name: 'name', label: 'Name', type: 'text', required: true }, { name: 'priority', label: 'Priority', type: 'number', value: 99 }, { name: 'visible', label: 'Visible', type: 'toggle', value: true } ] } }); dialogRef.afterClosed().subscribe(result => { if (result) { this.apiService.createCategory(this.projectId(), result).subscribe({ next: () => { this.snackBar.open('Category created!', 'Close', { duration: 2000 }); this.loadCategories(); }, error: (err) => { this.snackBar.open('Failed to create category', 'Close', { duration: 3000 }); } }); } }); } deleteCategory(node: CategoryNode, event: Event) { event.stopPropagation(); const dialogRef = this.dialog.open(ConfirmDialogComponent, { data: { title: 'Delete Category', message: `Are you sure you want to delete "${node.name}"? This will also delete all subcategories and items.`, confirmText: 'Delete', warning: true } }); dialogRef.afterClosed().subscribe(confirmed => { if (confirmed) { this.apiService.deleteCategory(node.id).subscribe({ next: () => { this.snackBar.open('Category deleted', 'Close', { duration: 2000 }); this.loadCategories(); }, error: (err) => { this.snackBar.open('Failed to delete category', 'Close', { duration: 3000 }); } }); } }); } deleteSubcategory(node: CategoryNode, event: Event) { event.stopPropagation(); const dialogRef = this.dialog.open(ConfirmDialogComponent, { data: { title: 'Delete Subcategory', message: `Are you sure you want to delete "${node.name}"? This will also delete all items.`, confirmText: 'Delete', cancelText: 'Cancel', dangerous: true } }); dialogRef.afterClosed().subscribe(confirmed => { if (confirmed) { this.apiService.deleteSubcategory(node.id).subscribe({ next: () => { this.snackBar.open('Subcategory deleted', 'Close', { duration: 2000 }); this.loadCategories(); }, error: (err) => { this.snackBar.open('Failed to delete subcategory', 'Close', { duration: 3000 }); } }); } }); } }