Skip to content

Commit 3bd5600

Browse files
authored
TT 589 improve time report presentation (#838)
* Adjust start_date and end_date report depending of the administrator time zone * Parsing date in the reports using timezone_offset with moment.js * Adjust entry.service.ts to do loadEntriesByTimeRange without timezone_offset * Implement test to ParseDateTimeOffset
1 parent b4c09ab commit 3bd5600

File tree

7 files changed

+160
-170
lines changed

7 files changed

+160
-170
lines changed

package-lock.json

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 54 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,57 @@
11
<div class="row scroll-table mt-5 ml-0">
2-
<table
3-
class="table table-striped mb-0"
4-
datatable
5-
[dtTrigger]="dtTrigger"
6-
[dtOptions]="dtOptions"
7-
*ngIf="(reportDataSource$ | async) as dataSource">
8-
<thead class="thead-blue">
9-
<tr class="d-flex">
10-
<th class="hidden-col">ID</th>
11-
<th class="col md-col">User email</th>
12-
<th class="col sm-col">Date</th>
13-
<th class="col sm-col" title="Duration (hours)">Duration</th>
14-
<th class="col x-sm-col" title="Time in">Time in</th>
15-
<th class="col x-sm-col" title="Time out">Time out</th>
16-
<th class="col md-col">Project</th>
17-
<th class="hidden-col">Project ID</th>
18-
<th class="col md-col">Customer</th>
19-
<th class="hidden-col">Customer ID</th>
20-
<th class="col md-col">Activity</th>
21-
<th class="col lg-col">Ticket</th>
22-
<th class="col lg-col">Description</th>
23-
<th class="col lg-col">Technologies</th>
24-
</tr>
25-
</thead>
26-
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
27-
<tbody *ngIf="!dataSource.isLoading">
28-
<tr class="d-flex" *ngFor="let entry of dataSource.data">
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">{{ entry.start_date | date: 'HH:mm' }}</td>
38-
<td class="col x-sm-col">{{ entry.end_date | date: 'HH:mm' }}</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)">
2+
<table class="table table-striped mb-0" datatable [dtTrigger]="dtTrigger" [dtOptions]="dtOptions" *ngIf="(reportDataSource$ | async) as dataSource">
3+
<thead class="thead-blue">
4+
<tr class="d-flex">
5+
<th class="hidden-col">ID</th>
6+
<th class="col md-col">User email</th>
7+
<th class="col sm-col">Date</th>
8+
<th class="col sm-col" title="Duration (hours)">Duration</th>
9+
<th class="col x-sm-col" title="Time in">Time in</th>
10+
<th class="col x-sm-col" title="Time out">Time out</th>
11+
<th class="col md-col">Project</th>
12+
<th class="hidden-col">Project ID</th>
13+
<th class="col md-col">Customer</th>
14+
<th class="hidden-col">Customer ID</th>
15+
<th class="col md-col">Activity</th>
16+
<th class="col lg-col">Ticket</th>
17+
<th class="col lg-col">Description</th>
18+
<th class="col lg-col">Technologies</th>
19+
</tr>
20+
</thead>
21+
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
22+
<tbody *ngIf="!dataSource.isLoading">
23+
<tr class="d-flex" *ngFor="let entry of dataSource.data">
24+
<td class="hidden-col">{{ entry.id }}</td>
25+
<td class="col md-col">{{ entry.owner_email }}</td>
26+
<td class="col sm-col">
27+
{{ entry.start_date | date: 'MM/dd/yyyy' }}
28+
</td>
29+
<td class="col sm-col">
30+
{{ entry.end_date | substractDate: entry.start_date }}
31+
</td>
32+
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.start_date,entry.timezone_offset) }}</td>
33+
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.end_date , entry.timezone_offset) }}</td>
34+
<td class="col md-col">{{ entry.project_name }}</td>
35+
<td class="hidden-col">{{ entry.project_id }}</td>
36+
<td class="col md-col">{{ entry.customer_name }}</td>
37+
<td class="hidden-col">{{ entry.customer_id }}</td>
38+
<td class="col md-col">{{ entry.activity_name }}</td>
39+
<td class="col lg-col">
40+
<ng-container *ngIf="entry.uri !== null">
41+
<a [class.is-url]="isURL(entry.uri)" (click)="openURLInNewTab(entry.uri)">
4742
{{ entry.uri }}
4843
</a>
49-
</ng-container>
50-
</td>
51-
<td class="col lg-col">{{ entry.description }}</td>
52-
<td class="col lg-col">
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>
62-
</div>
44+
</ng-container>
45+
</td>
46+
<td class="col lg-col">{{ entry.description }}</td>
47+
<td class="col lg-col">
48+
<ng-container *ngIf="entry.technologies.length > 0">
49+
<div *ngFor="let technology of entry.technologies" class="badge bg-secondary text-wrap">
50+
{{ technology }}
51+
</div>
52+
</ng-container>
53+
</td>
54+
</tr>
55+
</tbody>
56+
</table>
57+
</div>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Entry } from 'src/app/modules/shared/models';
88
import { DataSource } from 'src/app/modules/shared/models/data-source.model';
99
import { EntryState } from '../../../time-clock/store/entry.reducer';
1010
import { getReportDataSource } from '../../../time-clock/store/entry.selectors';
11+
import { ParseDateTimeOffset } from '../../../shared/formatters/parse-date-time-offset/parse-date-time-offset';
1112

1213
@Component({
1314
selector: 'app-time-entries-table',
@@ -60,9 +61,11 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
6061
isLoading$: Observable<boolean>;
6162
reportDataSource$: Observable<DataSource<Entry>>;
6263
rerenderTableSubscription: Subscription;
64+
dateTimeOffset: ParseDateTimeOffset;
6365

6466
constructor(private store: Store<EntryState>) {
6567
this.reportDataSource$ = this.store.pipe(select(getReportDataSource));
68+
this.dateTimeOffset = new ParseDateTimeOffset();
6669
}
6770

6871
ngOnInit(): void {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ParseDateTimeOffset } from './parse-date-time-offset';
2+
3+
describe('ParseDateToUtcComponent', () => {
4+
5+
it('returns converted date when his offset is 300', () => {
6+
let parseTimeOffset = new ParseDateTimeOffset();
7+
const date = '2022-03-30T13:00:00Z';
8+
const timezone_offset = 300;
9+
const dateOffset:string = '08:00';
10+
11+
expect(parseTimeOffset.parseDateTimeOffset(date, timezone_offset)).toEqual(dateOffset);
12+
});
13+
14+
it('returns converted date when his offset is 420', () => {
15+
let parseTimeOffset = new ParseDateTimeOffset();
16+
const date = '2022-03-30T16:30:00Z';
17+
const timezone_offset = 420;
18+
const dateOffset:string = '09:30';
19+
20+
expect(parseTimeOffset.parseDateTimeOffset(date, timezone_offset)).toEqual(dateOffset);
21+
});
22+
23+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as moment from 'moment';
2+
3+
export class ParseDateTimeOffset {
4+
5+
parseDateTimeOffset(date:string, offset): string{
6+
return moment.utc(date).utcOffset(-1*offset).format("HH:mm");
7+
}
8+
}
Lines changed: 60 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,85 @@
11
<div>
2-
<div>
3-
<button
4-
type="button"
5-
(click)="newEntry()"
6-
data-toggle="modal"
7-
data-target="#editRecordsByDate"
8-
class="btn btn-primary"
9-
>
2+
<div>
3+
<button type="button" (click)="newEntry()" data-toggle="modal" data-target="#editRecordsByDate" class="btn btn-primary">
104
Add new entry
115
</button>
12-
<button
13-
type="button"
14-
(click)="onDisplayModeChange()"
15-
class="btn btn-primary float-right"
16-
*ngIf="isFeatureToggleCalendarActive"
17-
>
6+
<button type="button" (click)="onDisplayModeChange()" class="btn btn-primary float-right" *ngIf="isFeatureToggleCalendarActive">
187
<em class="fas fa-list" *ngIf="displayGridView"></em>
198
<em class="fas fa-th" *ngIf="!displayGridView"></em>
209
</button>
21-
</div>
22-
<div style="height: 15px"></div>
23-
<app-month-picker [selectedDate]="selectedDate" (dateSelected)="dateSelected($event)"></app-month-picker>
24-
<div style="height: 15px"></div>
10+
</div>
11+
<div style="height: 15px"></div>
12+
<app-month-picker [selectedDate]="selectedDate" (dateSelected)="dateSelected($event)"></app-month-picker>
13+
<div style="height: 15px"></div>
2514

2615

27-
<div id="gridView" [hidden]="!displayGridView">
28-
<div *ngIf="timeEntriesDataSource$ | async as dataSource">
29-
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
30-
<app-calendar
31-
*ngIf="!dataSource.isLoading"
32-
[timeEntries$]="timeEntriesDataSource$"
33-
[currentDate]="selectedDate.toDate()"
34-
[calendarView]="calendarView"
35-
(changeDate)="changeDate($event)"
36-
(changeView)="changeView($event)"
37-
(viewModal)="editEntry($event.id)"
38-
(deleteTimeEntry)="openModal($event.timeEntry)"
39-
>
40-
</app-calendar>
16+
<div id="gridView" [hidden]="!displayGridView">
17+
<div *ngIf="timeEntriesDataSource$ | async as dataSource">
18+
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
19+
<app-calendar *ngIf="!dataSource.isLoading" [timeEntries$]="timeEntriesDataSource$" [currentDate]="selectedDate.toDate()" [calendarView]="calendarView" (changeDate)="changeDate($event)" (changeView)="changeView($event)" (viewModal)="editEntry($event.id)"
20+
(deleteTimeEntry)="openModal($event.timeEntry)">
21+
</app-calendar>
22+
</div>
4123
</div>
42-
</div>
4324

44-
<div id="listView" [hidden]="displayGridView">
45-
<table
46-
class="table table-sm table-striped mb-0"
47-
datatable
48-
[dtTrigger]="dtTrigger"
49-
[dtOptions]="dtOptions"
50-
*ngIf="(timeEntriesDataSource$ | async) as dataSource">
51-
<caption></caption>
52-
<thead class="thead-blue">
53-
<tr class="d-flex">
54-
<th class="col">Date</th>
55-
<th class="col">Time in - out</th>
56-
<th class="col">Duration</th>
57-
<th class="col">Customer</th>
58-
<th class="col">Project</th>
59-
<th class="col">Activity</th>
60-
<th class="col"></th>
61-
</tr>
62-
</thead>
63-
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
64-
<tbody *ngIf="!dataSource.isLoading">
65-
<tr class="d-flex" *ngFor="let entry of dataSource.data">
66-
<td class="col">{{ entry.start_date | date: 'MM/dd/yyyy' }}</td>
67-
<td class="col">{{ entry.start_date | date: 'HH:mm' }} - {{ entry.end_date | date: 'HH:mm' }}</td>
68-
<td class="col">{{ entry.end_date | substractDate: entry.start_date }}</td>
69-
<td class="col">{{ entry.customer_name }}</td>
70-
<td class="col">{{ entry.project_name }}</td>
71-
<td class="col">{{ entry.activity_name }}</td>
72-
<td class="col">
73-
<button
74-
class="btn btn-sm btn-primary"
75-
data-toggle="modal"
76-
data-target="#editRecordsByDate"
77-
(click)="editEntry(entry.id)"
78-
>
25+
<div id="listView" [hidden]="displayGridView">
26+
<table class="table table-sm table-striped mb-0" datatable [dtTrigger]="dtTrigger" [dtOptions]="dtOptions" *ngIf="(timeEntriesDataSource$ | async) as dataSource">
27+
<caption></caption>
28+
<thead class="thead-blue">
29+
<tr class="d-flex">
30+
<th class="col">Date</th>
31+
<th class="col">Time in - out</th>
32+
<th class="col">Duration</th>
33+
<th class="col">Customer</th>
34+
<th class="col">Project</th>
35+
<th class="col">Activity</th>
36+
<th class="col"></th>
37+
</tr>
38+
</thead>
39+
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
40+
<tbody *ngIf="!dataSource.isLoading">
41+
<tr class="d-flex" *ngFor="let entry of dataSource.data">
42+
<td class="col">{{ entry.start_date | date: 'MM/dd/yyyy' }}</td>
43+
<td class="col">{{ dateTimeOffset.parseDateTimeOffset(entry.start_date,entry.timezone_offset) }} - {{ dateTimeOffset.parseDateTimeOffset(entry.end_date,entry.timezone_offset) }}</td>
44+
<td class="col">{{ entry.end_date | substractDate: entry.start_date }}</td>
45+
<td class="col">{{ entry.customer_name }}</td>
46+
<td class="col">{{ entry.project_name }}</td>
47+
<td class="col">{{ entry.activity_name }}</td>
48+
<td class="col">
49+
<button class="btn btn-sm btn-primary" data-toggle="modal" data-target="#editRecordsByDate" (click)="editEntry(entry.id)">
7950
<i class="fa fa-edit fa-xs"></i>
8051
</button>
81-
<button
82-
class="btn btn-sm btn-danger ml-2"
83-
data-toggle="modal"
84-
data-target="#deleteModal"
85-
(click)="openModal(entry)"
86-
>
52+
<button class="btn btn-sm btn-danger ml-2" data-toggle="modal" data-target="#deleteModal" (click)="openModal(entry)">
8753
<i class="fa fa-trash fa-xs"></i>
8854
</button>
89-
</td>
90-
</tr>
91-
</tbody>
92-
</table>
93-
</div>
55+
</td>
56+
</tr>
57+
</tbody>
58+
</table>
59+
</div>
9460
</div>
9561

9662
<div class="modal fade" id="editRecordsByDate" tabindex="-1" role="dialog">
97-
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
98-
<div class="modal-content" cdkDrag (cdkDragEnded)="resetDraggablePosition($event)">
99-
<div class="modal-header">
100-
<h5 class="modal-title">{{ entryId ? 'Edit Entry' : 'New Entry' }}</h5>
101-
<div>
102-
<button class="btn shadow-none" data-bs-toggle="tooltip" title="Clean form values" (click)="detailsFields.cleanFieldsForm()">
63+
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
64+
<div class="modal-content" cdkDrag (cdkDragEnded)="resetDraggablePosition($event)">
65+
<div class="modal-header">
66+
<h5 class="modal-title">{{ entryId ? 'Edit Entry' : 'New Entry' }}</h5>
67+
<div>
68+
<button class="btn shadow-none" data-bs-toggle="tooltip" title="Clean form values" (click)="detailsFields.cleanFieldsForm()">
10369
<em class="fa fa-eraser"> </em>
10470
</button>
105-
<button class="btn shadow-none" data-bs-toggle="tooltip" title="Drag modal" cdkDragHandle>
71+
<button class="btn shadow-none" data-bs-toggle="tooltip" title="Drag modal" cdkDragHandle>
10672
<em class="fa fa-grip-vertical"></em>
10773
</button>
74+
</div>
75+
</div>
76+
<div class="modal-body">
77+
<app-details-fields [entryToEdit]="entry" (saveEntry)="saveEntry($event)" (projectSelected)="projectSelected($event)" [canMarkEntryAsWIP]="canMarkEntryAsWIP" #detailsFields>
78+
</app-details-fields>
79+
</div>
10880
</div>
109-
</div>
110-
<div class="modal-body">
111-
<app-details-fields
112-
[entryToEdit]="entry"
113-
(saveEntry)="saveEntry($event)"
114-
(projectSelected)="projectSelected($event)"
115-
[canMarkEntryAsWIP]="canMarkEntryAsWIP"
116-
#detailsFields
117-
>
118-
</app-details-fields>
119-
</div>
12081
</div>
121-
</div>
12282
</div>
12383

124-
<app-dialog
125-
*ngIf="showModal"
126-
class="modal fade"
127-
id="deleteModal"
128-
tabindex="-1"
129-
role="dialog"
130-
aria-hidden="true"
131-
[body]="message"
132-
[title]="'Delete Entry'"
133-
(closeModalEvent)="removeEntry()"
134-
>
135-
</app-dialog>
84+
<app-dialog *ngIf="showModal" class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-hidden="true" [body]="message" [title]="'Delete Entry'" (closeModalEvent)="removeEntry()">
85+
</app-dialog>

0 commit comments

Comments
 (0)