refactor: extracted assigned logic from AddTaskComponent to AssignedComponent and migrated to Django Rest
This commit is contained in:
parent
3293def254
commit
ff5abfa41a
10 changed files with 500 additions and 415 deletions
|
|
@ -5,14 +5,14 @@
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="headline"
|
class="header"
|
||||||
[ngStyle]="{ display: overlayData !== '' ? 'none' : 'block' }"
|
[ngStyle]="{ display: overlayData !== '' ? 'none' : 'block' }"
|
||||||
>
|
>
|
||||||
<div class="title">{{ "addTask.headline" | translate }}</div>
|
<div class="title">{{ "addTask.headline" | translate }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
(ngSubmit)="onSubmit(taskForm, overlayData)"
|
(ngSubmit)="onSubmit(taskForm)"
|
||||||
(keydown.enter)="handleEnterKey($event)"
|
(keydown.enter)="handleEnterKey($event)"
|
||||||
#taskForm="ngForm"
|
#taskForm="ngForm"
|
||||||
onsubmit="return false"
|
onsubmit="return false"
|
||||||
|
|
@ -26,7 +26,9 @@
|
||||||
>
|
>
|
||||||
<div class="left-side">
|
<div class="left-side">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<p>{{ "addTask.title" | translate }}<span class="red-dot">*</span></p>
|
<div class="headline">
|
||||||
|
{{ "addTask.title" | translate }}<span class="red-dot">*</span>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="title"
|
id="title"
|
||||||
|
|
@ -49,10 +51,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<p>
|
<div class="headline">
|
||||||
{{ "addTask.description" | translate
|
{{ "addTask.description" | translate
|
||||||
}}<span class="red-dot">*</span>
|
}}<span class="red-dot">*</span>
|
||||||
</p>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
id="description"
|
id="description"
|
||||||
rows="5"
|
rows="5"
|
||||||
|
|
@ -75,62 +77,11 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="assigned">
|
|
||||||
<p>{{ "addTask.assigned" | translate }}</p>
|
|
||||||
<input
|
|
||||||
#searchField
|
|
||||||
id="search-assigned"
|
|
||||||
class="search-assigned"
|
|
||||||
name="search"
|
|
||||||
placeholder="{{ 'addTask.assignedFormField' | translate }}"
|
|
||||||
type="text"
|
|
||||||
[(ngModel)]="searchValue"
|
|
||||||
(input)="searchTask(taskData.creator)"
|
|
||||||
autocomplete="off"
|
|
||||||
(click)="toggleAssignedMenu()"
|
|
||||||
/>
|
|
||||||
@if (isAssignedOpen) {
|
|
||||||
<app-assigned
|
<app-assigned
|
||||||
[filteredUsers]="filteredUsers"
|
|
||||||
[taskCreator]="taskData.creator"
|
[taskCreator]="taskData.creator"
|
||||||
[searchInput]="searchInput"
|
[assignedList]="taskData.assigned"
|
||||||
(assignedChange)="receiveAssigned($event)"
|
(assignedChange)="receiveAssigned($event)"
|
||||||
>
|
></app-assigned>
|
||||||
></app-assigned
|
|
||||||
>
|
|
||||||
<img class="close" src="./../../../assets/img/close.svg" alt="" />
|
|
||||||
} @else {
|
|
||||||
<img
|
|
||||||
class="open"
|
|
||||||
src="./../../../assets/img/add-task/arrow-down.svg"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="assigned-badge">
|
|
||||||
@for (user of taskData.assigned; track user ) {
|
|
||||||
<div
|
|
||||||
class="circle"
|
|
||||||
(mousemove)="openDialog(user, $event)"
|
|
||||||
(mouseleave)="closeDialog()"
|
|
||||||
[ngStyle]="{
|
|
||||||
'background-color': firebaseService.getUserDetails(user, 'color')
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="initials">
|
|
||||||
{{ firebaseService.getUserDetails(user, "initials") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@if (AssignedDialogId != '') {
|
|
||||||
<div class="dialog" [style.left.px]="dialogX" [style.top.px]="dialogY">
|
|
||||||
<p>
|
|
||||||
{{ firebaseService.getUserDetails(AssignedDialogId, "firstName") }}
|
|
||||||
{{ firebaseService.getUserDetails(AssignedDialogId, "lastName") }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="middle-spacer"><div class="line"></div></div>
|
<div class="middle-spacer"><div class="line"></div></div>
|
||||||
<div class="right-side">
|
<div class="right-side">
|
||||||
|
|
@ -140,7 +91,9 @@
|
||||||
'margin-top': overlayMobile ? '28px' : ''
|
'margin-top': overlayMobile ? '28px' : ''
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<p>{{ "addTask.date" | translate }}<span class="red-dot">*</span></p>
|
<div class="headline">
|
||||||
|
{{ "addTask.date" | translate }}<span class="red-dot">*</span>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
id="date"
|
id="date"
|
||||||
|
|
@ -161,7 +114,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="priority">
|
<div class="priority">
|
||||||
<p>{{ "addTask.priority" | translate }}</p>
|
<div class="headline">{{ "addTask.priority" | translate }}</div>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -217,9 +170,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="category">
|
<div class="category">
|
||||||
<p>
|
<div class="headline">
|
||||||
{{ "addTask.category" | translate }}<span class="red-dot">*</span>
|
{{ "addTask.category" | translate }}<span class="red-dot">*</span>
|
||||||
</p>
|
</div>
|
||||||
<select
|
<select
|
||||||
id="category"
|
id="category"
|
||||||
name="category"
|
name="category"
|
||||||
|
|
@ -246,7 +199,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="subtask">
|
<div class="subtask">
|
||||||
<p>{{ "addTask.subtask" | translate }}</p>
|
<div class="headline">{{ "addTask.subtask" | translate }}</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="subtask"
|
id="subtask"
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,6 @@ section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog {
|
|
||||||
position: fixed;
|
|
||||||
width: min-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 {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-task-overlay-content {
|
.edit-task-overlay-content {
|
||||||
height: calc(700px - 148px);
|
height: calc(700px - 148px);
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
|
|
@ -36,6 +21,12 @@ section {
|
||||||
background-color: var(--very-light-gray);
|
background-color: var(--very-light-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
select {
|
||||||
|
|
@ -65,12 +56,6 @@ select:valid {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 400;
|
|
||||||
padding-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.red-dot {
|
.red-dot {
|
||||||
color: var(--light-red);
|
color: var(--light-red);
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +76,7 @@ p {
|
||||||
|
|
||||||
/*------------- HEADER -------------*/
|
/*------------- HEADER -------------*/
|
||||||
|
|
||||||
.headline {
|
.header {
|
||||||
margin-bottom: 48px;
|
margin-bottom: 48px;
|
||||||
.title {
|
.title {
|
||||||
font-size: 61px;
|
font-size: 61px;
|
||||||
|
|
@ -105,55 +90,6 @@ p {
|
||||||
width: 45%;
|
width: 45%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assigned {
|
|
||||||
position: relative;
|
|
||||||
.open {
|
|
||||||
position: absolute;
|
|
||||||
top: 55px;
|
|
||||||
right: 20px;
|
|
||||||
width: 14px;
|
|
||||||
height: 7px;
|
|
||||||
}
|
|
||||||
.close {
|
|
||||||
position: absolute;
|
|
||||||
top: 50px;
|
|
||||||
right: 20px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
filter: invert(60%) sepia(58%) saturate(1629%) hue-rotate(165deg)
|
|
||||||
brightness(92%) contrast(92%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.assigned-badge {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin: 6px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 30px;
|
|
||||||
min-width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 100%;
|
|
||||||
border: 2px solid var(--white);
|
|
||||||
margin-right: 6px;
|
|
||||||
cursor: default;
|
|
||||||
.initials {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--white);
|
|
||||||
text-shadow: 1px 1px 2px var(--black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtask {
|
.subtask {
|
||||||
position: relative;
|
position: relative;
|
||||||
.add {
|
.add {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { CommonModule } from '@angular/common';
|
||||||
import { Component, HostListener, Input, OnInit } from '@angular/core';
|
import { Component, HostListener, 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 { User } from '../../interfaces/user.interface';
|
|
||||||
import { FirebaseService } from '../../services/firebase.service';
|
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';
|
||||||
|
|
@ -36,14 +35,7 @@ export class AddTaskComponent implements OnInit {
|
||||||
|
|
||||||
currentDate: string = new Date().toISOString().split('T')[0];
|
currentDate: string = new Date().toISOString().split('T')[0];
|
||||||
dateInPast: boolean = false;
|
dateInPast: boolean = false;
|
||||||
isAssignedOpen: boolean = false;
|
|
||||||
subtaskValue: string = '';
|
subtaskValue: string = '';
|
||||||
searchValue: string = '';
|
|
||||||
searchInput: boolean = false;
|
|
||||||
filteredUsers: User[] = [];
|
|
||||||
AssignedDialogId: string = '';
|
|
||||||
dialogX: number = 0;
|
|
||||||
dialogY: number = 0;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public firebaseService: FirebaseService,
|
public firebaseService: FirebaseService,
|
||||||
|
|
@ -142,32 +134,6 @@ export class AddTaskComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
this.updateDialogPosition(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the position of the user dialog based on the MouseEvent
|
|
||||||
* @param event the MouseEvent that triggered the dialog
|
|
||||||
*/
|
|
||||||
updateDialogPosition(event: MouseEvent) {
|
|
||||||
this.dialogX = event.clientX + 25;
|
|
||||||
this.dialogY = event.clientY + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the assigned user dialog
|
|
||||||
*/
|
|
||||||
closeDialog() {
|
|
||||||
this.AssignedDialogId = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the task data from the Firebase service for the given task id.
|
* Gets the task data from the Firebase service for the given task id.
|
||||||
* @param taskId the id of the task
|
* @param taskId the id of the task
|
||||||
|
|
@ -200,40 +166,6 @@ export class AddTaskComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the filteredUsers array with users that match the current search value
|
|
||||||
* from the getFilteredUsers list.
|
|
||||||
* @param taskCreator the id of the task creator
|
|
||||||
*/
|
|
||||||
searchTask(taskCreator: string): void {
|
|
||||||
this.updateSearchInput();
|
|
||||||
this.filteredUsers = this.firebaseService
|
|
||||||
.getFilteredUsers(taskCreator)
|
|
||||||
.filter(
|
|
||||||
(user) =>
|
|
||||||
user.firstName.toLowerCase().includes(this.searchValue) ||
|
|
||||||
user.lastName.toLowerCase().includes(this.searchValue) ||
|
|
||||||
user.initials.toLowerCase().includes(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.
|
|
||||||
*/
|
|
||||||
updateSearchInput() {
|
|
||||||
this.searchValue = this.replaceXSSChars(this.searchValue);
|
|
||||||
if (this.searchValue) {
|
|
||||||
this.searchInput = this.searchValue.toLowerCase().length > 0;
|
|
||||||
} else {
|
|
||||||
this.searchInput = false;
|
|
||||||
}
|
|
||||||
return this.searchInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the subtask value by removing any potential XSS characters.
|
* Updates the subtask value by removing any potential XSS characters.
|
||||||
* This ensures that the subtask value is sanitized before further processing or storage.
|
* This ensures that the subtask value is sanitized before further processing or storage.
|
||||||
|
|
@ -251,15 +183,6 @@ export class AddTaskComponent implements OnInit {
|
||||||
this.taskData.assigned = assigned;
|
this.taskData.assigned = assigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the assigned menu for the current task.
|
|
||||||
* This function toggles the boolean flag isAssignedOpen, which determines whether the assigned menu is open or not.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
toggleAssignedMenu() {
|
|
||||||
this.isAssignedOpen = !this.isAssignedOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the selected date is in the past.
|
* Checks if the selected date is in the past.
|
||||||
*
|
*
|
||||||
|
|
@ -373,7 +296,7 @@ export class AddTaskComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(ngForm: NgForm, overlayData: string) {
|
onSubmit(ngForm: NgForm) {
|
||||||
if (ngForm.submitted && ngForm.form.valid) {
|
if (ngForm.submitted && ngForm.form.valid) {
|
||||||
const { id, ...taskWithoutId } = this.taskData;
|
const { id, ...taskWithoutId } = this.taskData;
|
||||||
|
|
||||||
|
|
@ -412,28 +335,9 @@ export class AddTaskComponent implements OnInit {
|
||||||
*/
|
*/
|
||||||
deleteTask(overlayData: string) {
|
deleteTask(overlayData: string) {
|
||||||
this.apiService.deleteTaskById(overlayData);
|
this.apiService.deleteTaskById(overlayData);
|
||||||
this.closeDialog();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceXSSChars(input: string) {
|
replaceXSSChars(input: string) {
|
||||||
return input.replace(/</g, '<').replace(/>/g, '>');
|
return input.replace(/</g, '<').replace(/>/g, '>');
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:click', ['$event'])
|
|
||||||
/**
|
|
||||||
* Closes the assigned dropdown menu if the user clicks anywhere outside of
|
|
||||||
* the assigned dropdown menu.
|
|
||||||
* @param event the MouseEvent
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
checkOpenNavbar(event: MouseEvent) {
|
|
||||||
const targetElement = event.target as HTMLElement;
|
|
||||||
if (
|
|
||||||
!targetElement.closest('.search-assigned') &&
|
|
||||||
!targetElement.closest('app-assigned') &&
|
|
||||||
!targetElement.closest('.checkbox-img')
|
|
||||||
) {
|
|
||||||
this.isAssignedOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<section>
|
||||||
|
@if (filteredUsers.length === 0) {
|
||||||
|
<div class="content"><p class="no-users">No users found</p></div>
|
||||||
|
} @else { @for (user of filteredUsers; track user.id) {
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
(click)="addAssignedToTask(user.id)"
|
||||||
|
[ngClass]="{ selected: assigned.includes(user.id) }"
|
||||||
|
*ngIf="user.id"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="circle"
|
||||||
|
[ngStyle]="{
|
||||||
|
'background-color': user.color
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="initials">
|
||||||
|
{{ user.initials }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="details">
|
||||||
|
<div class="name">
|
||||||
|
<p>
|
||||||
|
{{ user.firstName }}
|
||||||
|
</p>
|
||||||
|
<span>, </span>
|
||||||
|
<p class="last-name">
|
||||||
|
{{ user.lastName }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
@if (assigned.includes(user.id)) {
|
||||||
|
<img
|
||||||
|
class="checkbox-img"
|
||||||
|
src="./../../../../assets/img/add-task/checkbox-checked.svg"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
} @else {
|
||||||
|
<img
|
||||||
|
class="checkbox-img"
|
||||||
|
src="./../../../../assets/img/add-task/checkbox-empty.svg"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} }
|
||||||
|
</section>
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
section {
|
||||||
|
position: absolute;
|
||||||
|
top: 75px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 125px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-radius: 0px 0px 20px 20px;
|
||||||
|
border: 1px solid var(--light-gray);
|
||||||
|
border-top: 0px;
|
||||||
|
background-color: var(--white);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--light-gray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 35px;
|
||||||
|
min-width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 100%;
|
||||||
|
border: 2px solid var(--white);
|
||||||
|
.initials {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--white);
|
||||||
|
text-shadow: 1px 1px 2px var(--black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-users {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
margin-left: 24px;
|
||||||
|
width: 80%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
img {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background-color: var(--dark-blue);
|
||||||
|
color: var(--white);
|
||||||
|
img {
|
||||||
|
filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(7500%)
|
||||||
|
hue-rotate(346deg) brightness(99%) contrast(103%);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--very-dark-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AssignedListComponent } from './assigned-list.component';
|
||||||
|
|
||||||
|
describe('AssignedListComponent', () => {
|
||||||
|
let component: AssignedListComponent;
|
||||||
|
let fixture: ComponentFixture<AssignedListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AssignedListComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AssignedListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { FirebaseService } from '../../../../services/firebase.service';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { User } from '../../../../interfaces/user.interface';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-assigned-list',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
templateUrl: './assigned-list.component.html',
|
||||||
|
styleUrl: './assigned-list.component.scss',
|
||||||
|
})
|
||||||
|
export class AssignedListComponent {
|
||||||
|
@Input() filteredUsers: User[] = [];
|
||||||
|
@Input() searchInput: boolean = false;
|
||||||
|
@Input() taskCreator: string = '';
|
||||||
|
@Output() assignedChange = new EventEmitter<string[]>();
|
||||||
|
|
||||||
|
users: User[] = [];
|
||||||
|
assigned: string[] = [];
|
||||||
|
currentUser: User | null = null;
|
||||||
|
|
||||||
|
constructor(public firebaseService: FirebaseService) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loadTaskAssignedData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the assignedChange event emitter with the current list of assigned users.
|
||||||
|
* This should be called whenever the assigned list is updated.
|
||||||
|
*/
|
||||||
|
updateAssigned() {
|
||||||
|
this.assignedChange.emit(this.assigned);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the assignment of a user to the task.
|
||||||
|
*
|
||||||
|
* If the user is not currently assigned, they are added to the assigned list.
|
||||||
|
* If the user is already assigned, they are removed from the list.
|
||||||
|
* Updates the local storage with the current list of assigned users and emits the
|
||||||
|
* assignedChange event to notify other components of the update.
|
||||||
|
*
|
||||||
|
* @param userId The ID of the user to be added or removed from the assigned list.
|
||||||
|
*/
|
||||||
|
addAssignedToTask(userId: string) {
|
||||||
|
if (!this.assigned.includes(userId)) {
|
||||||
|
this.assigned.push(userId);
|
||||||
|
} else {
|
||||||
|
this.assigned.splice(this.assigned.indexOf(userId), 1);
|
||||||
|
}
|
||||||
|
this.saveTaskData();
|
||||||
|
this.updateAssigned();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current list of assigned users to local storage.
|
||||||
|
*
|
||||||
|
* Retrieves the current task data from local storage, updates the assigned
|
||||||
|
* list with the current list of assigned users, and saves the updated task
|
||||||
|
* data back to local storage.
|
||||||
|
*/
|
||||||
|
saveTaskData() {
|
||||||
|
let taskDataString = localStorage.getItem('taskData');
|
||||||
|
if (taskDataString !== null) {
|
||||||
|
let taskData = JSON.parse(taskDataString);
|
||||||
|
taskData.assigned = this.assigned;
|
||||||
|
localStorage.setItem('taskData', JSON.stringify(taskData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of assigned users from local storage.
|
||||||
|
*
|
||||||
|
* Retrieves the task data from local storage, checks if the assigned list exists,
|
||||||
|
* and if so, assigns the list to the local assigned variable.
|
||||||
|
*/
|
||||||
|
loadTaskAssignedData() {
|
||||||
|
const taskDataString = localStorage.getItem('taskData');
|
||||||
|
if (taskDataString !== null) {
|
||||||
|
const taskData = JSON.parse(taskDataString);
|
||||||
|
if (taskData.hasOwnProperty('assigned')) {
|
||||||
|
this.assigned = taskData.assigned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,47 +1,65 @@
|
||||||
<section>
|
<div class="assigned">
|
||||||
@for (user of displayAssigned(); track user; let index = $index) {
|
<div class="headline">{{ "addTask.assigned" | translate }}</div>
|
||||||
<div
|
<input
|
||||||
class="content"
|
#searchField
|
||||||
(click)="addAssignedToTask(user.id)"
|
id="search-assigned"
|
||||||
[ngClass]="{ selected: assigned.includes(user.id) }"
|
class="search-assigned"
|
||||||
*ngIf="user.id"
|
name="search"
|
||||||
|
placeholder="{{ 'addTask.assignedFormField' | translate }}"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="searchValue"
|
||||||
|
(input)="searchTask()"
|
||||||
|
autocomplete="off"
|
||||||
|
(click)="toggleAssignedMenu()"
|
||||||
|
/>
|
||||||
|
@if (showAssignedList) {
|
||||||
|
<app-assigned-list
|
||||||
|
[filteredUsers]="filteredUsers"
|
||||||
|
[taskCreator]="taskCreator"
|
||||||
|
[searchInput]="searchInput"
|
||||||
|
(assignedChange)="receiveAssigned($event)"
|
||||||
>
|
>
|
||||||
|
></app-assigned-list
|
||||||
|
>
|
||||||
|
<img class="close" src="./../../../assets/img/close.svg" alt="" />
|
||||||
|
} @else {
|
||||||
|
<img
|
||||||
|
class="open"
|
||||||
|
src="./../../../assets/img/add-task/arrow-down.svg"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="assigned-badge">
|
||||||
|
@for (user of users; track user; let index = $index) { @for (users of
|
||||||
|
assignedList; track users ) { @if (user.id === users) {
|
||||||
<div
|
<div
|
||||||
class="circle"
|
class="circle"
|
||||||
|
(mousemove)="openTooltipUser(user.id, $event)"
|
||||||
|
(mouseleave)="closeTooltipUser()"
|
||||||
[ngStyle]="{
|
[ngStyle]="{
|
||||||
'background-color': firebaseService.getUserDetails(user.id, 'color')
|
'background-color': user.color
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="initials">
|
<div class="initials">
|
||||||
{{ firebaseService.getUserDetails(user.id, "initials") }}
|
{{ user.initials }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="details">
|
} } } @if (tooltipUserId) {
|
||||||
<div class="name">
|
<div
|
||||||
|
class="tooltip-user"
|
||||||
|
[style.left.px]="dialogX"
|
||||||
|
[style.top.px]="dialogY"
|
||||||
|
>
|
||||||
|
@for (user of users; track user) { @if (user.id === tooltipUserId) {
|
||||||
<p>
|
<p>
|
||||||
{{ firebaseService.getUserDetails(user.id, "firstName") }}
|
{{ user.firstName }}
|
||||||
</p>
|
</p>
|
||||||
<span>, </span>
|
<p>
|
||||||
<p class="last-name">
|
{{ user.lastName }}
|
||||||
{{ firebaseService.getUserDetails(user.id, "lastName") }}
|
|
||||||
</p>
|
</p>
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="checkbox">
|
|
||||||
@if (assigned.includes(user.id)) {
|
|
||||||
<img
|
|
||||||
class="checkbox-img"
|
|
||||||
src="./../../../../assets/img/add-task/checkbox-checked.svg"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
} @else {
|
|
||||||
<img
|
|
||||||
class="checkbox-img"
|
|
||||||
src="./../../../../assets/img/add-task/checkbox-empty.svg"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</section>
|
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,101 @@
|
||||||
section {
|
.assigned {
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 75px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
max-height: 125px;
|
|
||||||
padding-top: 12px;
|
|
||||||
border-radius: 0px 0px 20px 20px;
|
|
||||||
border: 1px solid var(--light-gray);
|
|
||||||
border-top: 0px;
|
|
||||||
background-color: var(--white);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.open {
|
||||||
display: flex;
|
position: absolute;
|
||||||
align-items: center;
|
top: 55px;
|
||||||
padding: 6px 12px;
|
right: 20px;
|
||||||
|
width: 14px;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
right: 20px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--light-gray);
|
filter: invert(60%) sepia(58%) saturate(1629%) hue-rotate(165deg)
|
||||||
|
brightness(92%) contrast(92%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.circle {
|
.assigned-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 6px 6px 12px 6px;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
.circle {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 35px;
|
width: 30px;
|
||||||
min-width: 35px;
|
min-width: 30px;
|
||||||
height: 35px;
|
height: 30px;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
border: 2px solid var(--white);
|
border: 2px solid var(--white);
|
||||||
|
margin-right: 6px;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
.initials {
|
.initials {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
text-shadow: 1px 1px 2px var(--black);
|
text-shadow: 1px 1px 2px var(--black);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.tooltip-user {
|
||||||
margin-left: 24px;
|
position: fixed;
|
||||||
width: 80%;
|
width: min-content;
|
||||||
overflow: hidden;
|
height: fit-content;
|
||||||
white-space: nowrap;
|
padding: 10px 15px;
|
||||||
display: inline-block;
|
border-radius: 0px 20px 20px 20px;
|
||||||
}
|
border: 1px solid var(--black);
|
||||||
|
z-index: 2;
|
||||||
.name {
|
background-color: var(--white);
|
||||||
display: flex;
|
p {
|
||||||
align-items: center;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
|
||||||
|
|
||||||
.last-name {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox {
|
|
||||||
img {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.headline {
|
||||||
background-color: var(--dark-blue);
|
font-size: 20px;
|
||||||
color: var(--white);
|
font-weight: 400;
|
||||||
img {
|
padding-bottom: 6px;
|
||||||
filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(7500%)
|
}
|
||||||
hue-rotate(346deg) brightness(99%) contrast(103%);
|
|
||||||
}
|
input,
|
||||||
&:hover {
|
textarea,
|
||||||
background-color: var(--very-dark-blue);
|
select {
|
||||||
}
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--light-gray);
|
||||||
|
padding: 12px 21px;
|
||||||
|
width: calc(100% - 42px);
|
||||||
|
color: var(--black);
|
||||||
|
background-color: var(--white);
|
||||||
|
resize: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.ng-invalid.ng-touched,
|
||||||
|
textarea.ng-invalid.ng-touched,
|
||||||
|
select.ng-invalid.ng-touched {
|
||||||
|
border: 1px dashed red;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:valid,
|
||||||
|
textarea:valid,
|
||||||
|
select:valid {
|
||||||
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,101 +1,109 @@
|
||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import {
|
||||||
import { FirebaseService } from '../../../services/firebase.service';
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
HostListener,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { AssignedListComponent } from './assigned-list/assigned-list.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
import { User } from '../../../interfaces/user.interface';
|
import { User } from '../../../interfaces/user.interface';
|
||||||
|
import { ApiService } from '../../../services/api.service';
|
||||||
|
import { map, catchError, of } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-assigned',
|
selector: 'app-assigned',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule],
|
imports: [CommonModule, FormsModule, TranslateModule, AssignedListComponent],
|
||||||
templateUrl: './assigned.component.html',
|
templateUrl: './assigned.component.html',
|
||||||
styleUrl: './assigned.component.scss',
|
styleUrl: './assigned.component.scss',
|
||||||
})
|
})
|
||||||
export class AssignedComponent {
|
export class AssignedComponent {
|
||||||
@Input() filteredUsers: User[] = [];
|
|
||||||
@Input() searchInput: boolean = false;
|
|
||||||
@Input() taskCreator: string = '';
|
@Input() taskCreator: string = '';
|
||||||
|
@Input() assignedList: string[] = [];
|
||||||
@Output() assignedChange = new EventEmitter<string[]>();
|
@Output() assignedChange = new EventEmitter<string[]>();
|
||||||
|
|
||||||
assigned: string[] = [];
|
tooltipUserId: string | null = null;
|
||||||
|
filteredUsers: User[] = [];
|
||||||
|
users: User[] = [];
|
||||||
|
searchValue: string = '';
|
||||||
|
searchInput: boolean = false;
|
||||||
|
showAssignedList: boolean = false;
|
||||||
|
dialogX: number = 0;
|
||||||
|
dialogY: number = 0;
|
||||||
|
|
||||||
constructor(public firebaseService: FirebaseService) {
|
constructor(private apiService: ApiService) {}
|
||||||
this.loadTaskAssignedData();
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loadUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
loadUsers(): void {
|
||||||
* Updates the assignedChange event emitter with the current list of assigned users.
|
this.apiService
|
||||||
* This should be called whenever the assigned list is updated.
|
.getUsers()
|
||||||
*/
|
.pipe(
|
||||||
updateAssigned() {
|
map((users) => users.filter((user) => user.id !== this.taskCreator)),
|
||||||
this.assignedChange.emit(this.assigned);
|
catchError(() => of([]))
|
||||||
|
)
|
||||||
|
.subscribe((filteredUsers) => {
|
||||||
|
this.users = filteredUsers;
|
||||||
|
this.filteredUsers = filteredUsers;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
searchTask(): void {
|
||||||
* Toggles the assignment of a user to the task.
|
this.searchValue = this.replaceXSSChars(this.searchValue) || '';
|
||||||
*
|
this.searchInput = this.searchValue.trim().length > 0;
|
||||||
* If the user is not currently assigned, they are added to the assigned list.
|
|
||||||
* If the user is already assigned, they are removed from the list.
|
|
||||||
* Updates the local storage with the current list of assigned users and emits the
|
|
||||||
* assignedChange event to notify other components of the update.
|
|
||||||
*
|
|
||||||
* @param userId The ID of the user to be added or removed from the assigned list.
|
|
||||||
*/
|
|
||||||
addAssignedToTask(userId: string) {
|
|
||||||
if (!this.assigned.includes(userId)) {
|
|
||||||
this.assigned.push(userId);
|
|
||||||
} else {
|
|
||||||
this.assigned.splice(this.assigned.indexOf(userId), 1);
|
|
||||||
}
|
|
||||||
this.saveTaskData();
|
|
||||||
this.updateAssigned();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const searchValue = this.searchValue.toLowerCase();
|
||||||
* Saves the current list of assigned users to local storage.
|
this.filteredUsers = this.users.filter(
|
||||||
*
|
(user) =>
|
||||||
* Retrieves the current task data from local storage, updates the assigned
|
user.firstName.toLowerCase().includes(searchValue) ||
|
||||||
* list with the current list of assigned users, and saves the updated task
|
user.lastName.toLowerCase().includes(searchValue) ||
|
||||||
* data back to local storage.
|
user.initials.toLowerCase().includes(searchValue)
|
||||||
*/
|
);
|
||||||
saveTaskData() {
|
if (this.filteredUsers.length === 0) {
|
||||||
let taskDataString = localStorage.getItem('taskData');
|
this.filteredUsers = [];
|
||||||
if (taskDataString !== null) {
|
|
||||||
let taskData = JSON.parse(taskDataString);
|
|
||||||
taskData.assigned = this.assigned;
|
|
||||||
localStorage.setItem('taskData', JSON.stringify(taskData));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
replaceXSSChars(input: string) {
|
||||||
* Loads the list of assigned users from local storage.
|
return input.replace(/</g, '<').replace(/>/g, '>');
|
||||||
*
|
|
||||||
* Retrieves the task data from local storage, checks if the assigned list exists,
|
|
||||||
* and if so, assigns the list to the local assigned variable.
|
|
||||||
*/
|
|
||||||
loadTaskAssignedData() {
|
|
||||||
const taskDataString = localStorage.getItem('taskData');
|
|
||||||
if (taskDataString !== null) {
|
|
||||||
const taskData = JSON.parse(taskDataString);
|
|
||||||
if (taskData.hasOwnProperty('assigned')) {
|
|
||||||
this.assigned = taskData.assigned;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
openTooltipUser(userId: any, event: MouseEvent) {
|
||||||
* Returns the list of users that should be displayed in the assigned component.
|
this.tooltipUserId = userId;
|
||||||
*
|
this.updateTooltipUserPosition(event);
|
||||||
* If the search input is active (i.e., the user is typing in the search box),
|
}
|
||||||
* the list of filtered users is returned. Otherwise, the list of users that are
|
|
||||||
* not the task creator is returned.
|
updateTooltipUserPosition(event: MouseEvent) {
|
||||||
*
|
this.dialogX = event.clientX + 25;
|
||||||
* @return The list of users to be displayed in the assigned component.
|
this.dialogY = event.clientY + 10;
|
||||||
*/
|
}
|
||||||
displayAssigned() {
|
|
||||||
if (this.searchInput) {
|
closeTooltipUser() {
|
||||||
return this.filteredUsers;
|
this.tooltipUserId = null;
|
||||||
} else {
|
}
|
||||||
return this.firebaseService.getFilteredUsers(this.taskCreator);
|
|
||||||
|
receiveAssigned(assigned: string[]) {
|
||||||
|
this.assignedChange.emit(assigned);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAssignedMenu() {
|
||||||
|
this.showAssignedList = !this.showAssignedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:click', ['$event'])
|
||||||
|
checkOpenNavbar(event: MouseEvent) {
|
||||||
|
const targetElement = event.target as HTMLElement;
|
||||||
|
if (
|
||||||
|
!targetElement.closest('.search-assigned') &&
|
||||||
|
!targetElement.closest('app-assigned-list') &&
|
||||||
|
!targetElement.closest('.checkbox-img')
|
||||||
|
) {
|
||||||
|
this.showAssignedList = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue