import { CommonModule } from '@angular/common'; import { Component, OnInit, inject, signal } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { AddTaskDialogComponent } from './add-task-dialog.component'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators, } from '@angular/forms'; import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatDividerModule } from '@angular/material/divider'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterModule } from '@angular/router'; import { CommentResponse, CommentsService, TaskResponse, TasksService, User, UsersService, } from '@org/shared/api'; import { ToastService } from '@org/shared/toast'; import { catchError, finalize, forkJoin, of } from 'rxjs'; @Component({ selector: 'lib-task-list', standalone: true, imports: [ CommonModule, RouterModule, MatCardModule, MatListModule, MatProgressSpinnerModule, MatToolbarModule, MatIconModule, MatButtonModule, MatExpansionModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatDividerModule, MatBadgeModule, MatTooltipModule, ], template: `
Task Management @if (loading()) {

Loading tasks...

} @else if (tasks().length === 0) {
assignment

No Tasks Found

There are currently no tasks in the system.

} @else {
@for (task of tasks(); track task.tasks.id) {
assignment {{ task.tasks.title }} @if (getTaskComments(task.tasks.id).length) { comment }
By {{ getUserName(task.tasks.user_id) }} {{ formatDate(task.tasks.timestamp) }}

{{ task.tasks.description }}

forum Comments

@if (getTaskComments(task.tasks.id).length) {
@for (comment of getTaskComments(task.tasks.id); track comment.comments.id) {
{{ comment.users.name }} {{ formatDate(comment.comments.timestamp) }}

{{ comment.comments.content }}

}
} @else {

No comments yet.

}

Add a comment

Your comment @if (commentForm.get('content')?.hasError('required') && commentForm.get('content')?.touched) { Comment text is required }
Post as @if (commentForm.get('userId')?.hasError('required') && commentForm.get('userId')?.touched) { User is required }
}
}
`, styles: [ ` .fab { position: fixed; bottom: 2rem; right: 2rem; z-index: 1100; } .dialog-backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; z-index: 1200; } .dialog-card { min-width: 320px; max-width: 90vw; } .page-container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; } .page-header { margin-bottom: 1rem; border-radius: 4px; } .spacer { flex: 1 1 auto; } .loading-container { display: flex; flex-direction: column; justify-content: center; align-items: center; margin: 4rem 0; gap: 1rem; } .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: #f5f5f5; border-radius: 8px; padding: 3rem; margin: 2rem 0; text-align: center; } .empty-icon { font-size: 4rem; height: 4rem; width: 4rem; color: #bdbdbd; margin-bottom: 1rem; } .task-container { margin: 1rem 0; } .task-panel { margin-bottom: 1rem; border-radius: 8px; } .task-title-container { display: flex; align-items: center; gap: 0.5rem; } .comment-badge { margin-left: 0.5rem; } .task-info { display: flex; flex-direction: column; align-items: flex-end; } .task-author { font-weight: 500; } .task-timestamp { font-size: 0.8rem; color: #757575; } .task-details { padding: 1rem 0; } .task-description { font-size: 1rem; line-height: 1.5; margin-bottom: 1.5rem; white-space: pre-line; } .comments-section { margin-top: 1.5rem; } .comments-section h3 { display: flex; align-items: center; gap: 0.5rem; font-size: 1.2rem; margin-bottom: 1rem; } .comments-list { margin-bottom: 1.5rem; } .comment-item { padding: 1rem; background-color: #f5f5f5; border-radius: 8px; margin-bottom: 1rem; } .comment-header { display: flex; justify-content: space-between; margin-bottom: 0.5rem; } .comment-author { font-weight: 500; } .comment-time { font-size: 0.8rem; color: #757575; } .comment-content { margin: 0; white-space: pre-line; } .no-comments { font-style: italic; color: #757575; text-align: center; padding: 1rem; background-color: #f5f5f5; border-radius: 8px; } .comment-form { background-color: #f9f9f9; border-radius: 8px; padding: 1rem; margin-top: 1rem; } .comment-form h4 { margin-top: 0; margin-bottom: 1rem; font-size: 1rem; } .form-row { margin-bottom: 1rem; } .form-actions { display: flex; align-items: flex-start; gap: 1rem; } .full-width { width: 100%; } button { height: 56px; display: flex; align-items: center; gap: 0.5rem; } .button-spinner { margin-right: 0.5rem; } `, ], }) export class TaskListComponent implements OnInit { private tasksService = inject(TasksService); private usersService = inject(UsersService); private commentsService = inject(CommentsService); private fb = inject(FormBuilder); private toastService = inject(ToastService); tasks = signal([]); users = signal([]); comments = signal([]); loading = signal(true); addingComment = signal(false); private readonly dialog = inject(MatDialog); openAddTaskDialog() { const dialogRef = this.dialog.open(AddTaskDialogComponent, { width: '400px', enterAnimationDuration: '220ms', exitAnimationDuration: '180ms', autoFocus: true, restoreFocus: true, disableClose: false, }); dialogRef.afterClosed().subscribe(result => { if (result) { this.loadData(); } }); } commentForm: FormGroup = this.fb.group({ content: ['', Validators.required], userId: ['', Validators.required], }); ngOnInit() { this.loadData(); } loadData() { this.loading.set(true); forkJoin({ users: this.usersService.getUsers(), tasks: this.tasksService.getTasks(), comments: this.commentsService.getComments(), }) .pipe( catchError((error) => { console.error('Error loading data:', error); this.toastService.show( 'Failed to load data. Please try again.', 'error' ); return of({ users: [], tasks: [], comments: [] }); }), finalize(() => this.loading.set(false)) ) .subscribe({ next: (data) => { this.users.set(data.users); this.tasks.set(data.tasks); this.comments.set(data.comments); }, }); } getUserName(userId: number): string { const user = this.users().find((u) => u.id === userId); return user ? user.name : 'Unknown User'; } getTaskComments(taskId: number): CommentResponse[] { return this.comments().filter( (comment) => comment.comments.task_id === taskId ); } formatDate(timestamp?: string): string { if (!timestamp) return 'Unknown date'; const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays === 0) { return ( 'Today at ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) ); } else if (diffDays === 1) { return ( 'Yesterday at ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) ); } else if (diffDays < 7) { return `${diffDays} days ago`; } else { return date.toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric', }); } } addComment(taskId: number) { if (this.commentForm.invalid) return; this.addingComment.set(true); const comment = { content: this.commentForm.value.content, task_id: taskId, user_id: parseInt(this.commentForm.value.userId), }; this.commentsService .createComment(comment) .pipe(finalize(() => this.addingComment.set(false))) .subscribe({ next: (newComment) => { this.comments.update((comments) => [...comments, newComment]); this.commentForm.reset({ content: '', userId: '' }); this.toastService.show('Comment added successfully!'); }, error: (error) => { console.error('Error adding comment:', error); this.toastService.show( 'Failed to add comment! Please try again.', 'error' ); }, }); } }