refactor: optimize and add comments to TaskComponent
This commit is contained in:
parent
b75fb18e42
commit
c02cf4d7a3
3 changed files with 182 additions and 137 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
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;
|
||||
}
|
||||
|
||||
.subtask-text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.footer {
|
||||
// Assignees
|
||||
|
||||
.assignees {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 24px;
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-badge {
|
||||
.assigned {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 170px;
|
||||
height: 45px;
|
||||
gap: 6px;
|
||||
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;
|
||||
}
|
||||
|
||||
.footer-badge span {
|
||||
margin-right: 4px;
|
||||
& span {
|
||||
color: var(--white);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-shadow: 1px 1px 2px var(--black);
|
||||
}
|
||||
|
||||
.footer-badge span:first-child {
|
||||
margin-left: 0 !important;
|
||||
&:hover {
|
||||
transform: translateY(-1px) scale(1.02);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-priority {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue