feat: migrate backend from Firebase to Django Rest and integrate ApiService in BoardComponent
This commit is contained in:
parent
5949934b0d
commit
df7629256a
5 changed files with 193 additions and 113 deletions
|
|
@ -54,9 +54,11 @@ export class AddTaskComponent implements OnInit {
|
|||
category: '',
|
||||
status: 'todo',
|
||||
priority: 'medium',
|
||||
subtasks: [],
|
||||
subtasksTitle: [],
|
||||
subtasksDone: [],
|
||||
assigned: [],
|
||||
assignees: [],
|
||||
creator: this.firebaseService.getCurrentUserId(),
|
||||
date: this.currentDate,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@
|
|||
type="text"
|
||||
placeholder="{{ 'board.findTask' | translate }}"
|
||||
[(ngModel)]="searchValue"
|
||||
(input)="searchTask()"
|
||||
(input)="searchTask(searchValue)"
|
||||
/>
|
||||
<span>
|
||||
<!-- Show clear icon when search input is active -->
|
||||
@if (this.searchInput) {
|
||||
<img
|
||||
src="./../../../assets/img/board/clear.svg"
|
||||
|
|
@ -19,6 +20,8 @@
|
|||
alt="clear"
|
||||
(click)="clearInput()"
|
||||
/>
|
||||
|
||||
<!-- Show search icon when no search input is active -->
|
||||
} @else {
|
||||
<img
|
||||
src="./../../../assets/img/board/search.svg"
|
||||
|
|
@ -40,93 +43,117 @@
|
|||
<div class="status">
|
||||
<div
|
||||
class="column"
|
||||
(dragover)="dragDropService.allowDrop($event, 'todo')"
|
||||
(drop)="dragDropService.drop($event, 'todo')"
|
||||
(dragover)="dragDropService.allowDrop($event, TODO)"
|
||||
(drop)="dragDropService.drop($event, TODO)"
|
||||
>
|
||||
<div class="headline">
|
||||
<span>{{ "board.todo" | translate }}</span
|
||||
><img
|
||||
src="./../../../assets/img/board/plus.svg"
|
||||
alt="add"
|
||||
(click)="addNewTaskOverlay('todo')"
|
||||
(click)="addNewTaskOverlay(TODO)"
|
||||
/>
|
||||
</div>
|
||||
<div id="todo" class="details">
|
||||
@for(task of getTaskStatus("todo"); track task) {
|
||||
<!-- Loop through 'todo' tasks and display them -->
|
||||
@for (task of filteredTasks[TODO]; track task.id) {
|
||||
<app-task [task]="task"></app-task>
|
||||
|
||||
<!-- Show empty state if no 'todo' tasks exist -->
|
||||
} @empty {
|
||||
<app-task-empty>{{ "board.noTasks" | translate }}</app-task-empty>
|
||||
} @if(taskMovedTo === "todo" && taskMovedFrom !== "todo") {
|
||||
}
|
||||
|
||||
<!-- Highlight task if just moved to 'todo' -->
|
||||
@if (taskMovedTo === TODO && taskMovedFrom !== TODO) {
|
||||
<app-task-highlighted></app-task-highlighted>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="column"
|
||||
(dragover)="dragDropService.allowDrop($event, 'inprogress')"
|
||||
(drop)="dragDropService.drop($event, 'inprogress')"
|
||||
(dragover)="dragDropService.allowDrop($event, IN_PROGRESS)"
|
||||
(drop)="dragDropService.drop($event, IN_PROGRESS)"
|
||||
>
|
||||
<div class="headline">
|
||||
<span>{{ "board.inProgress" | translate }}</span
|
||||
><img
|
||||
src="./../../../assets/img/board/plus.svg"
|
||||
alt="add"
|
||||
(click)="addNewTaskOverlay('inprogress')"
|
||||
(click)="addNewTaskOverlay(IN_PROGRESS)"
|
||||
/>
|
||||
</div>
|
||||
<div id="inprogress" class="details">
|
||||
@for(task of getTaskStatus("inprogress"); track task) {
|
||||
<!-- Loop through 'in progress' tasks and display them -->
|
||||
@for (task of filteredTasks[IN_PROGRESS]; track task.id) {
|
||||
<app-task [task]="task"></app-task>
|
||||
|
||||
<!-- Show empty state if no 'in progress' tasks exist -->
|
||||
} @empty {
|
||||
<app-task-empty>{{ "board.noTasks" | translate }}</app-task-empty>
|
||||
} @if(taskMovedTo === "inprogress" && taskMovedFrom !== "inprogress")
|
||||
{
|
||||
}
|
||||
|
||||
<!-- Highlight task if just moved to 'in progress' -->
|
||||
@if (taskMovedTo === IN_PROGRESS && taskMovedFrom !== IN_PROGRESS) {
|
||||
<app-task-highlighted></app-task-highlighted>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="column"
|
||||
(dragover)="dragDropService.allowDrop($event, 'awaitfeedback')"
|
||||
(drop)="dragDropService.drop($event, 'awaitfeedback')"
|
||||
(dragover)="dragDropService.allowDrop($event, AWAIT_FEEDBACK)"
|
||||
(drop)="dragDropService.drop($event, AWAIT_FEEDBACK)"
|
||||
>
|
||||
<div class="headline">
|
||||
<span>{{ "board.awaitFeedback" | translate }}</span
|
||||
><img
|
||||
src="./../../../assets/img/board/plus.svg"
|
||||
alt="add"
|
||||
(click)="addNewTaskOverlay('awaitfeedback')"
|
||||
(click)="addNewTaskOverlay(AWAIT_FEEDBACK)"
|
||||
/>
|
||||
</div>
|
||||
<div id="awaitfeedback" class="details">
|
||||
@for(task of getTaskStatus("awaitfeedback"); track task) {
|
||||
<app-task [task]="task"></app-task> } @empty {
|
||||
<!-- Loop through 'await feedback' tasks and display them -->
|
||||
@for (task of filteredTasks[AWAIT_FEEDBACK]; track task.id) {
|
||||
<app-task [task]="task"></app-task>
|
||||
|
||||
<!-- Show empty state if no 'await feedback' tasks exist -->
|
||||
} @empty {
|
||||
<app-task-empty>{{ "board.noTasks" | translate }}</app-task-empty>
|
||||
} @if(taskMovedTo === "awaitfeedback" && taskMovedFrom !==
|
||||
"awaitfeedback") {
|
||||
}
|
||||
|
||||
<!-- Highlight task if just moved to 'await feedback' -->
|
||||
@if (taskMovedTo === AWAIT_FEEDBACK && taskMovedFrom !==
|
||||
AWAIT_FEEDBACK) {
|
||||
<app-task-highlighted></app-task-highlighted>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="column"
|
||||
(dragover)="dragDropService.allowDrop($event, 'done')"
|
||||
(drop)="dragDropService.drop($event, 'done')"
|
||||
(dragover)="dragDropService.allowDrop($event, DONE)"
|
||||
(drop)="dragDropService.drop($event, DONE)"
|
||||
>
|
||||
<div class="headline">
|
||||
<span>{{ "board.done" | translate }}</span
|
||||
><img
|
||||
src="./../../../assets/img/board/plus.svg"
|
||||
alt="add"
|
||||
(click)="addNewTaskOverlay('done')"
|
||||
(click)="addNewTaskOverlay(DONE)"
|
||||
/>
|
||||
</div>
|
||||
<div id="done" class="details">
|
||||
@for(task of getTaskStatus("done"); track task) {
|
||||
<!-- Loop through 'done' tasks and display them -->
|
||||
@for (task of filteredTasks[DONE]; track task.id) {
|
||||
<app-task [task]="task"></app-task>
|
||||
|
||||
<!-- Show empty state if no 'done' tasks exist -->
|
||||
} @empty {
|
||||
<app-task-empty>{{ "board.noTasks" | translate }}</app-task-empty>
|
||||
} @if(taskMovedTo === "done" && taskMovedFrom !== "done") {
|
||||
}
|
||||
|
||||
<!-- Highlight task if just moved to 'done' -->
|
||||
@if (taskMovedTo === DONE && taskMovedFrom !== DONE) {
|
||||
<app-task-highlighted></app-task-highlighted>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ import { CommonModule } from '@angular/common';
|
|||
import { TaskComponent } from './task/task.component';
|
||||
import { TaskEmptyComponent } from './task/task-empty/task-empty.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FirebaseService } from '../../services/firebase.service';
|
||||
import { OverlayService } from '../../services/overlay.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { SharedService } from '../../services/shared.service';
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'app-board',
|
||||
|
|
@ -26,47 +28,84 @@ import { TaskHighlightedComponent } from './task/task-highlighted/task-highlight
|
|||
styleUrl: './board.component.scss',
|
||||
})
|
||||
export class BoardComponent {
|
||||
readonly TODO = 'todo';
|
||||
readonly IN_PROGRESS = 'inprogress';
|
||||
readonly AWAIT_FEEDBACK = 'awaitfeedback';
|
||||
readonly DONE = 'done';
|
||||
|
||||
constructor(
|
||||
public dragDropService: DragDropService,
|
||||
public overlayService: OverlayService,
|
||||
private firebaseService: FirebaseService,
|
||||
private sharedService: SharedService,
|
||||
private apiService: ApiService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
allTasks: Task[] = [];
|
||||
filteredTasks: { [key: string]: Task[] } = {};
|
||||
|
||||
searchValue: string = '';
|
||||
searchInput: boolean = false;
|
||||
taskMovedTo: string = '';
|
||||
taskMovedFrom: string = '';
|
||||
taskDropped: boolean = false;
|
||||
|
||||
/**
|
||||
* OnInit lifecycle hook. Subscribes to the following events:
|
||||
*
|
||||
* - itemDropped: updates the taskMovedTo and taskMovedFrom properties
|
||||
* - itemMovedTo: updates the taskMovedTo property
|
||||
* - itemMovedFrom: updates the taskMovedFrom property
|
||||
*
|
||||
* These properties are used to conditionally render a highlighted task
|
||||
* component when a task is moved.
|
||||
*
|
||||
* @returns void
|
||||
* Is called when the component is initialized.
|
||||
* Calls the `loadTasks` method to load tasks and subscribes to drag-and-drop events via `subscribeToDragDropEvents`.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.loadTasks();
|
||||
this.subscribeToDragDropEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
);
|
||||
|
||||
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[] });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to events from the DragDropService and handles them.
|
||||
* @remarks
|
||||
* Handles the following events:
|
||||
* - `itemDropped`: Calls `handleItemDropped` with the task id and status.
|
||||
* - `itemMovedTo`: Sets `taskMovedTo` to the status.
|
||||
* - `itemMovedFrom`: Sets `taskMovedFrom` to the status.
|
||||
*/
|
||||
private subscribeToDragDropEvents(): void {
|
||||
this.dragDropService.itemDropped.subscribe(({ id, status }) => {
|
||||
this.handleItemDropped(id, status);
|
||||
});
|
||||
|
||||
this.dragDropService.itemMovedTo.subscribe(({ status }) => {
|
||||
this.taskMovedTo = status;
|
||||
});
|
||||
|
||||
this.dragDropService.itemMovedFrom.subscribe(({ status }) => {
|
||||
this.taskMovedFrom = status;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new task by opening a new route if the user is in page view mode,
|
||||
* or by setting the overlay data to open a new task overlay in overlay mode.
|
||||
* @param status The status of the task to be added.
|
||||
* Opens an overlay or navigates to the add-task page based on the view mode.
|
||||
* @param status The status to be used for the new task.
|
||||
*/
|
||||
addNewTaskOverlay(status: string) {
|
||||
this.sharedService.isPageViewMedia
|
||||
|
|
@ -75,92 +114,67 @@ export class BoardComponent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves tasks based on their status.
|
||||
* If a search input is active, it filters the tasks from the filteredTasks list.
|
||||
* Otherwise, it filters from the complete list of tasks.
|
||||
*
|
||||
* @param status The status of the tasks to be retrieved.
|
||||
* @returns A list of tasks matching the given status.
|
||||
* Handles the event when an item is dropped on a column.
|
||||
* @param taskId The id of the task being moved.
|
||||
* @param status The status of the column where the task was dropped.
|
||||
*/
|
||||
getTaskStatus(status: string) {
|
||||
if (this.updateSearchInput()) {
|
||||
return this.firebaseService
|
||||
.getFiltertTasks()
|
||||
.filter((task) => task.status === status);
|
||||
} else {
|
||||
return this.firebaseService
|
||||
.getAllTasks()
|
||||
.filter((task) => task.status === status);
|
||||
handleItemDropped(taskId: string, status: string): void {
|
||||
this.apiService.updateTaskStatus(taskId, status).subscribe({
|
||||
next: () => {
|
||||
this.updateTaskStatus(taskId, status);
|
||||
},
|
||||
error: (error) => console.error('Error updating task:', error),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the status of a task in the local task lists.
|
||||
* @param taskId The ID of the task to be updated.
|
||||
* @param status The new status to assign to the task.
|
||||
*/
|
||||
updateTaskStatus(taskId: string, status: string) {
|
||||
const updatedTask = this.allTasks.find((task) => task.id === taskId);
|
||||
if (updatedTask) {
|
||||
const oldStatus = updatedTask.status;
|
||||
updatedTask.status = status;
|
||||
|
||||
this.filteredTasks[oldStatus] = this.filteredTasks[oldStatus].filter(
|
||||
(task) => task !== updatedTask
|
||||
);
|
||||
this.filteredTasks[status] = [
|
||||
...(this.filteredTasks[status] || []),
|
||||
updatedTask,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the status of a task in both the complete and filtered lists
|
||||
* of tasks when it is dropped.
|
||||
*
|
||||
* @param id The id of the task to be updated.
|
||||
* @param status The new status of the task.
|
||||
*/
|
||||
handleItemDropped(id: string, status: string): void {
|
||||
const index = this.firebaseService.allTasks.findIndex(
|
||||
(task) => task.id === id
|
||||
);
|
||||
const filteredIndex = this.firebaseService.filteredTasks.findIndex(
|
||||
(task) => task.id === id
|
||||
);
|
||||
if (index !== -1) {
|
||||
this.firebaseService.allTasks[index].status = status;
|
||||
if (filteredIndex !== -1) {
|
||||
this.firebaseService.filteredTasks[filteredIndex].status = status;
|
||||
}
|
||||
this.firebaseService.updateTask(id, index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the search input by resetting the search input flag and the search value, and
|
||||
* then calling searchTask() to update the list of tasks.
|
||||
* Clears the search input by resetting the search input flag and the search value.
|
||||
*/
|
||||
clearInput() {
|
||||
this.searchInput = false;
|
||||
this.searchValue = '';
|
||||
this.searchTask();
|
||||
this.searchTask(this.searchValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the search input flag and the search value by stripping any XSS
|
||||
* characters from the search value, and then setting the search input flag
|
||||
* to true if the search value is not empty, and false otherwise.
|
||||
*
|
||||
* @returns The updated search input flag.
|
||||
* Filters the tasks by the given search term.
|
||||
* @param searchTerm The search term to filter the tasks by.
|
||||
* @returns The filtered tasks, with the same structure as the original tasks.
|
||||
*/
|
||||
updateSearchInput() {
|
||||
this.searchValue = this.sharedService.replaceXSSChars(this.searchValue);
|
||||
if (this.searchValue) {
|
||||
this.searchInput = this.searchValue.toLowerCase().length > 0;
|
||||
} else {
|
||||
this.searchInput = false;
|
||||
}
|
||||
return this.searchInput;
|
||||
}
|
||||
searchTask(searchTerm: string): void {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
|
||||
/**
|
||||
* Filters the list of tasks based on the current search value.
|
||||
*
|
||||
* It first calls updateSearchInput() to update the search input flag and the search value.
|
||||
* Then it sets the filteredTasks property of the FirebaseService to the result of calling
|
||||
* filter() on the list of all tasks, where the filter condition is that any of the
|
||||
* task's title, description, or category includes the search value (case-insensitive).
|
||||
*/
|
||||
searchTask(): void {
|
||||
this.updateSearchInput();
|
||||
this.firebaseService.filteredTasks = this.firebaseService
|
||||
.getAllTasks()
|
||||
.filter(
|
||||
this.filteredTasks = Object.fromEntries(
|
||||
Object.entries(this.filteredTasks).map(([status]) => [
|
||||
status,
|
||||
this.allTasks.filter(
|
||||
(task) =>
|
||||
task.title.toLowerCase().includes(this.searchValue) ||
|
||||
task.description.toLowerCase().includes(this.searchValue) ||
|
||||
task.category.toLowerCase().includes(this.searchValue)
|
||||
task.status === status &&
|
||||
(task.title.toLowerCase().includes(searchTerm) ||
|
||||
task.description.toLowerCase().includes(searchTerm))
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,17 @@ export interface Task {
|
|||
category: string;
|
||||
status: string;
|
||||
priority: string;
|
||||
subtasks: Subtask[];
|
||||
subtasksTitle: string[];
|
||||
subtasksDone: boolean[];
|
||||
assigned: string[];
|
||||
assignees: string[];
|
||||
creator: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export interface Subtask {
|
||||
id: number;
|
||||
title: string;
|
||||
done: boolean;
|
||||
}
|
||||
|
|
|
|||
29
src/app/services/api.service.ts
Normal file
29
src/app/services/api.service.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { apiConfig } from '../environments/config';
|
||||
import { Task } from '../interfaces/task.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ApiService {
|
||||
private apiUrl = apiConfig.apiUrl;
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
// ------------- TASKS ------------- //
|
||||
|
||||
getTasksByStatus(status: string): Observable<Task[]> {
|
||||
return this.http.get<Task[]>(`${this.apiUrl}/api/tasks/`, {
|
||||
params: { status },
|
||||
});
|
||||
}
|
||||
|
||||
updateTaskStatus(taskId: string, status: string): Observable<Task> {
|
||||
return this.http.put<Task>(
|
||||
`${this.apiUrl}/api/tasks/${taskId}/update_status/`,
|
||||
{ status: status }
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue