refactor: optimize and add comments to TaskComponent

This commit is contained in:
Chneemann 2025-04-15 23:17:45 +02:00
parent b75fb18e42
commit c02cf4d7a3
3 changed files with 182 additions and 137 deletions

View file

@ -1,8 +1,9 @@
<section>
@if (task.id) {
<!-- If the task exists, display it -->
@if (task && task.id) {
<div (click)="handleMenuButtonClick($event, task.id)">
<div
class="content"
class="task"
[draggable]="disableDrag"
(dragstart)="
disableDrag ? dragDropService.startDragging($event, task) : null
@ -13,8 +14,10 @@
class="category"
[style.background-color]="categoryColors.get(task.category)"
>
{{ task.category }}
<p>{{ task.category }}</p>
</div>
<!-- If the user is on a mobile device, show the menu button -->
@if (pageViewMedia$ | async) {
<div class="menu-btn" (click)="handleMenuButtonClick($event, task.id)">
<img
@ -25,10 +28,13 @@
</div>
}
</div>
<div class="headline">{{ task.title }}</div>
<div class="title">{{ task.title }}</div>
<div class="description">
{{ task.description }}
</div>
<!-- If the task has subtasks, display them -->
@if(task.subtasks.length > 0) {
<div class="subtask">
<div class="subtask-line">
@ -37,46 +43,67 @@
[style.width.%]="completedSubtasksPercent()"
></span>
</div>
<div class="subtask-text">
{{ completedSubtasks() }} / {{ task.subtasks.length }} Subtasks
</div>
<p>{{ completedSubtasks() }} / {{ task.subtasks.length }} Subtasks</p>
</div>
}
<div class="footer">
<div class="footer-badge">
@for (user of task.userData; track user) { @if (task.creator ===
user.id) {
<span
class="footer-badged"
<div class="assignees">
<div class="assigned">
<!-- Loop through user data -->
@for (user of task.userData; track user) {
<!-- If this user is the creator of the task, display the badge -->
@if (task.creator === user.id) {
<div
class="assigned-badged"
(mousemove)="openDialog(task.creator, $event)"
(mouseleave)="closeDialog()"
[style.background-color]="user.color"
>{{ user.initials }}</span
>} } @for (user of task.userData; track user) { @for (assigned of
task.assignees; track assigned) { @if (assigned.userId === user.id) {
<span
class="footer-badged"
>
<span>{{ user.initials }}</span>
</div>
} }
<!-- Loop through user data -->
@for (user of task.userData; track user) {
<!-- Loop through task assignees -->
@for (assigned of task.assignees; track assigned) {
<!-- If this user is assigned to the task, display the badge -->
@if (assigned.userId === user.id) {
<div
class="assigned-badged"
(mousemove)="openDialog(user.id, $event)"
(mouseleave)="closeDialog()"
[style.background-color]="user.color"
>{{ user.initials }}</span
>
<span>{{ user.initials }}</span>
</div>
} } }
</div>
<div class="footer-priority prio-{{ task.priority }}"></div>
<div class="priority priority-{{ task.priority }}"></div>
</div>
</div>
</div>
@if (menuOpen){
<!-- If the task menu is open, display it -->
@if (mobileMenuOpen){
<app-task-menu
[task]="task"
[boardTaskStatus]="task.status"
(updateStatusEmitter)="onStatusUpdate($event)"
></app-task-menu>
} @if (AssignedDialogId) {
}
<!-- If the assigned dialog is open, display it -->
@if (assignedDialogId) {
<div class="dialog" [style.left.px]="dialogX" [style.top.px]="dialogY">
@for (user of task.userData; track user) { @if (user.id ===
AssignedDialogId) {
<!-- Loop through user data -->
@for (user of task.userData; track user) {
<!-- If the user id matches the assigned dialog id, display the user details -->
@if (user.id === assignedDialogId) {
<p>
{{ user.firstName }}
</p>

View file

@ -4,8 +4,7 @@ section {
position: relative;
}
.content {
position: relative;
.task {
display: flex;
flex-direction: column;
justify-content: center;
@ -13,27 +12,12 @@ section {
padding: 16px;
border-radius: 24px;
background-color: var(--white);
box-shadow: 0px 0px 10px 3px rgba(0, 0, 0, 0.08);
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.08);
margin-bottom: 16px;
cursor: pointer;
&:hover {
box-shadow: 0px 0px 10px 3px rgba(0, 0, 0, 0.3);
}
}
.dialog {
position: fixed;
width: fit-content;
height: fit-content;
padding: 10px 15px;
border-radius: 0px 20px 20px 20px;
border: 1px solid var(--black);
z-index: 2;
background-color: var(--white);
p,
span {
font-size: 18px;
font-weight: 400;
&:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
}
}
@ -41,23 +25,31 @@ section {
display: flex;
align-items: center;
justify-content: space-between;
.category {
width: fit-content;
font-size: 16px;
padding: 4px 16px;
border-radius: 8px;
color: var(--white);
text-shadow: 1px 1px 2px var(--black);
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
p {
font-size: 16px;
text-shadow: 1px 1px 2px var(--black);
}
}
.menu-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 6px 12px;
img {
height: 16px;
width: auto;
}
&:hover {
img {
filter: brightness(0) saturate(100%) invert(56%) sepia(64%)
@ -67,9 +59,9 @@ section {
}
}
.headline {
.title {
color: var(--dark-blue);
font-size: 16px;
font-size: 18px;
font-weight: 700;
margin-top: 12px;
}
@ -83,11 +75,18 @@ section {
overflow: auto;
}
// Subtasks
.subtask {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 24px;
p {
font-size: 12px;
font-weight: 400;
}
}
.subtask-line {
@ -100,78 +99,101 @@ section {
align-items: flex-start;
border-radius: 8px;
background-color: var(--very-light-gray);
.filler-full {
border-radius: 16px;
background-color: var(--light-blue);
height: 8px;
}
}
.filler-full {
border-radius: 16px;
background-color: var(--light-blue);
height: 8px;
}
// Assignees
.subtask-text {
font-size: 12px;
font-weight: 400;
}
.footer {
.assignees {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 24px;
margin-top: 15px;
width: 100%;
.assigned {
display: flex;
align-items: center;
position: relative;
width: 170px;
height: 45px;
gap: 6px;
overflow: auto;
}
}
.footer-badge {
display: flex;
width: 170px;
overflow: auto;
}
.footer-badged {
.assigned-badged {
display: flex;
justify-content: center;
align-items: center;
min-width: 32px;
min-height: 32px;
border-radius: 45px;
border: 1px solid var(--white);
color: var(--white);
font-size: 12px;
font-weight: 400;
width: 32px;
height: 32px;
border-radius: 100%;
border: 1px solid var(--black);
position: relative;
transition: box-shadow 0.2s ease, transform 0.2s ease;
margin-left: 3px;
&:nth-child(n + 2) {
margin-left: -12px;
}
& span {
color: var(--white);
font-size: 14px;
font-weight: 700;
text-shadow: 1px 1px 2px var(--black);
}
&:hover {
transform: translateY(-1px) scale(1.02);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
z-index: 2;
}
}
.footer-badge span {
margin-right: 4px;
text-shadow: 1px 1px 2px var(--black);
}
// Priority
.footer-badge span:first-child {
margin-left: 0 !important;
}
.footer-priority {
.priority {
background-size: 32px;
background-repeat: no-repeat;
background-position: center;
width: 32px;
height: 32px;
}
.priority {
background-size: 24px;
background-repeat: no-repeat;
background-position: center;
width: 24px;
height: 24px;
}
.prio-urgent {
.priority-urgent {
background-image: url(./../../../../assets/img/urgent.svg);
}
.prio-medium {
.priority-medium {
background-image: url(./../../../../assets/img/medium.svg);
}
.prio-low {
.priority-low {
background-image: url(./../../../../assets/img/low.svg);
}
// Dialog
.dialog {
position: fixed;
width: fit-content;
height: fit-content;
padding: 10px 15px;
border-radius: 0px 20px 20px 20px;
border: 1px solid var(--black);
z-index: 3;
background-color: var(--white);
p,
span {
font-size: 18px;
font-weight: 400;
}
}

View file

@ -33,22 +33,20 @@ export class TaskComponent {
readonly DIALOG_OFFSET_X = 25;
readonly DIALOG_OFFSET_Y = 10;
pageViewMedia$ = this.resizeService.pageViewMedia$;
assignees: Assignee[] = [];
categoryColors = new Map<string, string>([
['User Story', '#0038ff'],
['Technical Task', '#20d7c2'],
]);
pageViewMedia$ = this.resizeService.pageViewMedia$;
menuOpen = false;
mobileMenuOpen = false;
disableDrag = false;
AssignedDialogId = '';
assignedDialogId = '';
dialogX = 0;
dialogY = 0;
creator = '';
assignees: Assignee[] = [];
constructor(
public dragDropService: DragDropService,
public overlayService: OverlayService,
@ -78,20 +76,20 @@ export class TaskComponent {
* If the event target is a menu button or menu img, toggle the task menu
* If the event target is anything else, open the task details overlay
*/
handleMenuButtonClick(event: MouseEvent, taskId: string) {
handleMenuButtonClick(event: MouseEvent, taskId: string): void {
event.stopPropagation();
const targetElement = event.target as HTMLElement;
targetElement.classList.contains('menu-btn') ||
targetElement.classList.contains('menu-img')
? this.toggleTaskMenu()
? this.toggleMobileMenu()
: this.openTaskDetailsOverlay(taskId);
}
/**
* Toggle the task menu for the current task
*/
toggleTaskMenu() {
this.menuOpen = !this.menuOpen;
toggleMobileMenu(): void {
this.mobileMenuOpen = !this.mobileMenuOpen;
}
/**
@ -102,9 +100,9 @@ export class TaskComponent {
* If the page is in the media view, navigate to the task details page
* If the page is not in the media view, open the task overlay
*/
openTaskDetailsOverlay(taskId: string) {
if (this.menuOpen) {
this.toggleTaskMenu();
openTaskDetailsOverlay(taskId: string): void {
if (this.mobileMenuOpen) {
this.toggleMobileMenu();
}
this.pageViewMedia$.pipe(take(1)).subscribe((isPageViewMedia) => {
isPageViewMedia
@ -113,33 +111,13 @@ export class TaskComponent {
});
}
@HostListener('document:click', ['$event'])
/**
* If the target element of the event is not a task menu button,
* task menu img, or the task menu itself, close the task menu
* @param event the MouseEvent
*/
checkToggleTaskMenu(event: MouseEvent) {
const targetElement = event.target as HTMLElement;
const menuSelectors = ['.menu-btn', '.menu-img', 'app-task-menu'];
const isMenuClicked = menuSelectors.some((selector) =>
targetElement.closest(selector)
);
if (!isMenuClicked) {
this.menuOpen = false;
}
}
// User Dialog
/**
* Opens the user dialog for the given user id at the position of the mouse click
* @param userId the id of the user
* @param event the MouseEvent that triggered the dialog
*/
openDialog(userId: any, event: MouseEvent) {
this.AssignedDialogId = userId;
openDialog(userId: string, event: MouseEvent): void {
this.assignedDialogId = userId;
this.updateDialogPosition(event);
}
@ -147,7 +125,7 @@ export class TaskComponent {
* Updates the position of the user dialog based on the MouseEvent
* @param event the MouseEvent that triggered the dialog
*/
updateDialogPosition(event: MouseEvent) {
updateDialogPosition(event: MouseEvent): void {
this.dialogX = event.clientX + this.DIALOG_OFFSET_X;
this.dialogY = event.clientY + this.DIALOG_OFFSET_Y;
}
@ -155,12 +133,10 @@ export class TaskComponent {
/**
* Closes the user dialog
*/
closeDialog() {
this.AssignedDialogId = '';
closeDialog(): void {
this.assignedDialogId = '';
}
// Subtasks
/**
* Returns the number of completed subtasks of the task
* @returns the number of completed subtasks
@ -178,26 +154,46 @@ export class TaskComponent {
(subtask) => subtask.status
).length;
return (completedSubtasksCount / this.task.subtasks.length) * 100;
return this.task.subtasks.length > 0
? (completedSubtasksCount / this.task.subtasks.length) * 100
: 0;
}
/**
* 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) {
onStatusUpdate(event: TaskMoveEvent): void {
this.updateStatusEmitter.emit({
task: event.task,
moveTo: event.moveTo,
});
}
@HostListener('document:click', ['$event'])
/**
* If the target element of the event is not a task menu button,
* task menu img, or the task menu itself, close the task menu
* @param event the MouseEvent
*/
checkToggleTaskMenu(event: MouseEvent): void {
const targetElement = event.target as HTMLElement;
const menuSelectors = ['.menu-btn', '.menu-img', 'app-task-menu'];
const isMenuClicked = menuSelectors.some((selector) =>
targetElement.closest(selector)
);
if (!isMenuClicked) {
this.mobileMenuOpen = false;
}
}
@HostListener('window:resize')
/**
* Called when the window is resized.
* Updates the disableDragStatus so that tasks are not draggable on mobile devices.
*/
onResize() {
onResize(): void {
this.updateDisableDragStatus();
}
}