1- < div class ="grid ">
2- < div class ="col-12 ">
3- < p-card styleClass ="project-tasks-card ">
1+ < p-toolbar styleClass ="mb-4 border-round border-1 surface-border " [style] ="{'padding': '0.75rem'} ">
2+ < ng-template pTemplate ="left ">
3+ < div class ="flex flex-wrap gap-2 align-items-center ">
4+ < p-dropdown id ="projectSelect " [options] ="projects " [(ngModel)] ="selectedProject " optionLabel ="name "
5+ placeholder ="Select Project " [showClear] ="false " (onChange) ="onProjectChange() " [disabled] ="isLoadingProjects "
6+ styleClass ="p-dropdown-sm " [style] ="{ minWidth: '180px' } ">
7+ </ p-dropdown >
8+ </ div >
9+ </ ng-template >
410
5- < p-toolbar styleClass ="mb-6 px-3 py-2 border-round border-1 surface-border crud-toolbar ">
6- < ng-template pTemplate ="left ">
7- < div class ="flex flex-wrap gap-2 align-items-center ">
8- < button pButton pRipple icon ="pi pi-refresh " class ="p-button-outlined p-button-sm " (click) ="refreshTasks() "
9- pTooltip ="Refresh Task List " tooltipPosition ="top "
10- [disabled] ="isLoadingTasks || !selectedProject "> </ button >
11- </ div >
12- </ ng-template >
13-
14- < ng-template pTemplate ="right ">
15- < div class ="flex flex-wrap gap-3 align-items-center ">
16- < div class ="flex align-items-center ">
17- < p-dropdown id ="projectSelect " [options] ="projects " [(ngModel)] ="selectedProject " optionLabel ="name "
18- placeholder ="Select Project " [showClear] ="false " (onChange) ="onProjectChange() "
19- [disabled] ="isLoadingProjects " styleClass ="p-dropdown-sm " [style] ="{ minWidth: '180px' } ">
20- </ p-dropdown >
21- < p-progressSpinner *ngIf ="isLoadingProjects " styleClass ="w-1.5rem h-1.5rem ml-2 "
22- strokeWidth ="6 "> </ p-progressSpinner >
23- </ div >
24- < button pButton pRipple label ="Export " icon ="pi pi-upload "
25- class ="p-button-outlined p-button-secondary p-button-sm " tooltipPosition ="top "
26- pTooltip ="Feature not implemented "> </ button >
27- </ div >
28- </ ng-template >
29- </ p-toolbar >
30-
31- < div class ="flex flex-wrap align-items-center justify-content-between gap-3 mb-3 px-2 ">
32- < h4 class ="m-0 "> {{ selectedProject ? selectedProject.name + ' Tasks' : 'Tasks' }}</ h4 >
33- < span class ="p-input-icon-left ">
34- < i class ="pi pi-search "> </ i >
35- < input pInputText type ="text " #gbFilter (input) ="applyFilterGlobal($event) " placeholder ="Search... "
36- [value] ="globalFilterValue " class ="w-full sm:w-auto p-inputtext-sm " />
37- </ span >
11+ < ng-template pTemplate ="right ">
12+ < div class ="flex flex-wrap gap-3 align-items-center ">
13+ < div class ="flex align-items-center ">
14+ < p-progressSpinner *ngIf ="isLoadingProjects " styleClass ="w-1.5rem h-1.5rem ml-2 "
15+ strokeWidth ="6 "> </ p-progressSpinner >
3816 </ div >
17+ < button pButton pRipple label ="Export " icon ="pi pi-upload "
18+ class ="p-button-outlined p-button-secondary p-button-sm "
19+ (onClick) ="exportCSV(dt) "> </ button >
20+ </ div >
21+ </ ng-template >
22+ </ p-toolbar >
3923
40- < p-table #dt [value] ="tasks " dataKey ="id " [loading] ="isLoadingTasks " [rows] ="10 " [paginator] ="true "
41- [globalFilterFields] ="['title', 'description', 'assignedUserName', 'statusName'] "
42- currentPageReportTemplate ="Showing {first} to {last} of {totalRecords} tasks " [showCurrentPageReport] ="true "
43- paginatorPosition ="bottom " [rowsPerPageOptions] ="[10, 25, 50] " [tableStyle] ="{ 'min-width': '70rem' } ">
44-
45- < ng-template pTemplate ="header ">
46- < tr >
47- < th pSortableColumn ="title " style ="min-width:15rem; background-color: transparent; "> Title < p-sortIcon
48- field ="title "> </ p-sortIcon > </ th >
49- < th pSortableColumn ="status " style ="min-width:10rem; background-color: transparent; "> Status < p-sortIcon
50- field ="status "> </ p-sortIcon > </ th >
51- < th pSortableColumn ="createdAt " style ="min-width:8rem; background-color: transparent; "> Created At< p-sortIcon
52- field ="createdAt "> </ p-sortIcon > </ th >
53- < th pSortableColumn ="dueDate " style ="min-width:8rem; background-color: transparent; "> Due Date < p-sortIcon
54- field ="dueDate "> </ p-sortIcon > </ th >
55- < th pSortableColumn ="assignedUserName " style ="min-width:10rem; background-color: transparent; "> Assigned To
56- < p-sortIcon field ="assignedUserName "> </ p-sortIcon > </ th >
57- < th style ="width: 8rem; background-color: transparent; " class ="text-center "> Actions</ th >
58- </ tr >
59- </ ng-template >
60-
61- < ng-template pTemplate ="body " let-task let-rowIndex ="rowIndex ">
62- < tr >
63- < td >
64- < span class ="p-column-title "> Title</ span >
65- < span class ="font-medium block white-space-nowrap overflow-hidden text-overflow-ellipsis "
66- [pTooltip] ="task.title " tooltipPosition ="top "> {{ task.title }}</ span >
67- </ td >
68-
69- < td >
70- < span class ="p-column-title "> Status</ span >
71- < p-tag *ngIf ="!(showStatusDropdown && isAssignedToMe(task)) "
72- [value] ="getStatusName(task.status) " [severity] ="getTagSeverity(task.status) "
73- styleClass ="status-tag "
74- [pTooltip] ="isAssignedToMe(task) ? 'Click status to change' : (isUnassigned(task) ? 'Task is unassigned' : 'Assigned to ' + task.assignedUserName) "
75- tooltipPosition ="top " #tagElement (click) ="isAssignedToMe(task) ? showStatusDropdown = !showStatusDropdown : null "
76- [ngClass] ="{'cursor-pointer hover:opacity-80': isAssignedToMe(task)} ">
77- </ p-tag >
78-
79- < p-dropdown *ngIf ="showStatusDropdown && isAssignedToMe(task) " [options] ="statusOptions " [(ngModel)] ="task.status " optionLabel ="label "
80- optionValue ="value " (onChange) ="onStatusChange(task); showStatusDropdown = !showStatusDropdown " appendTo ="body " [showClear] ="false ">
81- < ng-template pTemplate ="selectedItem ">
82- < p-tag [value] ="getStatusName(task.status) " [severity] ="getTagSeverity(task.status) "
83- styleClass ="status-tag-option "> </ p-tag >
84- </ ng-template >
24+ < p-table #dt [value] ="tasks " dataKey ="id " [loading] ="isLoadingTasks " [rows] ="10 " [paginator] ="true "
25+ [globalFilterFields] ="['title', 'description', 'assignedUserName', 'statusName'] "
26+ currentPageReportTemplate ="Showing {first} to {last} of {totalRecords} tasks " [showCurrentPageReport] ="true "
27+ paginatorPosition ="bottom " [rowsPerPageOptions] ="[10, 25, 50] " [tableStyle] ="{ 'min-width': '70rem' } ">
28+ < ng-template pTemplate ="caption ">
29+ < div class ="flex ">
30+ < p-button label ="Clear " [outlined] ="true " icon ="pi pi-filter-slash " (onClick) ="clear(dt) " />
31+ < span class ="p-input-icon-left ml-auto ">
32+ < i class ="pi pi-search "> </ i >
33+ < input pInputText type ="text " [(ngModel)] ="searchValue " (input) ="onGlobalFilter(dt, $event) "
34+ placeholder ="Search... " />
35+ </ span >
36+ </ div >
37+ </ ng-template >
38+ < ng-template pTemplate ="header ">
39+ < tr >
40+ < th style ="min-width:15rem ">
41+ < div class ="flex align-items-center ">
42+ Title
43+ < p-columnFilter type ="text " field ="title " display ="menu " />
44+ </ div >
45+ </ th >
46+ < th style ="min-width:10rem ">
47+ < div class ="flex align-items-center ">
48+ Status
49+ < p-columnFilter field ="status " matchMode ="equals " display ="menu ">
50+ < ng-template pTemplate ="filter " let-value let-filter ="filterCallback ">
51+ < p-dropdown [ngModel] ="value " [options] ="statusOptions " (onChange) ="filter($event.value) " placeholder ="Any ">
8552 < ng-template let-option pTemplate ="item ">
86- < p-tag [value] ="option.label " [severity] ="getTagSeverity(option.value) "
87- styleClass ="status-tag-option "> </ p-tag >
53+ < p-tag [value] ="option.label " [severity] ="getTagSeverity(option.value) " />
8854 </ ng-template >
8955 </ p-dropdown >
90- </ td >
56+ </ ng-template >
57+ </ p-columnFilter >
58+ </ div >
59+ </ th >
60+ < th style ="min-width:8rem ">
61+ < div class ="flex align-items-center ">
62+ Created At
63+ < p-columnFilter type ="date " field ="createdAt " display ="menu " />
64+ </ div >
65+ </ th >
66+ < th style ="min-width:8rem ">
67+ < div class ="flex align-items-center ">
68+ Due Date
69+ < p-columnFilter type ="date " field ="dueDate " display ="menu " />
70+ </ div >
71+ </ th >
72+ < th style ="min-width:10rem ">
73+ < div class ="flex align-items-center ">
74+ Assigned To
75+ < p-columnFilter field ="assignedUserName " matchMode ="in " display ="menu " [showMatchModes] ="false "
76+ [showOperator] ="false " [showAddButton] ="false ">
77+ < ng-template pTemplate ="header ">
78+ < div class ="px-3 pt-3 pb-0 ">
79+ < span class ="font-bold "> User Filter</ span >
80+ </ div >
81+ </ ng-template >
82+ < ng-template pTemplate ="filter " let-value let-filter ="filterCallback ">
83+ < p-multiSelect [ngModel] ="value " [options] ="userOptions " placeholder ="Any "
84+ (onChange) ="filter($event.value) " optionLabel ="name ">
85+ < ng-template let-option pTemplate ="item ">
86+ < div class ="inline-block vertical-align-middle ">
87+ < p-avatar [label] ="getInitials(option.name) " shape ="circle "
88+ [styleClass] ="'mr-2' "> </ p-avatar >
89+ < span class ="ml-1 mt-1 "> {{ option.name }}</ span >
90+ </ div >
91+ </ ng-template >
92+ </ p-multiSelect >
93+ </ ng-template >
94+ </ p-columnFilter >
95+ </ div >
96+ </ th >
97+ < th style ="width: 8rem " class ="text-center "> </ th >
98+ </ tr >
99+ </ ng-template >
91100
92- < td >
93- < span class ="p-column-title "> Created At</ span >
94- {{ task.createdAt ? (task.createdAt | date:'MMM d, y') : '-' }}
95- </ td >
101+ < ng-template pTemplate ="body " let-task let-rowIndex ="rowIndex ">
102+ < tr >
103+ < td >
104+ < span class ="p-column-title "> Title</ span >
105+ < span class ="font-medium block white-space-nowrap overflow-hidden text-overflow-ellipsis "
106+ [pTooltip] ="task.title " tooltipPosition ="top "> {{ task.title }}</ span >
107+ </ td >
96108
97- < td >
98- < span class ="p-column-title "> Due Date</ span >
99- {{ task.dueDate ? (task.dueDate | date:'MMM d, y') : '-' }}
100- </ td >
109+ < td >
110+ < span class ="p-column-title "> Status</ span >
111+ < p-tag *ngIf ="!(showStatusDropdown && isAssignedToMe(task)) " [value] ="getStatusName(task.status) "
112+ [severity] ="getTagSeverity(task.status) " styleClass ="status-tag "
113+ [pTooltip] ="isUnassigned(task) ? 'Task is unassigned' : 'Assigned to ' + task.assignedUserName "
114+ tooltipPosition ="top " #tagElement
115+ [ngClass] ="{'cursor-pointer hover:opacity-80': isAssignedToMe(task)} ">
116+ </ p-tag >
101117
102- < td >
103- < span class ="p-column-title "> Assigned To</ span >
104- < div *ngIf ="!isUnassigned(task) " class ="flex align-items-center gap-2 ">
105- < p-avatar [label] ="getInitials(task.assignedUserName) " shape ="circle " size ="normal "
106- [styleClass] ="'user-avatar ' + (isAssignedToMe(task) ? 'avatar-me' : 'avatar-other') "
107- [pTooltip] ="task.assignedUserName " tooltipPosition ="top ">
108- </ p-avatar >
109- < span class ="font-medium "> {{ task.assignedUserName }}</ span >
110- </ div >
111- < span *ngIf ="isUnassigned(task) " class ="text-color-secondary text-sm font-italic ">
112- Unassigned
113- </ span >
114- </ td >
118+ < p-dropdown *ngIf ="showStatusDropdown && isAssignedToMe(task) " [options] ="statusOptions "
119+ [(ngModel)] ="task.status " optionLabel ="label " optionValue ="value "
120+ (onChange) ="onStatusChange(task); showStatusDropdown = !showStatusDropdown " appendTo ="body "
121+ [showClear] ="false ">
122+ < ng-template pTemplate ="selectedItem ">
123+ < p-tag [value] ="getStatusName(task.status) " [severity] ="getTagSeverity(task.status) "
124+ styleClass ="status-tag-option "> </ p-tag >
125+ </ ng-template >
126+ < ng-template let-option pTemplate ="item ">
127+ < p-tag [value] ="option.label " [severity] ="getTagSeverity(option.value) "
128+ styleClass ="status-tag-option "> </ p-tag >
129+ </ ng-template >
130+ </ p-dropdown >
131+ </ td >
115132
116- < td class ="text-center ">
117- < span class ="p-column-title "> Actions</ span >
118- < div class ="flex gap-2 justify-content-center ">
119- < button *ngIf ="isUnassigned(task) " pButton pRipple icon ="pi pi-user-plus " class ="action-button "
120- severity ="success " [outlined] ="true " [rounded] ="true " (click) ="assignToMe(task) "
121- pTooltip ="Assign to Me " tooltipPosition ="top ">
122- </ button >
123- < button *ngIf ="isAssignedToMe(task) " pButton pRipple icon ="pi pi-user-minus " class ="action-button "
124- severity ="warning " [outlined] ="true " [rounded] ="true " (click) ="unassignTask(task) "
125- pTooltip ="Unassign Task " tooltipPosition ="top ">
126- </ button >
127- < button *ngIf ="!isUnassigned(task) && !isAssignedToMe(task) " pButton pRipple icon ="pi pi-user "
128- class ="action-button " severity ="secondary " [outlined] ="true " [rounded] ="true " tooltipPosition ="top "
129- disabled >
130- </ button >
131- </ div >
132- </ td >
133- </ tr >
134- </ ng-template >
133+ < td >
134+ < span class ="p-column-title "> Created At</ span >
135+ {{ task.createdAt ? (task.createdAt | date:'MMM d, y') : '-' }}
136+ </ td >
135137
136- < ng-template pTemplate ="emptymessage " let-columns >
137- < tr >
138- < td [attr.colspan] ="columns?.length ">
139- < div class ="text-center p-4 ">
140- < i class ="pi pi-inbox text-4xl text-color-secondary mb-3 "> </ i >
141- < p > {{ selectedProject ? 'No tasks found for this project.' : 'Please select a project.' }}</ p >
142- </ div >
143- </ td >
144- </ tr >
145- </ ng-template >
138+ < td >
139+ < span class ="p-column-title "> Due Date</ span >
140+ {{ task.dueDate ? (task.dueDate | date:'MMM d, y') : '-' }}
141+ </ td >
142+
143+ < td >
144+ < span class ="p-column-title "> Assigned To</ span >
145+ < div *ngIf ="!isUnassigned(task) " class ="flex align-items-center gap-2 ">
146+ < p-avatar [label] ="getInitials(task.assignedUserName) " shape ="circle " size ="normal "
147+ [styleClass] ="'user-avatar ' + (isAssignedToMe(task) ? 'avatar-me' : 'avatar-other') "
148+ [pTooltip] ="task.assignedUserName " tooltipPosition ="top ">
149+ </ p-avatar >
150+ < span class ="font-medium "> {{ task.assignedUserName }}</ span >
151+ </ div >
152+ < span *ngIf ="isUnassigned(task) " class ="text-color-secondary text-sm font-italic ">
153+ Unassigned
154+ </ span >
155+ </ td >
156+
157+ < td class ="text-center ">
158+ < span class ="p-column-title "> </ span >
159+ < div class ="flex gap-2 justify-content-center ">
160+ < button *ngIf ="isUnassigned(task) " pButton pRipple icon ="pi pi-user-plus " class ="action-button "
161+ severity ="success " [outlined] ="true " [rounded] ="true " (click) ="assignToMe(task); showStatusDropdown = !showStatusDropdown " pTooltip ="Assign to Me "
162+ tooltipPosition ="top ">
163+ </ button >
164+ < button *ngIf ="isAssignedToMe(task) " pButton pRipple icon ="pi pi-user-minus " class ="action-button "
165+ severity ="warning " [outlined] ="true " [rounded] ="true " (click) ="unassignTask(task); showStatusDropdown = !showStatusDropdown " pTooltip ="Unassign Task "
166+ tooltipPosition ="top ">
167+ </ button >
168+ < button *ngIf ="!isUnassigned(task) && !isAssignedToMe(task) " pButton pRipple icon ="pi pi-user "
169+ class ="action-button " severity ="secondary " [outlined] ="true " [rounded] ="true " tooltipPosition ="top "
170+ disabled >
171+ </ button >
172+ </ div >
173+ </ td >
174+ </ tr >
175+ </ ng-template >
146176
147- < ng-template pTemplate ="loadingbody " let-columns >
148- < tr *ngFor =" let item of [1,2,3,4,5,6] " class =" p-datatable-loading-row " >
149- < td > < p-skeleton height =" 1.5rem " styleClass =" mb-2 " > </ p-skeleton > </ td >
150- < td > < p-skeleton height =" 1.5rem " styleClass =" mb-2 " > </ p-skeleton > </ td >
151- < td > < p-skeleton height =" 1.5rem " styleClass =" mb-2 "> </ p-skeleton > </ td >
152- < td > < p-skeleton height =" 1.5rem " styleClass =" mb-2 " > </ p-skeleton > </ td >
153- < td > < p-skeleton height =" 1.5rem " styleClass =" mb-2 " > </ p-skeleton > </ td >
154- < td > < p-skeleton height =" 1.5rem " styleClass =" mb-2 " > </ p-skeleton > </ td >
155- </ tr >
156- </ ng-template >
177+ < ng-template pTemplate ="emptymessage " let-columns >
178+ < tr >
179+ < td [attr.colspan] =" 6 " >
180+ < div class =" text-center p-4 " >
181+ < i class =" pi pi-inbox text-4xl text-color-secondary mb-3 "> </ i >
182+ < p > {{ selectedProject ? 'No tasks found for this project.' : 'Please select a project.' }} </ p >
183+ </ div >
184+ </ td >
185+ </ tr >
186+ </ ng-template >
157187
158- </ p-table >
159- </ p-card >
160- </ div >
161- </ div >
188+ < ng-template pTemplate ="loadingbody " let-columns >
189+ < tr *ngFor ="let item of [1,2,3] " class ="p-datatable-loading-row ">
190+ < td > < p-skeleton height ="1.5rem " styleClass ="mb-2 "> </ p-skeleton > </ td >
191+ < td > < p-skeleton height ="1.5rem " styleClass ="mb-2 "> </ p-skeleton > </ td >
192+ < td > < p-skeleton height ="1.5rem " styleClass ="mb-2 "> </ p-skeleton > </ td >
193+ < td > < p-skeleton height ="1.5rem " styleClass ="mb-2 "> </ p-skeleton > </ td >
194+ < td > < p-skeleton height ="1.5rem " styleClass ="mb-2 "> </ p-skeleton > </ td >
195+ < td > < p-skeleton height ="1.5rem " styleClass ="mb-2 "> </ p-skeleton > </ td >
196+ </ tr >
197+ </ ng-template >
198+ </ p-table >
0 commit comments