Skip to content

Commit 771c347

Browse files
feat: TTL-910 add activity and project filters
1 parent 13399b9 commit 771c347

File tree

13 files changed

+202
-114
lines changed

13 files changed

+202
-114
lines changed

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import { V2RedirectComponent } from './modules/v2-redirect/v2-redirect.component
102102
import { SpinnerOverlayComponent } from './modules/shared/components/spinner-overlay/spinner-overlay.component';
103103
import { SpinnerInterceptor } from './modules/shared/interceptors/spinner.interceptor';
104104
import { SearchProjectComponent } from './modules/shared/components/search-project/search-project.component';
105+
import { SearchActivityComponent } from './modules/shared/components/search-activity/search-activity.component';
105106

106107
const maskConfig: Partial<IConfig> = {
107108
validation: false,
@@ -144,6 +145,7 @@ const maskConfig: Partial<IConfig> = {
144145
TechnologiesComponent,
145146
SearchUserComponent,
146147
SearchProjectComponent,
148+
SearchActivityComponent,
147149
TimeEntriesSummaryComponent,
148150
TimeDetailsPipe,
149151
InputLabelComponent,

src/app/modules/reports/components/time-entries-table/time-entries-table.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<div class="row scroll-table mt-5 ml-0">
22
<app-search-user [users]="users" (selectedUserId)="user($event)"></app-search-user>
33
<app-search-project [projects]="listProjects" (selectedProjectId)="project($event)"></app-search-project>
4+
<app-search-activity [activities]="activities" (selectedActivityId)="activity($event)"></app-search-activity>
45
<table
56
class="table table-striped mb-0"
67
datatable

src/app/modules/reports/components/time-entries-table/time-entries-table.component.ts

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import { DataTableDirective } from 'angular-datatables';
55
import * as moment from 'moment';
66
import { Observable, Subject, Subscription } from 'rxjs';
77
import { filter } from 'rxjs/operators';
8-
import { Entry, Project } from 'src/app/modules/shared/models';
8+
import { Activity, Entry, Project } from 'src/app/modules/shared/models';
99
import { DataSource } from 'src/app/modules/shared/models/data-source.model';
1010
import { EntryState } from '../../../time-clock/store/entry.reducer';
1111
import { getReportDataSource, getResultSumEntriesSelected } from '../../../time-clock/store/entry.selectors';
1212
import { TotalHours } from '../../models/total-hours-report';
1313
import { User } from 'src/app/modules/users/models/users';
1414
import { LoadUsers, UserActionTypes } from 'src/app/modules/users/store/user.actions';
1515
import { ParseDateTimeOffset } from '../../../shared/formatters/parse-date-time-offset/parse-date-time-offset';
16-
import { LoadProjects, ProjectActionTypes } from 'src/app/modules/customer-management/components/projects/components/store/project.actions';
16+
import {
17+
LoadProjects,
18+
ProjectActionTypes,
19+
} from 'src/app/modules/customer-management/components/projects/components/store/project.actions';
20+
import { ActivityManagementActionTypes, LoadActivities } from 'src/app/modules/activities-management/store';
1721

1822
@Component({
1923
selector: 'app-time-entries-table',
@@ -23,12 +27,14 @@ import { LoadProjects, ProjectActionTypes } from 'src/app/modules/customer-manag
2327
export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewInit {
2428
@Output() selectedUserId = new EventEmitter<string>();
2529
@Output() selectedProjectId = new EventEmitter<string>();
30+
@Output() selectedActivityId = new EventEmitter<string>();
2631

2732
selectOptionValues = [15, 30, 50, 100, -1];
2833
selectOptionNames = [15, 30, 50, 100, 'All'];
2934
totalTimeSelected: moment.Duration;
3035
users: User[] = [];
3136
projects: Project[] = [];
37+
activities: Activity[] = [];
3238
removeFirstColumn = 'th:not(:first)';
3339

3440
dtOptions: any = {
@@ -40,7 +46,7 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
4046
{
4147
text: 'Column Visibility' + ' ▼',
4248
extend: 'colvis',
43-
columns: ':not(.hidden-col)'
49+
columns: ':not(.hidden-col)',
4450
},
4551
{
4652
extend: 'print',
@@ -52,27 +58,34 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
5258
extend: 'excel',
5359
exportOptions: {
5460
format: {
55-
body: this.bodyExportOptions
61+
body: this.bodyExportOptions,
5662
},
5763
columns: this.removeFirstColumn,
5864
},
5965
text: 'Excel',
60-
filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}`
66+
filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}`,
6167
},
6268
{
6369
extend: 'csv',
6470
exportOptions: {
6571
format: {
66-
body: this.bodyExportOptions
72+
body: this.bodyExportOptions,
6773
},
6874
columns: this.removeFirstColumn,
6975
},
7076
text: 'CSV',
71-
filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}`
77+
filename: `time-entries-${formatDate(new Date(), 'MM_dd_yyyy-HH_mm', 'en')}`,
7278
},
7379
],
74-
columnDefs: [{ type: 'date', targets: 3}, {orderable: false, targets: [0]}],
75-
order: [[1, 'asc'], [2, 'desc'], [4, 'desc']]
80+
columnDefs: [
81+
{ type: 'date', targets: 3 },
82+
{ orderable: false, targets: [0] },
83+
],
84+
order: [
85+
[1, 'asc'],
86+
[2, 'desc'],
87+
[4, 'desc'],
88+
],
7689
};
7790
dtTrigger: Subject<any> = new Subject();
7891
@ViewChild(DataTableDirective, { static: false })
@@ -87,15 +100,17 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
87100
dateTimeOffset: ParseDateTimeOffset;
88101
listProjects: Project[] = [];
89102

90-
constructor(private store: Store<EntryState>,
91-
private actionsSubject$: ActionsSubject,
92-
private storeUser: Store<User>,
93-
private storeProject: Store<Project>
94-
) {
95-
this.reportDataSource$ = this.store.pipe(select(getReportDataSource));
96-
this.resultSumEntriesSelected$ = this.store.pipe(select(getResultSumEntriesSelected));
97-
this.dateTimeOffset = new ParseDateTimeOffset();
98-
this.resultSumEntriesSelected = new TotalHours();
103+
constructor(
104+
private store: Store<EntryState>,
105+
private actionsSubject$: ActionsSubject,
106+
private storeUser: Store<User>,
107+
private storeProject: Store<Project>,
108+
private storeActivity: Store<Activity>
109+
) {
110+
this.reportDataSource$ = this.store.pipe(select(getReportDataSource));
111+
this.resultSumEntriesSelected$ = this.store.pipe(select(getResultSumEntriesSelected));
112+
this.dateTimeOffset = new ParseDateTimeOffset();
113+
this.resultSumEntriesSelected = new TotalHours();
99114
}
100115

101116
uploadUsers(): void {
@@ -117,7 +132,18 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
117132
const sortProjects = [...action.payload];
118133
sortProjects.sort((a, b) => a.name.localeCompare(b.name));
119134
this.projects = sortProjects;
120-
this.projects = this.projects.filter(project => project.status === 'active');
135+
this.projects = this.projects.filter((project) => project.status === 'active');
136+
this.projects.sort((a, b) => {
137+
const x = a.customer.name.toLowerCase();
138+
const y = b.customer.name.toLowerCase();
139+
if (x > y) {
140+
return 1;
141+
}
142+
if (x < y) {
143+
return -1;
144+
}
145+
return 0;
146+
});
121147
this.projects.forEach((project) => {
122148
const projectWithSearchField = { ...project };
123149
projectWithSearchField.search_field = `${project.customer.name} - ${project.name}`;
@@ -126,17 +152,29 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
126152
});
127153
}
128154

155+
uploadActivities(): void {
156+
this.storeActivity.dispatch(new LoadActivities());
157+
this.actionsSubject$
158+
.pipe(filter((action: any) => action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS))
159+
.subscribe((action) => {
160+
const sortActivities = [...action.payload];
161+
sortActivities.sort((a, b) => a.name.localeCompare(b.name));
162+
this.activities = sortActivities;
163+
});
164+
}
165+
129166
ngOnInit(): void {
130167
this.rerenderTableSubscription = this.reportDataSource$.subscribe((ds) => {
131168
this.totalHoursSubscription = this.resultSumEntriesSelected$.subscribe((actTotalHours) => {
132-
this.resultSumEntriesSelected = actTotalHours;
133-
this.totalTimeSelected = moment.duration(0);
134-
});
169+
this.resultSumEntriesSelected = actTotalHours;
170+
this.totalTimeSelected = moment.duration(0);
171+
});
135172
this.sumDates(ds.data);
136173
this.rerenderDataTable();
137174
});
138175
this.uploadUsers();
139176
this.uploadProjects();
177+
this.uploadActivities();
140178
}
141179

142180
ngAfterViewInit(): void {
@@ -172,20 +210,17 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
172210
return data.toString().replace(/<((.|\n){0,200}?)>/gi, '') || '';
173211
}
174212

175-
176213
sumDates(arrayData: Entry[]): TotalHours {
177214
this.resultSum = new TotalHours();
178215
const arrayDurations = new Array();
179-
arrayData.forEach(entry => {
216+
arrayData.forEach((entry) => {
180217
const start = moment(entry.end_date).diff(moment(entry.start_date));
181218
arrayDurations.push(moment.utc(start).format('HH:mm:ss'));
182219
});
183220

184-
const totalDurations = arrayDurations.slice(1)
185-
.reduce((prev, cur) => {
186-
return prev.add(cur);
187-
},
188-
moment.duration(arrayDurations[0]));
221+
const totalDurations = arrayDurations.slice(1).reduce((prev, cur) => {
222+
return prev.add(cur);
223+
}, moment.duration(arrayDurations[0]));
189224
const daysInHours = totalDurations.days() * 24;
190225
this.resultSum.hours = totalDurations.hours() + daysInHours;
191226
this.resultSum.minutes = totalDurations.minutes();
@@ -201,7 +236,11 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
201236
this.selectedProjectId.emit(projectId);
202237
}
203238

204-
sumHoursEntriesSelected(entry: Entry, checked: boolean){
239+
activity(activityId: string) {
240+
this.selectedActivityId.emit(activityId);
241+
}
242+
243+
sumHoursEntriesSelected(entry: Entry, checked: boolean) {
205244
this.resultSumEntriesSelected = new TotalHours();
206245
const duration = moment.duration(moment(entry.end_date).diff(moment(entry.start_date)));
207246
this.totalTimeSelected = checked ? this.totalTimeSelected.add(duration) : this.totalTimeSelected.subtract(duration);

src/app/modules/reports/components/time-range-custom/time-range-custom.component.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { TimeRangeHeaderComponent } from './time-range-header/time-range-header.
2525
})
2626
export class TimeRangeCustomComponent implements OnInit, OnChanges {
2727
@Input() userId: string;
28+
@Input() projectId: string;
29+
@Input() activityId: string;
2830
customHeader = TimeRangeHeaderComponent;
2931
range = new FormGroup({
3032
start: new FormControl(null),
@@ -42,6 +44,12 @@ export class TimeRangeCustomComponent implements OnInit, OnChanges {
4244
if (!changes.userId.firstChange){
4345
this.onSubmit();
4446
}
47+
if (!changes.projectId.firstChange){
48+
this.onSubmit();
49+
}
50+
if (!changes.activityId.firstChange){
51+
this.onSubmit();
52+
}
4553
}
4654

4755
setInitialDataOnScreen() {
@@ -62,7 +70,7 @@ export class TimeRangeCustomComponent implements OnInit, OnChanges {
6270
this.store.dispatch(new entryActions.LoadEntriesByTimeRange({
6371
start_date: moment(this.range.getRawValue().start).startOf('day'),
6472
end_date: moment(this.range.getRawValue().end).endOf('day'),
65-
}, this.userId));
73+
}, this.userId, this.projectId, this.activityId));
6674
}
6775
}
6876

src/app/modules/reports/components/time-range-form/time-range-form.component.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { DateAdapter } from '@angular/material/core';
1616
export class TimeRangeFormComponent implements OnInit, OnChanges {
1717

1818
@Input() userId: string;
19+
@Input() projectId: string;
20+
@Input() activityId: string;
1921

2022
public reportForm: FormGroup;
2123
private startDate = new FormControl('');
@@ -37,6 +39,12 @@ export class TimeRangeFormComponent implements OnInit, OnChanges {
3739
if (!changes.userId.firstChange){
3840
this.onSubmit();
3941
}
42+
if (!changes.projectId.firstChange){
43+
this.onSubmit();
44+
}
45+
if (!changes.activityId.firstChange){
46+
this.onSubmit();
47+
}
4048
}
4149

4250
setInitialDataOnScreen() {
@@ -56,7 +64,7 @@ export class TimeRangeFormComponent implements OnInit, OnChanges {
5664
this.store.dispatch(new entryActions.LoadEntriesByTimeRange({
5765
start_date: moment(this.startDate.value).startOf('day'),
5866
end_date: moment(this.endDate.value).endOf('day'),
59-
}, this.userId));
67+
}, this.userId, this.projectId, this.activityId));
6068
}
6169
}
6270
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
<app-time-range-custom [userId]="userId"></app-time-range-custom>
2-
<app-time-entries-table (selectedUserId)="user($event)"></app-time-entries-table>
1+
<app-time-range-custom [userId]="userId" [projectId]="projectId" [activityId]="activityId"></app-time-range-custom>
2+
<app-time-entries-table (selectedUserId)="user($event)" (selectedProjectId)="project($event)" (selectedActivityId)="activity($event)"></app-time-entries-table>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div class="form-group" >
2+
<label>Activity: </label>
3+
4+
<ng-select [(ngModel)]="selectedActivity" [multiple]="false" placeholder="Select activity" (change)="updateActivity()" class="selectActivity">
5+
<ng-option *ngFor="let activity of activities" value={{activity.id}}>{{activity.name}}</ng-option >
6+
</ng-select>
7+
8+
</div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
label {
2+
width: 225px;
3+
}
4+
.selectActivity {
5+
display: inline-block;
6+
width: 350px;
7+
padding: 0 12px 15px 12px;
8+
}
9+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Component, EventEmitter, Input, Output } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-search-activity',
5+
templateUrl: './search-activity.component.html',
6+
styleUrls: ['./search-activity.component.scss'],
7+
})
8+
9+
export class SearchActivityComponent {
10+
11+
readonly ALLOW_SELECT_MULTIPLE = false;
12+
selectedActivity: string;
13+
14+
@Input() activities: string[] = [];
15+
16+
@Output() selectedActivityId = new EventEmitter<string>();
17+
18+
updateActivity() {
19+
this.selectedActivityId.emit(this.selectedActivity || '*' );
20+
}
21+
}
22+

src/app/modules/shared/components/search-project/search-project.component.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,15 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
55
templateUrl: './search-project.component.html',
66
styleUrls: ['./search-project.component.scss'],
77
})
8-
98
export class SearchProjectComponent {
10-
119
readonly ALLOW_SELECT_MULTIPLE = false;
12-
selectedProject: string[];
10+
selectedProject: string;
1311

1412
@Input() projects: string[] = [];
1513

16-
@Output() selectedProjectId = new EventEmitter<string[] | string>();
14+
@Output() selectedProjectId = new EventEmitter<string>();
1715

1816
updateProject() {
19-
this.selectedProjectId.emit(this.selectedProject.length === 0 ? '*' : this.selectedProject);
17+
this.selectedProjectId.emit(this.selectedProject || '*');
2018
}
2119
}
22-

0 commit comments

Comments
 (0)