feat: migrate subtask creation and toggle (check/uncheck) functionality to Django Rest

This commit is contained in:
Chneemann 2025-04-02 20:32:23 +02:00
parent ff5abfa41a
commit 45a40a3980
7 changed files with 65 additions and 79 deletions

View file

@ -1,8 +1,7 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, HostListener, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms'; import { FormsModule, NgForm } from '@angular/forms';
import { AssignedComponent } from './assigned/assigned.component'; import { AssignedComponent } from './assigned/assigned.component';
import { FirebaseService } from '../../services/firebase.service';
import { Task } from '../../interfaces/task.interface'; import { Task } from '../../interfaces/task.interface';
import { OverlayService } from '../../services/overlay.service'; import { OverlayService } from '../../services/overlay.service';
import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form-btn.component'; import { FormBtnComponent } from '../../shared/components/buttons/form-btn/form-btn.component';
@ -38,7 +37,6 @@ export class AddTaskComponent implements OnInit {
subtaskValue: string = ''; subtaskValue: string = '';
constructor( constructor(
public firebaseService: FirebaseService,
private overlayService: OverlayService, private overlayService: OverlayService,
private taskService: TaskService, private taskService: TaskService,
private apiService: ApiService, private apiService: ApiService,
@ -57,7 +55,7 @@ export class AddTaskComponent implements OnInit {
priority: this.taskService.getPriorities()[0], priority: this.taskService.getPriorities()[0],
subtasks: [], subtasks: [],
subtasksTitle: [], subtasksTitle: [],
subtasksDone: [], subtasksStatus: [],
assigned: [], assigned: [],
assignees: [], assignees: [],
userData: [], userData: [],
@ -97,7 +95,9 @@ export class AddTaskComponent implements OnInit {
async loadEditTaskData() { async loadEditTaskData() {
if (this.overlayData) { if (this.overlayData) {
const taskData = await firstValueFrom(this.getTaskData(this.overlayData)); const taskData = await firstValueFrom(this.getTaskData(this.overlayData));
Object.assign(this.taskData, taskData); if (taskData) {
this.taskData = { ...this.taskData, ...taskData };
}
} else if (this.overlayType === 'newTaskOverlay') { } else if (this.overlayType === 'newTaskOverlay') {
this.taskData.status = this.overlayData; this.taskData.status = this.overlayData;
} }
@ -149,7 +149,7 @@ export class AddTaskComponent implements OnInit {
*/ */
addSubtask(subtaskName: string) { addSubtask(subtaskName: string) {
this.taskData.subtasksTitle.unshift(subtaskName); this.taskData.subtasksTitle.unshift(subtaskName);
this.taskData.subtasksDone.push(false); this.taskData.subtasksStatus.push(false);
this.saveTaskData(); this.saveTaskData();
} }
@ -161,7 +161,7 @@ export class AddTaskComponent implements OnInit {
const index = this.taskData.subtasksTitle.indexOf(subtaskName); const index = this.taskData.subtasksTitle.indexOf(subtaskName);
if (index !== -1) { if (index !== -1) {
this.taskData.subtasksTitle.splice(index, 1); this.taskData.subtasksTitle.splice(index, 1);
this.taskData.subtasksDone.splice(index, 1); this.taskData.subtasksStatus.splice(index, 1);
this.saveTaskData(); this.saveTaskData();
} }
} }
@ -270,7 +270,7 @@ export class AddTaskComponent implements OnInit {
this.taskData.category = ''; this.taskData.category = '';
this.taskData.assigned = []; this.taskData.assigned = [];
this.taskData.subtasksTitle = []; this.taskData.subtasksTitle = [];
this.taskData.subtasksDone = []; this.taskData.subtasksStatus = [];
} }
/** /**
@ -298,9 +298,7 @@ export class AddTaskComponent implements OnInit {
onSubmit(ngForm: NgForm) { onSubmit(ngForm: NgForm) {
if (ngForm.submitted && ngForm.form.valid) { if (ngForm.submitted && ngForm.form.valid) {
const { id, ...taskWithoutId } = this.taskData; this.apiService.saveNewTask(this.taskData).subscribe({
this.apiService.saveNewTask(taskWithoutId).subscribe({
next: (response) => { next: (response) => {
this.toastNotificationService.createTaskSuccessToast(); this.toastNotificationService.createTaskSuccessToast();
this.updateNotifierService.notifyUpdate('task'); this.updateNotifierService.notifyUpdate('task');

View file

@ -132,7 +132,7 @@ export class TaskComponent {
* @returns the number of completed subtasks * @returns the number of completed subtasks
*/ */
completedSubtasks(): number { completedSubtasks(): number {
return this.task.subtasks.filter((subtask) => subtask.done).length; return this.task.subtasks.filter((subtask) => subtask.status).length;
} }
/** /**
@ -141,7 +141,7 @@ export class TaskComponent {
*/ */
completedSubtasksPercent(): number { completedSubtasksPercent(): number {
const completedSubtasksCount = this.task.subtasks.filter( const completedSubtasksCount = this.task.subtasks.filter(
(subtask) => subtask.done (subtask) => subtask.status
).length; ).length;
return (completedSubtasksCount / this.task.subtasks.length) * 100; return (completedSubtasksCount / this.task.subtasks.length) * 100;

View file

@ -9,7 +9,7 @@ export interface Task {
priority: string; priority: string;
subtasks: Subtask[]; subtasks: Subtask[];
subtasksTitle: string[]; subtasksTitle: string[];
subtasksDone: boolean[]; subtasksStatus: boolean[];
assigned: string[]; assigned: string[];
assignees: Assignee[]; assignees: Assignee[];
userData: UserSummary[]; userData: UserSummary[];
@ -18,9 +18,9 @@ export interface Task {
} }
export interface Subtask { export interface Subtask {
id: number; id: string;
title: string; title: string;
done: boolean; status: boolean;
} }
export interface Assignee { export interface Assignee {

View file

@ -50,6 +50,21 @@ export class ApiService {
}); });
} }
updateSubtaskStatus(
taskId: string,
body: {
subtask_id: string;
subtask_title: string;
subtask_status: boolean;
}
): Observable<any> {
return this.request<any>(
'PATCH',
`/api/tasks/${taskId}/update_subtask/`,
body
);
}
saveNewTask(task: Task): Observable<Task> { saveNewTask(task: Task): Observable<Task> {
return this.request<Task>('POST', '/api/tasks/', task); return this.request<Task>('POST', '/api/tasks/', task);
} }

View file

@ -35,6 +35,12 @@ export class ToastNotificationService {
this.createInfoToast('Task successfully moved!', 'Task Moved'); this.createInfoToast('Task successfully moved!', 'Task Moved');
} }
// Subtask
updateSubtaskSuccessToast(): void {
this.createSuccessToast('Subtask updated successfully!', 'Subtask Updated');
}
// Contacts // Contacts
createContactSuccessToast(): void { createContactSuccessToast(): void {

View file

@ -30,7 +30,7 @@
</div> </div>
<div class="date"> <div class="date">
<p>{{ "addTask.date" | translate }}:</p> <p>{{ "addTask.date" | translate }}:</p>
{{ timeConverter(task.date) }} {{ task.date | date : "dd. MMM yyyy" }}
</div> </div>
<div class="priority"> <div class="priority">
<p>{{ "addTask.priority" | translate }}:</p> <p>{{ "addTask.priority" | translate }}:</p>
@ -104,12 +104,17 @@
<div <div
class="single-subtask" class="single-subtask"
(click)=" (click)="
toggleSubtaskStatus(overlayData, i, task.subtasksDone, subtask.done) toggleSubtaskStatus(
overlayData,
subtask.id,
subtask.title,
subtask.status
)
" "
> >
<img <img
[src]=" [src]="
subtask.done subtask.status
? './../../../../../assets/img/board/check-btn-checked.svg' ? './../../../../../assets/img/board/check-btn-checked.svg'
: './../../../../../assets/img/board/check-btn-unchecked.svg' : './../../../../../assets/img/board/check-btn-unchecked.svg'
" "

View file

@ -46,13 +46,6 @@ export class TaskOverlayComponent implements OnInit {
['Technical Task', '#20d7c2'], ['Technical Task', '#20d7c2'],
]); ]);
/**
* OnInit lifecycle hook.
*
* Initializes the overlay component by checking if the `overlayData` is empty.
* If it is, subscribes to route parameters to obtain and assign the `id` to `overlayData`.
* Also sets `overlayMobile` to `true` if parameters are successfully retrieved.
*/
ngOnInit() { ngOnInit() {
this.setCurrentUserId(); this.setCurrentUserId();
this.setOverlayDataFromRoute(); this.setOverlayDataFromRoute();
@ -117,11 +110,6 @@ export class TaskOverlayComponent implements OnInit {
}, 10); }, 10);
} }
/**
* Deletes the task with the given overlay data from the Firebase Realtime Database and closes the overlay.
* @param overlayData the overlay data of the task to be deleted
* @returns {void}
*/
deleteTask(taskId: string) { deleteTask(taskId: string) {
this.apiService.deleteTaskById(taskId).subscribe({ this.apiService.deleteTaskById(taskId).subscribe({
next: (task) => { next: (task) => {
@ -135,28 +123,31 @@ export class TaskOverlayComponent implements OnInit {
this.closeDialog(); this.closeDialog();
} }
/**
* Toggles the status of a single subtask of a task.
*
* This function takes the task id, the index of the subtask,
* the array of subtask statuses, and the status of the subtask.
* It toggles the status of the subtask at the given index
* and updates the subtasksDone property of the task in the
* Firebase Realtime Database.
*
* @param taskId The task id of the task containing the subtask.
* @param index The index of the subtask.
* @param array The array of subtask statuses.
* @param status The status of the subtask at the given index.
*/
toggleSubtaskStatus( toggleSubtaskStatus(
taskId: string, taskId: string,
index: number, subtaskId: string,
array: boolean[], subtaskTitle: string,
status: boolean currentStatus: boolean
) { ) {
status ? (array[index] = false) : (array[index] = true); const body = {
this.firebaseService.updateSubTask(taskId, array); subtask_id: subtaskId,
subtask_title: subtaskTitle,
subtask_status: !currentStatus,
};
this.apiService.updateSubtaskStatus(taskId, body).subscribe(
(response) => {
this.task?.subtasks.forEach((subtask) => {
if (subtask.id === subtaskId) {
subtask.status = !currentStatus;
}
});
this.toastNotificationService.updateSubtaskSuccessToast();
this.updateNotifierService.notifyUpdate('task');
},
(error) => {
console.error('Error updating subtask:', error);
}
);
} }
/** /**
@ -170,33 +161,4 @@ export class TaskOverlayComponent implements OnInit {
capitalizeFirstLetter(data: string) { capitalizeFirstLetter(data: string) {
return data.charAt(0).toUpperCase() + data.slice(1); return data.charAt(0).toUpperCase() + data.slice(1);
} }
/**
* Converts a date string to a human-readable format.
*
* @param dateString - The date string to convert.
* @returns A string representing the date in the format "MMM. DD, YYYY".
*/
timeConverter(dateString: string) {
var a = new Date(dateString);
var months = [
'Jan.',
'Feb.',
'Mar.',
'Apr.',
'May.',
'Jun.',
'Jul.',
'Aug.',
'Sep.',
'Oct.',
'Nov.',
'Dec.',
];
var year = a.getFullYear();
var month = months[a.getMonth()];
var date = a.getDate();
var time = month + ' ' + date + ', ' + year;
return time;
}
} }