Skip to content

Commit bb04771

Browse files
authored
TTA-196-duration-column-formatting-ui (#941)
* refactor: TTA-196 duration as float on reports * changed pipe function * refactor: TTA-196 display hours as float * refactor: TTA-196 fix imports and lint errors * refactor: TTA-196 number of fixed digits as constant with name * fix: TTA-196 variable was ill defined * refactor: TTA-196 2 digits for fixed point duration of time entries in reports * fix: TTA-196 fixed tests for 2 fixed point digits. TODO: use that constant in tests Co-authored-by: mmaquina <[email protected]>
1 parent eed3671 commit bb04771

File tree

10 files changed

+154
-70
lines changed

10 files changed

+154
-70
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ testem.log
4242
/typings
4343
debug.log
4444
*.vscode
45+
.hintrc
4546

4647
# System Files
4748
.DS_Store

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { UserEffects } from './modules/user/store/user.effects';
7272
import { EntryEffects } from './modules/time-clock/store/entry.effects';
7373
import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.token.interceptor';
7474
import { SubstractDatePipe } from './modules/shared/pipes/substract-date/substract-date.pipe';
75+
import { SubstractDatePipeDisplayAsFloat } from './modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe';
7576
import { TechnologiesComponent } from './modules/shared/components/technologies/technologies.component';
7677
import { TimeEntriesSummaryComponent } from './modules/time-clock/components/time-entries-summary/time-entries-summary.component';
7778
import { TimeDetailsPipe } from './modules/time-clock/pipes/time-details.pipe';
@@ -138,6 +139,7 @@ const maskConfig: Partial<IConfig> = {
138139
CreateProjectTypeComponent,
139140
EntryFieldsComponent,
140141
SubstractDatePipe,
142+
SubstractDatePipeDisplayAsFloat,
141143
TechnologiesComponent,
142144
SearchUserComponent,
143145
TimeEntriesSummaryComponent,

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

Lines changed: 69 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,74 @@
11
<div class="row scroll-table mt-5 ml-0">
2-
<app-search-user [users]="users" (selectedUserId)="user($event)"></app-search-user>
2+
<app-search-user [users]="users" (selectedUserId)="user($event)"></app-search-user>
33

4-
<table class="table table-striped mb-0" datatable [dtTrigger]="dtTrigger" [dtOptions]="dtOptions" *ngIf="(reportDataSource$ | async) as dataSource">
5-
<thead class="thead-blue">
6-
<tr class="d-flex">
7-
<th class="col md-col">Selected</th>
8-
<th class="hidden-col">ID</th>
9-
<th class="col md-col">User email</th>
10-
<th class="col sm-col">Date</th>
11-
<th class="col sm-col" title="Duration (hours)">Duration</th>
12-
<th class="col x-sm-col" title="Time in">Time in</th>
13-
<th class="col x-sm-col" title="Time out">Time out</th>
14-
<th class="col md-col">Project</th>
15-
<th class="hidden-col">Project ID</th>
16-
<th class="col md-col">Customer</th>
17-
<th class="hidden-col">Customer ID</th>
18-
<th class="col md-col">Activity</th>
19-
<th class="col lg-col">Ticket</th>
20-
<th class="col lg-col">Description</th>
21-
<th class="col lg-col">Technologies</th>
22-
</tr>
23-
</thead>
24-
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
25-
<tbody *ngIf="!dataSource.isLoading">
26-
<tr class="d-flex col-height" *ngFor="let entry of dataSource.data">
27-
<td class="col md-col"><mat-checkbox
28-
(change)="sumHoursEntriesSelected(entry, $event.checked)"></mat-checkbox></td>
29-
<td class="hidden-col">{{ entry.id }}</td>
30-
<td class="col md-col">{{ entry.owner_email }}</td>
31-
<td class="col sm-col">
32-
{{ entry.start_date | date: 'MM/dd/yyyy' }}
33-
</td>
34-
<td class="col sm-col">
35-
{{ entry.end_date | substractDate: entry.start_date }}
36-
</td>
37-
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.start_date,entry.timezone_offset) }}</td>
38-
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.end_date , entry.timezone_offset) }}</td>
39-
<td class="col md-col">{{ entry.project_name }}</td>
40-
<td class="hidden-col">{{ entry.project_id }}</td>
41-
<td class="col md-col">{{ entry.customer_name }}</td>
42-
<td class="hidden-col">{{ entry.customer_id }}</td>
43-
<td class="col md-col">{{ entry.activity_name }}</td>
44-
<td class="col lg-col">
45-
<ng-container *ngIf="entry.uri !== null">
46-
<a [class.is-url]="isURL(entry.uri)" (click)="openURLInNewTab(entry.uri)">
4+
<table
5+
class="table table-striped mb-0"
6+
datatable
7+
[dtTrigger]="dtTrigger"
8+
[dtOptions]="dtOptions"
9+
*ngIf="reportDataSource$ | async as dataSource"
10+
>
11+
<thead class="thead-blue">
12+
<tr class="d-flex">
13+
<th class="col md-col">Selected</th>
14+
<th class="hidden-col">ID</th>
15+
<th class="col md-col">User email</th>
16+
<th class="col sm-col">Date</th>
17+
<th class="col sm-col" title="Duration (hours)">Duration</th>
18+
<th class="col x-sm-col" title="Time in">Time in</th>
19+
<th class="col x-sm-col" title="Time out">Time out</th>
20+
<th class="col md-col">Project</th>
21+
<th class="hidden-col">Project ID</th>
22+
<th class="col md-col">Customer</th>
23+
<th class="hidden-col">Customer ID</th>
24+
<th class="col md-col">Activity</th>
25+
<th class="col lg-col">Ticket</th>
26+
<th class="col lg-col">Description</th>
27+
<th class="col lg-col">Technologies</th>
28+
</tr>
29+
</thead>
30+
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
31+
<tbody *ngIf="!dataSource.isLoading">
32+
<tr class="d-flex col-height" *ngFor="let entry of dataSource.data">
33+
<td class="col md-col">
34+
<mat-checkbox (change)="sumHoursEntriesSelected(entry, $event.checked)"></mat-checkbox>
35+
</td>
36+
<td class="hidden-col">{{ entry.id }}</td>
37+
<td class="col md-col">{{ entry.owner_email }}</td>
38+
<td class="col sm-col">
39+
{{ entry.start_date | date: 'MM/dd/yyyy' }}
40+
</td>
41+
<td class="col sm-col">
42+
{{ entry.end_date | substractDateDisplayAsFloat: entry.start_date }}
43+
</td>
44+
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.start_date, entry.timezone_offset) }}</td>
45+
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.end_date, entry.timezone_offset) }}</td>
46+
<td class="col md-col">{{ entry.project_name }}</td>
47+
<td class="hidden-col">{{ entry.project_id }}</td>
48+
<td class="col md-col">{{ entry.customer_name }}</td>
49+
<td class="hidden-col">{{ entry.customer_id }}</td>
50+
<td class="col md-col">{{ entry.activity_name }}</td>
51+
<td class="col lg-col">
52+
<ng-container *ngIf="entry.uri !== null">
53+
<a [class.is-url]="isURL(entry.uri)" (click)="openURLInNewTab(entry.uri)">
4754
{{ entry.uri }}
4855
</a>
49-
</ng-container>
50-
</td>
51-
<td class="col lg-scroll">{{ entry.description }}</td>
52-
<td class="col lg-scroll">
53-
<ng-container *ngIf="entry.technologies.length > 0">
54-
<div *ngFor="let technology of entry.technologies" class="badge bg-secondary text-wrap">
55-
{{ technology }}
56-
</div>
57-
</ng-container>
58-
</td>
59-
</tr>
60-
</tbody>
61-
</table>
56+
</ng-container>
57+
</td>
58+
<td class="col lg-scroll">{{ entry.description }}</td>
59+
<td class="col lg-scroll">
60+
<ng-container *ngIf="entry.technologies.length > 0">
61+
<div *ngFor="let technology of entry.technologies" class="badge bg-secondary text-wrap">
62+
{{ technology }}
63+
</div>
64+
</ng-container>
65+
</td>
66+
</tr>
67+
</tbody>
68+
</table>
69+
</div>
70+
<div class="alert alert-dark mt-3">
71+
Total: {{ this.resultSum.hours }} hours, {{ this.resultSum.minutes }} minutes, <br />
72+
Total hours entries selected: {{ resultSumEntriesSelected.hours }} hours,
73+
{{ resultSumEntriesSelected.minutes }} minutes
6274
</div>
63-
<div class="alert alert-dark mt-3">Total: {{this.resultSum.hours}} hours, {{this.resultSum.minutes}} minutes,
64-
<br/> Total hours entries selected: {{resultSumEntriesSelected.hours}} hours, {{resultSumEntriesSelected.minutes}} minutes</div>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DataTablesModule } from 'angular-datatables';
44
import { NgxPaginationModule } from 'ngx-pagination';
55
import { Entry } from 'src/app/modules/shared/models';
66
import { SubstractDatePipe } from 'src/app/modules/shared/pipes/substract-date/substract-date.pipe';
7+
import { SubstractDatePipeDisplayAsFloat } from 'src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe';
78
import { getReportDataSource, getResultSumEntriesSelected } from 'src/app/modules/time-clock/store/entry.selectors';
89
import { EntryState } from '../../../time-clock/store/entry.reducer';
910
import { TimeEntriesTableComponent } from './time-entries-table.component';
@@ -80,7 +81,7 @@ describe('Reports Page', () => {
8081
waitForAsync(() => {
8182
TestBed.configureTestingModule({
8283
imports: [NgxPaginationModule, DataTablesModule],
83-
declarations: [TimeEntriesTableComponent, SubstractDatePipe],
84+
declarations: [TimeEntriesTableComponent, SubstractDatePipe, SubstractDatePipeDisplayAsFloat],
8485
providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }],
8586
}).compileComponents();
8687

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,3 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
183183
return this.resultSumEntriesSelected;
184184
}
185185
}
186-

src/app/modules/shared/interceptors/spinner.interceptor.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ describe('SpinnerInterceptorService test', () => {
1616
],
1717
});
1818

19-
class MockHttpHandler implements HttpHandler {
20-
handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
21-
return of(new HttpResponse());
22-
}
19+
class MockHttpHandler implements HttpHandler {
20+
handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
21+
return of(new HttpResponse());
2322
}
23+
}
2424

2525
let overlay: Overlay;
2626
let httpHandler: HttpHandler;

src/app/modules/shared/interceptors/spinner.interceptor.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ export class SpinnerInterceptor implements HttpInterceptor {
1717
req: HttpRequest<any>,
1818
next: HttpHandler
1919
): Observable<HttpEvent<any>> {
20-
if(req.url.endsWith('recent')){
20+
if (req.url.endsWith('recent')) {
2121
const spinnerSubscription: Subscription = this.spinnerOverlayService.spinner$.subscribe();
22-
return next
22+
return next
2323
.handle(req)
2424
.pipe(finalize(() => spinnerSubscription.unsubscribe()));
25-
}else{
25+
} else {
2626
return next.handle(req);
2727
}
28-
2928
}
3029
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { SubstractDatePipeDisplayAsFloat } from './substract-date-return-float.pipe';
2+
3+
describe('SubstractDatePipeDisplayAsFloat', () => {
4+
it('create an instance', () => {
5+
const pipe = new SubstractDatePipeDisplayAsFloat();
6+
expect(pipe).toBeTruthy();
7+
});
8+
9+
/*TODO: tests will be more robust if they take into account FIXED_POINT_DIGITS*/
10+
it('returns the date diff as float hours (xx.xx)', () => {
11+
[
12+
{ endDate: '2021-04-11T10:20:00Z', startDate: '2021-04-11T08:00:00Z', expectedDiff: '2.33' },
13+
{ endDate: '2021-04-11T17:40:00Z', startDate: '2021-04-11T17:10:00Z', expectedDiff: '0.50' },
14+
{ endDate: '2021-04-11T18:18:00Z', startDate: '2021-04-11T18:00:00Z', expectedDiff: '0.30' },
15+
{ endDate: '2021-04-12T12:18:00Z', startDate: '2021-04-11T10:00:00Z', expectedDiff: '26.30' },
16+
{ endDate: '2021-04-12T10:01:00Z', startDate: '2021-04-12T10:00:00Z', expectedDiff: '0.02' },
17+
{ endDate: '2021-04-11T11:27:00Z', startDate: '2021-04-11T10:03:45Z', expectedDiff: '1.39' },
18+
].forEach(({ startDate, endDate, expectedDiff }) => {
19+
const fromDate = new Date(endDate);
20+
const substractDate = new Date(startDate);
21+
22+
const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate);
23+
24+
expect(diff).toBe(expectedDiff);
25+
});
26+
});
27+
28+
it('returns -.- if fromDate is null', () => {
29+
const fromDate = null;
30+
const substractDate = new Date('2011-04-11T08:00:30Z');
31+
32+
const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate);
33+
34+
expect(diff).toBe('-.-');
35+
});
36+
37+
it('returns -.- if substractDate is null', () => {
38+
const fromDate = new Date('2011-04-11T08:00:30Z');
39+
const substractDate = null;
40+
41+
const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate);
42+
43+
expect(diff).toBe('-.-');
44+
});
45+
46+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
import * as moment from 'moment';
3+
4+
const FIXED_POINT_DIGITS = 2;
5+
@Pipe({
6+
name: 'substractDateDisplayAsFloat'
7+
})
8+
export class SubstractDatePipeDisplayAsFloat implements PipeTransform {
9+
10+
transform(fromDate: Date, substractDate: Date): string {
11+
12+
if (fromDate === null || substractDate === null) {
13+
return '-.-';
14+
}
15+
16+
const startDate = moment(substractDate);
17+
const endDate = moment(fromDate);
18+
const duration = this.getTimeDifference(startDate, endDate);
19+
return duration.asHours().toFixed(FIXED_POINT_DIGITS).toString();
20+
}
21+
22+
getTimeDifference(substractDate: moment.Moment, fromDate: moment.Moment): moment.Duration {
23+
return moment.duration(fromDate.diff(substractDate));
24+
}
25+
26+
}

src/app/modules/shared/services/spinner-overlay.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { SpinnerOverlayComponent } from './../components/spinner-overlay/spinner
99
providedIn: 'root',
1010
})
1111
export class SpinnerOverlayService {
12-
public overlayRef: OverlayRef = undefined;
1312
static spinner$: any;
13+
public overlayRef: OverlayRef = undefined;
1414

1515
constructor(private readonly overlay: Overlay) {}
1616

0 commit comments

Comments
 (0)