diff --git a/src/app/components/add-task/add-task.component.ts b/src/app/components/add-task/add-task.component.ts
index ddc3af7..615dbc9 100644
--- a/src/app/components/add-task/add-task.component.ts
+++ b/src/app/components/add-task/add-task.component.ts
@@ -59,6 +59,7 @@ export class AddTaskComponent implements OnInit {
subtasksDone: [],
assigned: [],
assignees: [],
+ userData: [],
creator: this.firebaseService.getCurrentUserId(),
date: this.currentDate,
};
diff --git a/src/app/components/board/board.component.ts b/src/app/components/board/board.component.ts
index cbbbf5e..cc2bd21 100644
--- a/src/app/components/board/board.component.ts
+++ b/src/app/components/board/board.component.ts
@@ -11,7 +11,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { TaskHighlightedComponent } from './task/task-highlighted/task-highlighted.component';
import { ApiService } from '../../services/api.service';
import { Task } from '../../interfaces/task.interface';
-import { forkJoin } from 'rxjs';
+import { TaskService } from '../../services/task.service';
@Component({
selector: 'app-board',
@@ -37,6 +37,7 @@ export class BoardComponent {
public dragDropService: DragDropService,
public overlayService: OverlayService,
private sharedService: SharedService,
+ private taskService: TaskService,
private apiService: ApiService,
private router: Router
) {}
@@ -62,22 +63,15 @@ export class BoardComponent {
* Retrieves all tasks from the API and initializes the `allTasks` and `filteredTasks` properties.
*/
loadTasks(): void {
- const statuses = [
- this.TODO,
- this.IN_PROGRESS,
- this.AWAIT_FEEDBACK,
- this.DONE,
- ];
- const requests = statuses.map((status) =>
- this.apiService.getTasksByStatus(status)
- );
+ this.taskService.loadAllTasks().subscribe({
+ next: (result) => {
+ this.allTasks = result.allTasks;
+ this.filteredTasks = result.filteredTasks;
+ },
- forkJoin(requests).subscribe((results) => {
- this.allTasks = results.flat();
- this.filteredTasks = statuses.reduce((acc, status, index) => {
- acc[status] = results[index];
- return acc;
- }, {} as { [key: string]: Task[] });
+ error: (err) => {
+ console.error('Error loading the tasks:', err);
+ },
});
}
diff --git a/src/app/components/board/task/task.component.html b/src/app/components/board/task/task.component.html
index 1847982..c27b810 100644
--- a/src/app/components/board/task/task.component.html
+++ b/src/app/components/board/task/task.component.html
@@ -27,7 +27,7 @@
{{ task.description }}
- @if(task.subtasksTitle.length > 0) {
+ @if(task.subtasks.length > 0) {
- {{ completedSubtasks() }} / {{ task.subtasksTitle.length }} Subtasks
+ {{ completedSubtasks() }} / {{ task.subtasks.length }} Subtasks
}
@@ -70,14 +73,15 @@
>
} @if (AssignedDialogId != '') {
+ @for (user of task.userData; track user) { @if (user.id ===
+ AssignedDialogId) {
- {{ firebaseService.getUserDetails(AssignedDialogId, "firstName") }}
- @if (AssignedDialogId === firebaseService.getCurrentUserId()) {
- (du) }
+ {{ user.firstName }}
- {{ firebaseService.getUserDetails(AssignedDialogId, "lastName") }}
+ {{ user.lastName }}
+ }}
} }
diff --git a/src/app/components/board/task/task.component.ts b/src/app/components/board/task/task.component.ts
index 1198625..95ba6c5 100644
--- a/src/app/components/board/task/task.component.ts
+++ b/src/app/components/board/task/task.component.ts
@@ -22,6 +22,9 @@ export class TaskComponent {
dialogX: number = 0;
dialogY: number = 0;
+ creator: any = '';
+ assignees: any[] = [];
+
categoryColors = new Map([
['User Story', '#0038ff'],
['Technical Task', '#20d7c2'],
@@ -129,8 +132,7 @@ export class TaskComponent {
* @returns the number of completed subtasks
*/
completedSubtasks(): number {
- return this.task.subtasksDone.filter((subtask: boolean) => subtask === true)
- .length;
+ return this.task.subtasks.filter((subtask) => subtask.done).length;
}
/**
@@ -138,12 +140,11 @@ export class TaskComponent {
* @returns the percentage of completed subtasks as a number
*/
completedSubtasksPercent(): number {
- const subtasks = this.task.subtasksDone;
- const completedSubtasksCount = subtasks.filter(
- (subtask: boolean) => subtask === true
+ const completedSubtasksCount = this.task.subtasks.filter(
+ (subtask) => subtask.done
).length;
- return (completedSubtasksCount / subtasks.length) * 100;
+ return (completedSubtasksCount / this.task.subtasks.length) * 100;
}
// Assigned
diff --git a/src/app/interfaces/task.interface.ts b/src/app/interfaces/task.interface.ts
index e14fd28..0776d21 100644
--- a/src/app/interfaces/task.interface.ts
+++ b/src/app/interfaces/task.interface.ts
@@ -1,3 +1,5 @@
+import { UserSummary } from './user.interface';
+
export interface Task {
id?: string;
title: string;
@@ -9,7 +11,8 @@ export interface Task {
subtasksTitle: string[];
subtasksDone: boolean[];
assigned: string[];
- assignees: string[];
+ assignees: Assignee[];
+ userData: UserSummary[];
creator: string;
date: string;
}
@@ -19,3 +22,9 @@ export interface Subtask {
title: string;
done: boolean;
}
+
+export interface Assignee {
+ id: string;
+ user: string;
+ task: string;
+}
diff --git a/src/app/interfaces/user.interface.ts b/src/app/interfaces/user.interface.ts
index c631ff3..8d6e809 100644
--- a/src/app/interfaces/user.interface.ts
+++ b/src/app/interfaces/user.interface.ts
@@ -10,3 +10,11 @@ export interface User {
status: boolean;
lastLogin: number;
}
+
+export interface UserSummary {
+ id?: string;
+ firstName: string;
+ lastName: string;
+ initials: string;
+ color: string;
+}
diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts
index 28dd9db..2d96b12 100644
--- a/src/app/services/api.service.ts
+++ b/src/app/services/api.service.ts
@@ -1,8 +1,9 @@
-import { Injectable } from '@angular/core';
+import { Injectable, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { apiConfig } from '../environments/config';
import { Task } from '../interfaces/task.interface';
+import { User } from '../interfaces/user.interface';
@Injectable({
providedIn: 'root',
@@ -14,6 +15,10 @@ export class ApiService {
// ------------- TASKS ------------- //
+ getTaskById(taskId: string): Observable {
+ return this.http.get(`${this.apiUrl}/api/tasks/${taskId}/`);
+ }
+
getTasksByStatus(status: string): Observable {
return this.http.get(`${this.apiUrl}/api/tasks/`, {
params: { status },
@@ -26,4 +31,12 @@ export class ApiService {
{ status: status }
);
}
+
+ // ------------- USERS ------------- //
+
+ getUsersByIds(userIds: string[]): Observable {
+ return this.http.get(`${this.apiUrl}/api/users/`, {
+ params: { ids: userIds.join(',') },
+ });
+ }
}
diff --git a/src/app/services/task.service.ts b/src/app/services/task.service.ts
new file mode 100644
index 0000000..2107d59
--- /dev/null
+++ b/src/app/services/task.service.ts
@@ -0,0 +1,130 @@
+import { Injectable } from '@angular/core';
+import { Observable, forkJoin, of } from 'rxjs';
+import { ApiService } from './api.service';
+import { Task } from '../interfaces/task.interface';
+import { catchError, map, switchMap } from 'rxjs/operators';
+import { UserSummary } from '../interfaces/user.interface';
+
+@Injectable({ providedIn: 'root' })
+export class TaskService {
+ private readonly statuses = ['todo', 'inprogress', 'awaitfeedback', 'done'];
+
+ constructor(private apiService: ApiService) {}
+
+ loadAllTasks(): Observable<{
+ allTasks: Task[];
+ filteredTasks: Record;
+ }> {
+ return forkJoin(
+ this.statuses.map((status) => this.fetchTasksByStatus(status))
+ ).pipe(
+ switchMap((results) => {
+ const allTasks = results.flat();
+ const filteredTasks = this.statuses.reduce((acc, status, index) => {
+ acc[status] = results[index] || [];
+ return acc;
+ }, {} as Record);
+
+ return this.loadUsersForTasks(allTasks).pipe(
+ map((tasksWithUsers) => {
+ const updatedFilteredTasks = this.statuses.reduce((acc, status) => {
+ acc[status] = tasksWithUsers.filter(
+ (task) => task.status === status
+ );
+ return acc;
+ }, {} as Record);
+
+ return {
+ allTasks: tasksWithUsers,
+ filteredTasks: updatedFilteredTasks,
+ };
+ })
+ );
+ })
+ );
+ }
+
+ private fetchTasksByStatus(status: string): Observable {
+ return this.apiService.getTasksByStatus(status).pipe(
+ catchError((error) => {
+ console.error(
+ `Fehler beim Abrufen von Tasks für Status ${status}:`,
+ error
+ );
+ return of([]);
+ })
+ );
+ }
+
+ private loadUsersForTasks(tasks: Task[]): Observable {
+ const userIds = new Set(
+ tasks.flatMap((task) => [
+ task.creator,
+ ...task.assignees.map((a) => a.user),
+ ])
+ );
+
+ return this.apiService.getUsersByIds([...userIds]).pipe(
+ catchError((error) => {
+ console.error('Fehler beim Abrufen der Benutzer:', error);
+ return of([]);
+ }),
+ map((users) => {
+ const userMap = this.createUserMap(users);
+ return tasks.map((task) => ({
+ ...task,
+ userData: [
+ ...task.assignees.map((a) => userMap[a.user] || null),
+ userMap[task.creator] || null,
+ ].filter(Boolean),
+ }));
+ })
+ );
+ }
+
+ loadSingleTask(taskId: string): Observable {
+ return this.apiService.getTaskById(taskId).pipe(
+ switchMap((task) => {
+ if (!task) return of(null);
+ const userIds = [task.creator, ...task.assignees.map((a) => a.user)];
+
+ return this.apiService.getUsersByIds(userIds).pipe(
+ map((users) => ({
+ ...task,
+ userData: this.mapTaskUsers(task, users),
+ })),
+ catchError((error) => {
+ console.error('Fehler beim Abrufen der Benutzerdaten:', error);
+ return of(task);
+ })
+ );
+ }),
+ catchError((error) => {
+ console.error('Fehler beim Laden des Tasks:', error);
+ return of(null);
+ })
+ );
+ }
+
+ private createUserMap(users: UserSummary[]): Record {
+ return users.reduce((acc, user) => {
+ if (user?.id)
+ acc[user.id] = {
+ id: user.id,
+ firstName: user.firstName,
+ lastName: user.lastName,
+ initials: user.initials,
+ color: user.color,
+ };
+ return acc;
+ }, {} as Record);
+ }
+
+ private mapTaskUsers(task: Task, users: UserSummary[]): UserSummary[] {
+ const userMap = this.createUserMap(users);
+ return [
+ ...task.assignees.map((a) => userMap[a.user] || null),
+ userMap[task.creator] || null,
+ ].filter(Boolean);
+ }
+}
diff --git a/src/app/shared/components/overlay/task-overlay/task-overlay.component.html b/src/app/shared/components/overlay/task-overlay/task-overlay.component.html
index bb0062e..424d9b4 100644
--- a/src/app/shared/components/overlay/task-overlay/task-overlay.component.html
+++ b/src/app/shared/components/overlay/task-overlay/task-overlay.component.html
@@ -3,16 +3,14 @@
overlay: !overlayMobile,
'overlay-mobile': overlayMobile,
}"
- *ngIf="overlayData !== '' && getTaskData(overlayData).length > 0"
+ *ngIf="overlayData !== '' && task != null"
>
- @if(getTaskData(overlayData)[0].creator ===
- firebaseService.getCurrentUserId()) {
+ @if(task.creator === firebaseService.getCurrentUserId()) {

diff --git a/src/app/shared/components/overlay/task-overlay/task-overlay.component.ts b/src/app/shared/components/overlay/task-overlay/task-overlay.component.ts
index 684aaea..0726f84 100644
--- a/src/app/shared/components/overlay/task-overlay/task-overlay.component.ts
+++ b/src/app/shared/components/overlay/task-overlay/task-overlay.component.ts
@@ -6,6 +6,8 @@ import { OverlayService } from '../../../../services/overlay.service';
import { ActivatedRoute, Router } from '@angular/router';
import { BtnBackComponent } from '../../buttons/btn-back/btn-back.component';
import { TranslateModule } from '@ngx-translate/core';
+import { Task } from '../../../../interfaces/task.interface';
+import { TaskService } from '../../../../services/task.service';
@Component({
selector: 'app-task-overlay',
@@ -18,11 +20,13 @@ export class TaskOverlayComponent implements OnInit {
@Input() overlayData: string = '';
@Output() closeDialogEmitter = new EventEmitter
();
+ task: Task | null = null;
overlayMobile: boolean = false;
constructor(
public firebaseService: FirebaseService,
private overlayService: OverlayService,
+ private taskService: TaskService,
private router: Router,
private route: ActivatedRoute
) {}
@@ -40,16 +44,30 @@ export class TaskOverlayComponent implements OnInit {
* Also sets `overlayMobile` to `true` if parameters are successfully retrieved.
*/
ngOnInit() {
- if (this.overlayData == '') {
- if (this.route.params.subscribe()) {
- this.route.params.subscribe((params) => {
- this.overlayData = params['id'];
- this.overlayMobile = true;
- });
- }
+ this.setOverlayDataFromRoute();
+ this.loadTask(this.overlayData);
+ }
+
+ setOverlayDataFromRoute() {
+ if (this.overlayData === '') {
+ this.route.params.subscribe((params) => {
+ this.overlayData = params['id'];
+ this.overlayMobile = true;
+ });
}
}
+ loadTask(taskId: string) {
+ this.taskService.loadSingleTask(taskId).subscribe({
+ next: (task) => {
+ this.task = task;
+ },
+ error: (err) => {
+ console.error('Error loading the task:', err);
+ },
+ });
+ }
+
/**
* Close the task overlay.
*
@@ -89,40 +107,6 @@ export class TaskOverlayComponent implements OnInit {
this.closeDialog();
}
- /**
- * Gets the task data from the Firebase Realtime Database for the given task id.
- *
- * This function takes a task id and returns an array of tasks with the given id.
- * The function uses the Firebase service to get all tasks from the Realtime Database
- * and then filters the result to return only the task with the given id.
- *
- * @param taskId The id of the task to be retrieved.
- * @returns {Task[]} An array of tasks with the given id.
- */
- getTaskData(taskId: string) {
- return this.firebaseService
- .getAllTasks()
- .filter((task) => task.id === taskId);
- }
-
- /**
- * Retrieves the status of a single subtask of a task.
- *
- * This function takes the task id and the index of the subtask
- * and returns the status of the subtask at the given index.
- *
- * @param taskId The task id of the task containing the subtask.
- * @param index The index of the subtask.
- * @returns {boolean} The status of the subtask at the given index.
- */
- getSubTaskStatus(taskId: string, index: number) {
- const subtask = this.firebaseService
- .getAllTasks()
- .filter((task) => task.id === taskId);
-
- return subtask[0].subtasksDone[index];
- }
-
/**
* Toggles the status of a single subtask of a task.
*