diff --git a/package-lock.json b/package-lock.json
index 336ab149c..46642683c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "time-tracker",
- "version": "1.46.1",
+ "version": "1.47.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -6142,6 +6142,15 @@
"p-try": "^2.0.0"
}
},
+ "serialize-javascript": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
+ "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
"ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
@@ -18472,15 +18481,6 @@
}
}
},
- "serialize-javascript": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
- "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
- "dev": true,
- "requires": {
- "randombytes": "^2.1.0"
- }
- },
"serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
@@ -19902,9 +19902,9 @@
}
},
"serialize-javascript": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
- "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
+ "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
@@ -21132,9 +21132,9 @@
},
"dependencies": {
"serialize-javascript": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
- "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
+ "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
diff --git a/package.json b/package.json
index 7ac6015d7..3d2589351 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "time-tracker",
- "version": "1.46.1",
+ "version": "1.47.0",
"scripts": {
"preinstall": "npx npm-force-resolutions",
"ng": "ng",
@@ -106,8 +106,8 @@
},
"config": {
"commit-message-validator": {
- "pattern": "^(fix: TT-|feat: TT-|perf: TT-|build: TT-|ci: TT-|docs: TT-|refactor: TT-|style: TT-|test: TT-)[0-9].*",
- "errorMessage": "Your commit message needs to start with fix: , feat:, or perf: followed by any commit message, e.g. fix: TT-43 any commit message"
+ "pattern": "^(fix: TT-|feat: TT-|perf: TT-|build: TT-|ci: TT-|docs: TT-|refactor: TT-|style: TT-|test: TT-|code-smell: TT-)[0-9].*",
+ "errorMessage": "Your commit message needs to start with fix: , feat:, or perf: followed by any commit message, e.g. fix: TT-43 any commit message."
}
},
"resolutions": {
diff --git a/src/app/modules/time-entries/components/calendar/calendar.component.html b/src/app/modules/time-entries/components/calendar/calendar.component.html
index 08164362c..f2364ddc3 100644
--- a/src/app/modules/time-entries/components/calendar/calendar.component.html
+++ b/src/app/modules/time-entries/components/calendar/calendar.component.html
@@ -128,7 +128,7 @@
-
+
{
let fakeEntry: Entry;
let fakeEntryRunning: Entry;
- beforeEach(waitForAsync( () => {
- TestBed.configureTestingModule({
- declarations: [ CalendarComponent ]
+ beforeEach(
+ waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [CalendarComponent],
+ }).compileComponents();
+
+ currentDate = moment();
+ fakeEntry = {
+ id: 'entry_1',
+ project_id: 'abc',
+ project_name: 'Time-tracker',
+ start_date: new Date('2020-02-05T15:36:15.887Z'),
+ end_date: new Date('2020-02-05T18:36:15.887Z'),
+ customer_name: 'ioet Inc.',
+ activity_id: 'development',
+ technologies: ['Angular', 'TypeScript'],
+ description: 'No comments',
+ uri: 'EY-25',
+ };
+ fakeEntryRunning = {
+ id: 'entry_1',
+ project_id: 'abc',
+ project_name: 'Time-tracker',
+ start_date: new Date('2020-02-05T15:36:15.887Z'),
+ end_date: null,
+ customer_name: 'ioet Inc.',
+ activity_id: 'development',
+ technologies: ['Angular', 'TypeScript'],
+ description: 'No comments',
+ uri: 'EY-25',
+ };
+
+ jasmine.clock().mockDate(currentDate.toDate());
})
- .compileComponents();
-
- currentDate = moment();
- fakeEntry = {
- id: 'entry_1',
- project_id: 'abc',
- project_name: 'Time-tracker',
- start_date: new Date('2020-02-05T15:36:15.887Z'),
- end_date: new Date('2020-02-05T18:36:15.887Z'),
- customer_name: 'ioet Inc.',
- activity_id: 'development',
- technologies: ['Angular', 'TypeScript'],
- description: 'No comments',
- uri: 'EY-25',
- };
- fakeEntryRunning = {
- id: 'entry_1',
- project_id: 'abc',
- project_name: 'Time-tracker',
- start_date: new Date('2020-02-05T15:36:15.887Z'),
- end_date: null,
- customer_name: 'ioet Inc.',
- activity_id: 'development',
- technologies: ['Angular', 'TypeScript'],
- description: 'No comments',
- uri: 'EY-25',
- };
-
- jasmine.clock().mockDate(currentDate.toDate());
- }));
+ );
beforeEach(() => {
fixture = TestBed.createComponent(CalendarComponent);
@@ -83,11 +84,11 @@ describe('CalendarComponent', () => {
end: fakeEntry.end_date,
title: fakeEntry.description,
id: fakeEntry.id,
- meta: fakeEntry
+ meta: fakeEntry,
};
const fakeDatasource = {
isLoading: false,
- data: [fakeEntry]
+ data: [fakeEntry],
};
const fakeTimeEntries = of(fakeDatasource);
const expectedtimeEntriesAsEvent = [fakeTimeEntryAsEvent];
@@ -104,11 +105,11 @@ describe('CalendarComponent', () => {
end: fakeEntryRunning.end_date,
title: fakeEntryRunning.description,
id: fakeEntryRunning.id,
- meta: fakeEntryRunning
+ meta: fakeEntryRunning,
};
const fakeDatasource = {
isLoading: false,
- data: [fakeEntryRunning]
+ data: [fakeEntryRunning],
};
const fakeTimeEntries = of(fakeDatasource);
const expectedtimeEntriesAsEvent = [fakeTimeEntryAsEvent];
@@ -141,7 +142,7 @@ describe('CalendarComponent', () => {
end: fakeEntry.end_date,
title: fakeEntry.description,
id: fakeEntry.id,
- meta: fakeEntry
+ meta: fakeEntry,
};
const fakeValueEmit = {
id: fakeEntry.id,
@@ -159,7 +160,7 @@ describe('CalendarComponent', () => {
end: fakeEntry.end_date,
title: fakeEntry.description,
id: fakeEntry.id,
- meta: fakeEntry
+ meta: fakeEntry,
};
const fakeValueEmit = {
timeEntry: fakeEntry,
@@ -195,6 +196,13 @@ describe('CalendarComponent', () => {
expect(component.calendarView).toEqual(fakeCalendarView);
});
+ it('set srcoll to current time marker in calendarView when is call scrollToCurrentTimeMarker', () => {
+ const fakeCalendarView: CalendarView = CalendarView.Week;
+ spyOn(component, 'scrollToCurrentTimeMarker');
+ component.changeCalendarView(fakeCalendarView);
+ expect(component.scrollToCurrentTimeMarker).toHaveBeenCalled();
+ });
+
it('set false in nextDateDisabled when call navigationEnable and calendarView != Month and currentDate + 1 day is not greater to initialDate', () => {
component.currentDate = moment().subtract(2, 'day').toDate();
component.initialDate = moment().toDate();
diff --git a/src/app/modules/time-entries/components/calendar/calendar.component.ts b/src/app/modules/time-entries/components/calendar/calendar.component.ts
index 76c118fef..627b06b0b 100644
--- a/src/app/modules/time-entries/components/calendar/calendar.component.ts
+++ b/src/app/modules/time-entries/components/calendar/calendar.component.ts
@@ -1,14 +1,21 @@
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import {
- CalendarEvent,
- CalendarView,
-} from 'angular-calendar';
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnInit,
+ Output,
+ ViewChild,
+} from '@angular/core';
+import { CalendarEvent, CalendarView } from 'angular-calendar';
import { Observable } from 'rxjs';
import * as moment from 'moment';
import { DataSource } from '../../../shared/models/data-source.model';
import { Entry } from 'src/app/modules/shared/models';
import { map } from 'rxjs/operators';
import { SubstractDatePipe } from 'src/app/modules/shared/pipes/substract-date/substract-date.pipe';
+import { differenceInMinutes, startOfDay, startOfHour } from 'date-fns';
@Component({
selector: 'app-calendar',
@@ -16,7 +23,12 @@ import { SubstractDatePipe } from 'src/app/modules/shared/pipes/substract-date/s
styleUrls: ['./calendar.component.scss'],
})
export class CalendarComponent implements OnInit {
- @Input() set timeEntries$(timeEntries: Observable>){
+ readonly DEFAULT_HEADER_HEIGHT = 52;
+ readonly VARIATION_HEIGHT: number = 2;
+
+ @ViewChild('scrollContainer') scrollContainer: ElementRef;
+
+ @Input() set timeEntries$(timeEntries: Observable>) {
this.castEntryToCalendarEvent(timeEntries);
}
@Input() calendarView: CalendarView = CalendarView.Month;
@@ -25,7 +37,7 @@ export class CalendarComponent implements OnInit {
@Output() viewModal: EventEmitter = new EventEmitter();
@Output() deleteTimeEntry: EventEmitter = new EventEmitter();
@Output() changeDate: EventEmitter = new EventEmitter<{
- date: Date
+ date: Date;
}>();
initialDate: Date;
@@ -34,97 +46,113 @@ export class CalendarComponent implements OnInit {
timeEntriesAsEvent: CalendarEvent[];
nextDateDisabled: boolean;
- constructor() {
+ constructor(private referenceChangeDetector: ChangeDetectorRef) {
this.initialDate = new Date();
this.previusDate = new Date();
this.isToday = false;
this.timeEntriesAsEvent = [];
this.nextDateDisabled = true;
- }
+ }
ngOnInit(): void {
this.isToday = this.isVisibleForCurrentDate();
this.navigationEnable(this.calendarView);
}
- get CalendarViewEnum(): typeof CalendarView{
+ get CalendarViewEnum(): typeof CalendarView {
return CalendarView;
}
+ scrollToCurrentTimeMarker() {
+ if (this.calendarView === CalendarView.Week || CalendarView.Day) {
+ const minutesSinceStartOfDay = differenceInMinutes(startOfHour(this.currentDate), startOfDay(this.currentDate));
+ const headerHeight = this.calendarView === CalendarView.Week ? this.DEFAULT_HEADER_HEIGHT : 0;
+ this.scrollContainer.nativeElement.scrollTop = minutesSinceStartOfDay * this.VARIATION_HEIGHT + headerHeight;
+ }
+ }
+
castEntryToCalendarEvent(timeEntries$: Observable>) {
- timeEntries$.pipe(
- map((timeEntriesDatasorce) => timeEntriesDatasorce.data.map(
- (timeEntries) => ({
- start: new Date(timeEntries.start_date),
- end: timeEntries.end_date ? new Date(timeEntries.end_date) : null ,
- title: timeEntries.description,
- id: timeEntries.id,
- meta: timeEntries
- } as CalendarEvent)
+ timeEntries$
+ .pipe(
+ map((timeEntriesDatasorce) =>
+ timeEntriesDatasorce.data.map(
+ (timeEntries) =>
+ ({
+ start: new Date(timeEntries.start_date),
+ end: timeEntries.end_date ? new Date(timeEntries.end_date) : null,
+ title: timeEntries.description,
+ id: timeEntries.id,
+ meta: timeEntries,
+ } as CalendarEvent)
+ )
)
)
- )
- .subscribe((timeEntriesAsEvent) => {
+ .subscribe((timeEntriesAsEvent) => {
this.timeEntriesAsEvent = [...timeEntriesAsEvent].reverse();
- });
+ });
}
handleEditEvent(timeEntryAsEvent: CalendarEvent): void {
- this.viewModal.emit( {
- id: timeEntryAsEvent.id
+ this.viewModal.emit({
+ id: timeEntryAsEvent.id,
});
}
handleDeleteEvent(timeEntryAsEvent: CalendarEvent): void {
this.deleteTimeEntry.emit({
- timeEntry: timeEntryAsEvent.meta
+ timeEntry: timeEntryAsEvent.meta,
});
}
- handleChangeDateEvent(): void{
+ handleChangeDateEvent(): void {
const date = this.currentDate;
this.isToday = this.isVisibleForCurrentDate();
this.navigationEnable(this.calendarView);
- this.changeDate.emit({date});
+ this.changeDate.emit({ date });
}
- changeCalendarView(calendarView: CalendarView){
+ changeCalendarView(calendarView: CalendarView) {
this.calendarView = calendarView;
+ this.scrollContainer.nativeElement.scrollTop = 0;
+ if (this.calendarView !== CalendarView.Month) {
+ this.referenceChangeDetector.detectChanges();
+ this.scrollToCurrentTimeMarker();
+ }
}
- navigationEnable(calendarView: CalendarView){
+ navigationEnable(calendarView: CalendarView) {
let enable = false;
const currentDate = moment(this.currentDate);
const initialDate = moment(this.initialDate);
- if (calendarView === CalendarView.Month){
+ if (calendarView === CalendarView.Month) {
if (currentDate.month() === initialDate.month() && currentDate.year() === initialDate.year()) {
enable = true;
}
}
currentDate.add(1, 'day');
- if (currentDate > initialDate){
+ if (currentDate > initialDate) {
enable = true;
}
this.nextDateDisabled = enable;
}
- getTimeWork(startDate: Date, endDate: Date): number{
- if (!endDate){
+ getTimeWork(startDate: Date, endDate: Date): number {
+ if (!endDate) {
return 30;
}
- return new SubstractDatePipe().transformInMinutes( endDate , startDate);
+ return new SubstractDatePipe().transformInMinutes(endDate, startDate);
}
- timeIsGreaterThan(startDate: Date, endDate: Date, timeRange: number ): boolean{
+ timeIsGreaterThan(startDate: Date, endDate: Date, timeRange: number): boolean {
const timeWorkInMinutes = this.getTimeWork(startDate, endDate);
return timeWorkInMinutes > timeRange;
}
- isVisibleForCurrentView(currentCalendarView: CalendarView, desiredView: CalendarView ): boolean{
+ isVisibleForCurrentView(currentCalendarView: CalendarView, desiredView: CalendarView): boolean {
return currentCalendarView === desiredView;
}
- isVisibleForCurrentDate(): boolean{
+ isVisibleForCurrentDate(): boolean {
const currentDate: Date = new Date(this.currentDate);
const initialDate: Date = new Date(this.initialDate);
return currentDate.setHours(0, 0, 0, 0) === initialDate.setHours(0, 0, 0, 0);
diff --git a/src/app/modules/time-entries/pages/time-entries.component.html b/src/app/modules/time-entries/pages/time-entries.component.html
index 1711ea0b8..df2e1869b 100644
--- a/src/app/modules/time-entries/pages/time-entries.component.html
+++ b/src/app/modules/time-entries/pages/time-entries.component.html
@@ -42,7 +42,7 @@
-
+
Date |
diff --git a/src/app/modules/time-entries/pages/time-entries.component.spec.ts b/src/app/modules/time-entries/pages/time-entries.component.spec.ts
index 2406a1a59..4ff687dd6 100644
--- a/src/app/modules/time-entries/pages/time-entries.component.spec.ts
+++ b/src/app/modules/time-entries/pages/time-entries.component.spec.ts
@@ -668,4 +668,11 @@ describe('TimeEntriesComponent', () => {
expect(HTMLTimeEntriesView).not.toBeNull();
});
+
+ it('after the component is initialized it should initialize the table', () => {
+ spyOn(component.dtTrigger, 'next');
+ component.ngAfterViewInit();
+
+ expect(component.dtTrigger.next).toHaveBeenCalled();
+ });
});
diff --git a/src/app/modules/time-entries/pages/time-entries.component.ts b/src/app/modules/time-entries/pages/time-entries.component.ts
index 7ba274bbe..3fdc19aba 100644
--- a/src/app/modules/time-entries/pages/time-entries.component.ts
+++ b/src/app/modules/time-entries/pages/time-entries.component.ts
@@ -1,7 +1,7 @@
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Component, OnDestroy, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { ActionsSubject, select, Store } from '@ngrx/store';
import { ToastrService } from 'ngx-toastr';
-import { Observable, Subscription } from 'rxjs';
+import { Observable, Subscription, Subject } from 'rxjs';
import { delay, filter } from 'rxjs/operators';
import { ProjectSelectedEvent } from '../../shared/components/details-fields/project-selected-event';
import { SaveEntryEvent } from '../../shared/components/details-fields/save-entry-event';
@@ -14,12 +14,13 @@ import { EntryActionTypes } from './../../time-clock/store/entry.actions';
import { getActiveTimeEntry, getTimeEntriesDataSource } from './../../time-clock/store/entry.selectors';
import { CookieService } from 'ngx-cookie-service';
import { FeatureToggle } from './../../../../environments/enum';
+import { DataTableDirective } from 'angular-datatables';
@Component({
selector: 'app-time-entries',
templateUrl: './time-entries.component.html',
styleUrls: ['./time-entries.component.scss'],
})
-export class TimeEntriesComponent implements OnInit, OnDestroy {
+export class TimeEntriesComponent implements OnInit, OnDestroy, AfterViewInit {
entryId: string;
entry: Entry;
activeTimeEntry: Entry;
@@ -38,6 +39,11 @@ export class TimeEntriesComponent implements OnInit, OnDestroy {
selectedYear: number;
selectedMonthAsText: string;
isActiveEntryOverlapping = false;
+ dtOptions: any = {};
+ dtTrigger: Subject = new Subject();
+ @ViewChild(DataTableDirective, { static: false })
+ dtElement: DataTableDirective;
+ rerenderTableSubscription: Subscription;
constructor(
private store: Store,
private toastrService: ToastrService,
@@ -49,8 +55,18 @@ export class TimeEntriesComponent implements OnInit, OnDestroy {
}
ngOnDestroy(): void {
this.entriesSubscription.unsubscribe();
+ this.rerenderTableSubscription.unsubscribe();
+ this.dtTrigger.unsubscribe();
}
ngOnInit(): void {
+ this.dtOptions = {
+ scrollY: '325px',
+ paging: false,
+ responsive: true,
+ };
+ this.rerenderTableSubscription = this.timeEntriesDataSource$.subscribe((ds) => {
+ this.rerenderDataTable();
+ });
this.loadActiveEntry();
this.isFeatureToggleCalendarActive = (this.cookiesService.get(FeatureToggle.TIME_TRACKER_CALENDAR) === 'true');
this.entriesSubscription = this.actionsSubject$.pipe(
@@ -65,6 +81,9 @@ export class TimeEntriesComponent implements OnInit, OnDestroy {
this.store.dispatch(new entryActions.LoadEntries(this.selectedMonth, this.selectedYear));
});
}
+ ngAfterViewInit(): void {
+ this.rerenderDataTable();
+ }
newEntry() {
if (this.wasEditingExistingTimeEntry) {
this.entry = null;
@@ -216,4 +235,15 @@ export class TimeEntriesComponent implements OnInit, OnDestroy {
});
}
}
+
+ private rerenderDataTable(): void {
+ if (this.dtElement && this.dtElement.dtInstance) {
+ this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
+ dtInstance.destroy();
+ this.dtTrigger.next();
+ });
+ } else {
+ this.dtTrigger.next();
+ }
+ }
}