feat: integrate moveTask functionality into TaskMenuComponent

This commit is contained in:
Chneemann 2025-04-13 13:49:35 +02:00
parent 2632f859fe
commit 00b811afbd
7 changed files with 129 additions and 51 deletions

View file

@ -57,7 +57,10 @@
<div id="todo" class="details">
<!-- Loop through 'todo' tasks and display them -->
@for (task of filteredTasks[TODO]; track task.id) {
<app-task [task]="task"></app-task>
<app-task
[task]="task"
(updateStatusEmitter)="onStatusUpdate($event)"
></app-task>
<!-- Show empty state if no 'todo' tasks exist -->
} @empty {
@ -90,7 +93,10 @@
<div id="inprogress" class="details">
<!-- Loop through 'in progress' tasks and display them -->
@for (task of filteredTasks[IN_PROGRESS]; track task.id) {
<app-task [task]="task"></app-task>
<app-task
[task]="task"
(updateStatusEmitter)="onStatusUpdate($event)"
></app-task>
<!-- Show empty state if no 'in progress' tasks exist -->
} @empty {
@ -123,7 +129,10 @@
<div id="awaitfeedback" class="details">
<!-- Loop through 'await feedback' tasks and display them -->
@for (task of filteredTasks[AWAIT_FEEDBACK]; track task.id) {
<app-task [task]="task"></app-task>
<app-task
[task]="task"
(updateStatusEmitter)="onStatusUpdate($event)"
></app-task>
<!-- Show empty state if no 'await feedback' tasks exist -->
} @empty {
@ -157,7 +166,10 @@
<div id="done" class="details">
<!-- Loop through 'done' tasks and display them -->
@for (task of filteredTasks[DONE]; track task.id) {
<app-task [task]="task"></app-task>
<app-task
[task]="task"
(updateStatusEmitter)="onStatusUpdate($event)"
></app-task>
<!-- Show empty state if no 'done' tasks exist -->
} @empty {

View file

@ -9,7 +9,7 @@ import { Router } from '@angular/router';
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 { Task, TaskMoveEvent } from '../../interfaces/task.interface';
import { TaskService } from '../../services/task.service';
import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component';
import { debounceTime, finalize, Subject, take, takeUntil } from 'rxjs';
@ -38,13 +38,13 @@ export class BoardComponent implements OnInit, OnDestroy {
readonly AWAIT_FEEDBACK = 'awaitfeedback';
readonly DONE = 'done';
allTasks: Task[] = [];
allTasks: Array<Task> = [];
filteredTasks: { [key: string]: Task[] } = {};
searchValue: string = '';
searchInput: boolean = false;
taskMovedTo: string = '';
taskMovedFrom: string = '';
taskMovedTo = '';
taskMovedFrom = '';
searchValue = '';
searchInput = false;
isLoading = false;
private destroy$ = new Subject<void>();
@ -151,6 +151,14 @@ export class BoardComponent implements OnInit, OnDestroy {
});
}
/**
* Updates the status of a task when its status is changed.
* @param event The event containing the task and the new status to move to.
*/
onStatusUpdate(event: TaskMoveEvent) {
this.handleItemDropped(event.task, event.moveTo);
}
/**
* Handles the event when an item is dropped on a column.
* @param taskId The id of the task being moved.

View file

@ -1,11 +1,11 @@
<section>
@if (boardTaskStatus !== 'todo') {
<div class="link" (click)="moveTask('todo')">To do</div>
} @if (boardTaskStatus !== 'inprogress') {
<div class="link" (click)="moveTask('inprogress')">In progress</div>
} @if (boardTaskStatus !== 'awaitfeedback') {
<div class="link" (click)="moveTask('awaitfeedback')">Await feedback</div>
} @if (boardTaskStatus !== 'done') {
<div class="link" (click)="moveTask('done')">Done</div>
@if (boardTaskStatus !== TODO) {
<div class="link" (click)="moveTask(TODO)">To do</div>
} @if (boardTaskStatus !== IN_PROGRESS) {
<div class="link" (click)="moveTask(IN_PROGRESS)">In progress</div>
} @if (boardTaskStatus !== AWAIT_FEEDBACK) {
<div class="link" (click)="moveTask(AWAIT_FEEDBACK)">Await feedback</div>
} @if (boardTaskStatus !== DONE) {
<div class="link" (click)="moveTask(DONE)">Done</div>
}
</section>

View file

@ -1,4 +1,5 @@
import { Component, Input } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Task, TaskMoveEvent } from '../../../../interfaces/task.interface';
@Component({
selector: 'app-task-menu',
@ -8,22 +9,19 @@ import { Component, Input } from '@angular/core';
styleUrl: './task-menu.component.scss',
})
export class TaskMenuComponent {
@Input() taskId: string = '';
@Input() boardTaskStatus: string = '';
@Input() task!: Task;
@Input() boardTaskStatus!: string;
@Output() updateStatusEmitter = new EventEmitter<TaskMoveEvent>();
constructor() {}
readonly TODO = 'todo';
readonly IN_PROGRESS = 'inprogress';
readonly AWAIT_FEEDBACK = 'awaitfeedback';
readonly DONE = 'done';
/**
* Moves the task to a specified status.
*
* This function updates the status of the task identified by `taskId`
* in both `allTasks` and `filteredTasks` lists to the new status
* specified by `moveTo`. It also updates the task status in the
* Firestore database.
*
* @param moveTo - The new status to move the task to.
*/
moveTask(moveTo: string) {
// TODO
this.updateStatusEmitter.emit({
task: this.task,
moveTo: moveTo,
});
}
}

View file

@ -1,10 +1,13 @@
<section>
@if (task.id) {
<div (click)="handleMenuButtonClick($event, task.id)">
{{ disableDrag }}
<div
class="content"
draggable="true"
(dragstart)="dragDropService.startDragging($event, task)"
[draggable]="disableDrag"
(dragstart)="
disableDrag ? dragDropService.startDragging($event, task) : null
"
>
<div class="header">
<div
@ -65,10 +68,11 @@
</div>
</div>
</div>
@if (isMenuOpen){
@if (menuOpen){
<app-task-menu
[taskId]="task.id"
[task]="task"
[boardTaskStatus]="task.status"
(updateStatusEmitter)="onStatusUpdate($event)"
></app-task-menu>
} @if (AssignedDialogId != '') {
<div class="dialog" [style.left.px]="dialogX" [style.top.px]="dialogY">

View file

@ -1,7 +1,17 @@
import { CommonModule } from '@angular/common';
import { Component, HostListener, Input } from '@angular/core';
import {
Component,
EventEmitter,
HostListener,
Input,
Output,
} from '@angular/core';
import { DragDropService } from '../../../services/drag-drop.service';
import { Task } from '../../../interfaces/task.interface';
import {
Assignee,
Task,
TaskMoveEvent,
} from '../../../interfaces/task.interface';
import { OverlayService } from '../../../services/overlay.service';
import { Router } from '@angular/router';
import { TaskMenuComponent } from './task-menu/task-menu.component';
@ -16,15 +26,13 @@ import { take } from 'rxjs';
styleUrl: './task.component.scss',
})
export class TaskComponent {
@Input() task: Task = {} as Task;
@Input() task!: Task;
@Output() updateStatusEmitter = new EventEmitter<TaskMoveEvent>();
isMenuOpen: boolean = false;
AssignedDialogId: string = '';
dialogX: number = 0;
dialogY: number = 0;
readonly DISABLE_DRAG_BREAKPOINT = 667;
readonly DIALOG_OFFSET_X = 25;
readonly DIALOG_OFFSET_Y = 10;
creator: any = '';
assignees: any[] = [];
categoryColors = new Map<string, string>([
['User Story', '#0038ff'],
['Technical Task', '#20d7c2'],
@ -32,6 +40,15 @@ export class TaskComponent {
pageViewMedia$ = this.resizeService.pageViewMedia$;
menuOpen = false;
disableDrag = false;
AssignedDialogId = '';
dialogX = 0;
dialogY = 0;
creator = '';
assignees: Assignee[] = [];
constructor(
public dragDropService: DragDropService,
public overlayService: OverlayService,
@ -39,6 +56,20 @@ export class TaskComponent {
private router: Router
) {}
/**
* Sets the initial state of the `disableDrag` property based on the window width.
*/
ngOnInit(): void {
this.updateDisableDragStatus();
}
/**
* Updates the `disableDrag` property based on the current window width.
*/
private updateDisableDragStatus(): void {
this.disableDrag = window.innerWidth > this.DISABLE_DRAG_BREAKPOINT;
}
/**
* Handles the button click event on a task in the board
* @param event the MouseEvent
@ -60,7 +91,7 @@ export class TaskComponent {
* Toggle the task menu for the current task
*/
toggleTaskMenu() {
this.isMenuOpen = !this.isMenuOpen;
this.menuOpen = !this.menuOpen;
}
/**
@ -72,7 +103,7 @@ export class TaskComponent {
* If the page is not in the media view, open the task overlay
*/
openTaskDetailsOverlay(taskId: string) {
if (this.isMenuOpen) {
if (this.menuOpen) {
this.toggleTaskMenu();
}
this.pageViewMedia$.pipe(take(1)).subscribe((isPageViewMedia) => {
@ -96,7 +127,7 @@ export class TaskComponent {
);
if (!isMenuClicked) {
this.isMenuOpen = false;
this.menuOpen = false;
}
}
@ -117,8 +148,8 @@ export class TaskComponent {
* @param event the MouseEvent that triggered the dialog
*/
updateDialogPosition(event: MouseEvent) {
this.dialogX = event.clientX + 25;
this.dialogY = event.clientY + 10;
this.dialogX = event.clientX + this.DIALOG_OFFSET_X;
this.dialogY = event.clientY + this.DIALOG_OFFSET_Y;
}
/**
@ -149,4 +180,24 @@ export class TaskComponent {
return (completedSubtasksCount / this.task.subtasks.length) * 100;
}
/**
* Emits an event to update the status of a task.
* @param event The TaskMoveEvent containing the task and the new status to move to.
*/
onStatusUpdate(event: TaskMoveEvent) {
this.updateStatusEmitter.emit({
task: event.task,
moveTo: event.moveTo,
});
}
@HostListener('window:resize')
/**
* Called when the window is resized.
* Updates the disableDragStatus so that tasks are not draggable on mobile devices.
*/
onResize() {
this.updateDisableDragStatus();
}
}

View file

@ -24,3 +24,8 @@ export interface Assignee {
id?: string;
userId: string;
}
export interface TaskMoveEvent {
task: Task;
moveTo: string;
}