Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ testem.log
/typings
debug.log
*.vscode
.hintrc

# System Files
.DS_Store
Expand Down
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { UserEffects } from './modules/user/store/user.effects';
import { EntryEffects } from './modules/time-clock/store/entry.effects';
import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.token.interceptor';
import { SubstractDatePipe } from './modules/shared/pipes/substract-date/substract-date.pipe';
import { SubstractDatePipeDisplayAsFloat } from './modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe';
import { TechnologiesComponent } from './modules/shared/components/technologies/technologies.component';
import { TimeEntriesSummaryComponent } from './modules/time-clock/components/time-entries-summary/time-entries-summary.component';
import { TimeDetailsPipe } from './modules/time-clock/pipes/time-details.pipe';
Expand Down Expand Up @@ -138,6 +139,7 @@ const maskConfig: Partial<IConfig> = {
CreateProjectTypeComponent,
EntryFieldsComponent,
SubstractDatePipe,
SubstractDatePipeDisplayAsFloat,
TechnologiesComponent,
SearchUserComponent,
TimeEntriesSummaryComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,64 +1,74 @@
<div class="row scroll-table mt-5 ml-0">
<app-search-user [users]="users" (selectedUserId)="user($event)"></app-search-user>
<app-search-user [users]="users" (selectedUserId)="user($event)"></app-search-user>

<table class="table table-striped mb-0" datatable [dtTrigger]="dtTrigger" [dtOptions]="dtOptions" *ngIf="(reportDataSource$ | async) as dataSource">
<thead class="thead-blue">
<tr class="d-flex">
<th class="col md-col">Selected</th>
<th class="hidden-col">ID</th>
<th class="col md-col">User email</th>
<th class="col sm-col">Date</th>
<th class="col sm-col" title="Duration (hours)">Duration</th>
<th class="col x-sm-col" title="Time in">Time in</th>
<th class="col x-sm-col" title="Time out">Time out</th>
<th class="col md-col">Project</th>
<th class="hidden-col">Project ID</th>
<th class="col md-col">Customer</th>
<th class="hidden-col">Customer ID</th>
<th class="col md-col">Activity</th>
<th class="col lg-col">Ticket</th>
<th class="col lg-col">Description</th>
<th class="col lg-col">Technologies</th>
</tr>
</thead>
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
<tbody *ngIf="!dataSource.isLoading">
<tr class="d-flex col-height" *ngFor="let entry of dataSource.data">
<td class="col md-col"><mat-checkbox
(change)="sumHoursEntriesSelected(entry, $event.checked)"></mat-checkbox></td>
<td class="hidden-col">{{ entry.id }}</td>
<td class="col md-col">{{ entry.owner_email }}</td>
<td class="col sm-col">
{{ entry.start_date | date: 'MM/dd/yyyy' }}
</td>
<td class="col sm-col">
{{ entry.end_date | substractDate: entry.start_date }}
</td>
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.start_date,entry.timezone_offset) }}</td>
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.end_date , entry.timezone_offset) }}</td>
<td class="col md-col">{{ entry.project_name }}</td>
<td class="hidden-col">{{ entry.project_id }}</td>
<td class="col md-col">{{ entry.customer_name }}</td>
<td class="hidden-col">{{ entry.customer_id }}</td>
<td class="col md-col">{{ entry.activity_name }}</td>
<td class="col lg-col">
<ng-container *ngIf="entry.uri !== null">
<a [class.is-url]="isURL(entry.uri)" (click)="openURLInNewTab(entry.uri)">
<table
class="table table-striped mb-0"
datatable
[dtTrigger]="dtTrigger"
[dtOptions]="dtOptions"
*ngIf="reportDataSource$ | async as dataSource"
>
<thead class="thead-blue">
<tr class="d-flex">
<th class="col md-col">Selected</th>
<th class="hidden-col">ID</th>
<th class="col md-col">User email</th>
<th class="col sm-col">Date</th>
<th class="col sm-col" title="Duration (hours)">Duration</th>
<th class="col x-sm-col" title="Time in">Time in</th>
<th class="col x-sm-col" title="Time out">Time out</th>
<th class="col md-col">Project</th>
<th class="hidden-col">Project ID</th>
<th class="col md-col">Customer</th>
<th class="hidden-col">Customer ID</th>
<th class="col md-col">Activity</th>
<th class="col lg-col">Ticket</th>
<th class="col lg-col">Description</th>
<th class="col lg-col">Technologies</th>
</tr>
</thead>
<app-loading-bar *ngIf="dataSource.isLoading"></app-loading-bar>
<tbody *ngIf="!dataSource.isLoading">
<tr class="d-flex col-height" *ngFor="let entry of dataSource.data">
<td class="col md-col">
<mat-checkbox (change)="sumHoursEntriesSelected(entry, $event.checked)"></mat-checkbox>
</td>
<td class="hidden-col">{{ entry.id }}</td>
<td class="col md-col">{{ entry.owner_email }}</td>
<td class="col sm-col">
{{ entry.start_date | date: 'MM/dd/yyyy' }}
</td>
<td class="col sm-col">
{{ entry.end_date | substractDateDisplayAsFloat: entry.start_date }}
</td>
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.start_date, entry.timezone_offset) }}</td>
<td class="col x-sm-col">{{ dateTimeOffset.parseDateTimeOffset(entry.end_date, entry.timezone_offset) }}</td>
<td class="col md-col">{{ entry.project_name }}</td>
<td class="hidden-col">{{ entry.project_id }}</td>
<td class="col md-col">{{ entry.customer_name }}</td>
<td class="hidden-col">{{ entry.customer_id }}</td>
<td class="col md-col">{{ entry.activity_name }}</td>
<td class="col lg-col">
<ng-container *ngIf="entry.uri !== null">
<a [class.is-url]="isURL(entry.uri)" (click)="openURLInNewTab(entry.uri)">
{{ entry.uri }}
</a>
</ng-container>
</td>
<td class="col lg-scroll">{{ entry.description }}</td>
<td class="col lg-scroll">
<ng-container *ngIf="entry.technologies.length > 0">
<div *ngFor="let technology of entry.technologies" class="badge bg-secondary text-wrap">
{{ technology }}
</div>
</ng-container>
</td>
</tr>
</tbody>
</table>
</ng-container>
</td>
<td class="col lg-scroll">{{ entry.description }}</td>
<td class="col lg-scroll">
<ng-container *ngIf="entry.technologies.length > 0">
<div *ngFor="let technology of entry.technologies" class="badge bg-secondary text-wrap">
{{ technology }}
</div>
</ng-container>
</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-dark mt-3">
Total: {{ this.resultSum.hours }} hours, {{ this.resultSum.minutes }} minutes, <br />
Total hours entries selected: {{ resultSumEntriesSelected.hours }} hours,
{{ resultSumEntriesSelected.minutes }} minutes
</div>
<div class="alert alert-dark mt-3">Total: {{this.resultSum.hours}} hours, {{this.resultSum.minutes}} minutes,
<br/> Total hours entries selected: {{resultSumEntriesSelected.hours}} hours, {{resultSumEntriesSelected.minutes}} minutes</div>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DataTablesModule } from 'angular-datatables';
import { NgxPaginationModule } from 'ngx-pagination';
import { Entry } from 'src/app/modules/shared/models';
import { SubstractDatePipe } from 'src/app/modules/shared/pipes/substract-date/substract-date.pipe';
import { SubstractDatePipeDisplayAsFloat } from 'src/app/modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe';
import { getReportDataSource, getResultSumEntriesSelected } from 'src/app/modules/time-clock/store/entry.selectors';
import { EntryState } from '../../../time-clock/store/entry.reducer';
import { TimeEntriesTableComponent } from './time-entries-table.component';
Expand Down Expand Up @@ -80,7 +81,7 @@ describe('Reports Page', () => {
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NgxPaginationModule, DataTablesModule],
declarations: [TimeEntriesTableComponent, SubstractDatePipe],
declarations: [TimeEntriesTableComponent, SubstractDatePipe, SubstractDatePipeDisplayAsFloat],
providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }],
}).compileComponents();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,3 @@ export class TimeEntriesTableComponent implements OnInit, OnDestroy, AfterViewIn
return this.resultSumEntriesSelected;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ describe('SpinnerInterceptorService test', () => {
],
});

class MockHttpHandler implements HttpHandler {
handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
return of(new HttpResponse());
}
class MockHttpHandler implements HttpHandler {
handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
return of(new HttpResponse());
}
}

let overlay: Overlay;
let httpHandler: HttpHandler;
Expand Down
7 changes: 3 additions & 4 deletions src/app/modules/shared/interceptors/spinner.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ export class SpinnerInterceptor implements HttpInterceptor {
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if(req.url.endsWith('recent')){
if (req.url.endsWith('recent')) {
const spinnerSubscription: Subscription = this.spinnerOverlayService.spinner$.subscribe();
return next
return next
.handle(req)
.pipe(finalize(() => spinnerSubscription.unsubscribe()));
}else{
} else {
return next.handle(req);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { SubstractDatePipeDisplayAsFloat } from './substract-date-return-float.pipe';

describe('SubstractDatePipeDisplayAsFloat', () => {
it('create an instance', () => {
const pipe = new SubstractDatePipeDisplayAsFloat();
expect(pipe).toBeTruthy();
});

/*TODO: tests will be more robust if they take into account FIXED_POINT_DIGITS*/
it('returns the date diff as float hours (xx.xx)', () => {
[
{ endDate: '2021-04-11T10:20:00Z', startDate: '2021-04-11T08:00:00Z', expectedDiff: '2.33' },
{ endDate: '2021-04-11T17:40:00Z', startDate: '2021-04-11T17:10:00Z', expectedDiff: '0.50' },
{ endDate: '2021-04-11T18:18:00Z', startDate: '2021-04-11T18:00:00Z', expectedDiff: '0.30' },
{ endDate: '2021-04-12T12:18:00Z', startDate: '2021-04-11T10:00:00Z', expectedDiff: '26.30' },
{ endDate: '2021-04-12T10:01:00Z', startDate: '2021-04-12T10:00:00Z', expectedDiff: '0.02' },
{ endDate: '2021-04-11T11:27:00Z', startDate: '2021-04-11T10:03:45Z', expectedDiff: '1.39' },
].forEach(({ startDate, endDate, expectedDiff }) => {
const fromDate = new Date(endDate);
const substractDate = new Date(startDate);

const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate);

expect(diff).toBe(expectedDiff);
});
});

it('returns -.- if fromDate is null', () => {
const fromDate = null;
const substractDate = new Date('2011-04-11T08:00:30Z');

const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate);

expect(diff).toBe('-.-');
});

it('returns -.- if substractDate is null', () => {
const fromDate = new Date('2011-04-11T08:00:30Z');
const substractDate = null;

const diff = new SubstractDatePipeDisplayAsFloat().transform(fromDate, substractDate);

expect(diff).toBe('-.-');
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

const FIXED_POINT_DIGITS = 2;
@Pipe({
name: 'substractDateDisplayAsFloat'
})
export class SubstractDatePipeDisplayAsFloat implements PipeTransform {

transform(fromDate: Date, substractDate: Date): string {

if (fromDate === null || substractDate === null) {
return '-.-';
}

const startDate = moment(substractDate);
const endDate = moment(fromDate);
const duration = this.getTimeDifference(startDate, endDate);
return duration.asHours().toFixed(FIXED_POINT_DIGITS).toString();
}

getTimeDifference(substractDate: moment.Moment, fromDate: moment.Moment): moment.Duration {
return moment.duration(fromDate.diff(substractDate));
}

}
2 changes: 1 addition & 1 deletion src/app/modules/shared/services/spinner-overlay.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { SpinnerOverlayComponent } from './../components/spinner-overlay/spinner
providedIn: 'root',
})
export class SpinnerOverlayService {
public overlayRef: OverlayRef = undefined;
static spinner$: any;
public overlayRef: OverlayRef = undefined;

constructor(private readonly overlay: Overlay) {}

Expand Down