diff --git a/src/app/modules/shared/components/month-picker/month-picker.component.html b/src/app/modules/shared/components/month-picker/month-picker.component.html index 204ecb6d7..8bb7c7901 100644 --- a/src/app/modules/shared/components/month-picker/month-picker.component.html +++ b/src/app/modules/shared/components/month-picker/month-picker.component.html @@ -1,10 +1,22 @@ -
-
-
{{ month }}
+
+
+ +

+ {{selectedYearText}} +

+ +
+
+
+
+
diff --git a/src/app/modules/shared/components/month-picker/month-picker.component.scss b/src/app/modules/shared/components/month-picker/month-picker.component.scss index f4123a4b3..fc1a00665 100644 --- a/src/app/modules/shared/components/month-picker/month-picker.component.scss +++ b/src/app/modules/shared/components/month-picker/month-picker.component.scss @@ -5,6 +5,9 @@ line-height: 1.2em; font-weight: bold; } +.content-months { + margin: 0; +} .month { background: $primary; @@ -21,5 +24,7 @@ @include highlight(); border-radius: 0.2em; text-decoration: underline; - +} +.spacing { + padding: 0.1em; } diff --git a/src/app/modules/shared/components/month-picker/month-picker.component.spec.ts b/src/app/modules/shared/components/month-picker/month-picker.component.spec.ts index c8d29053e..ea46623f0 100644 --- a/src/app/modules/shared/components/month-picker/month-picker.component.spec.ts +++ b/src/app/modules/shared/components/month-picker/month-picker.component.spec.ts @@ -1,4 +1,6 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import * as moment from 'moment'; +import { NEVER } from 'rxjs'; import { MonthPickerComponent } from './month-picker.component'; @@ -23,10 +25,46 @@ describe('MonthPickerComponent', () => { expect(component).toBeTruthy(); }); - it('should emit activeMonth event', () => { - const month = 2; - spyOn(component.monthSelected, 'emit'); - component.getMonth(month); - expect(component.monthSelected.emit).toHaveBeenCalledWith(month + 1); + it('should emit monthIndex and year', () => { + const month = new Date().getMonth(); + const year = new Date().getFullYear(); + + spyOn(component.dateSelected, 'emit'); + expect(component.dateSelected.emit({ monthIndex: month, year: year })); }); + + + it('should add a year to current year', () => { + component.selectedYearMoment = moment(); + const incrementYear = moment().add(1, 'year').format('Y'); + spyOn(component, 'increment').and.callThrough(); + + component.increment(); + + expect(component.increment).toHaveBeenCalled(); + expect(component.selectedYearText).toEqual(incrementYear); + expect(component.selectedYearMoment.format('Y')).toEqual(incrementYear); + }); + + + it('should subtract a year to current year', () => { + component.selectedYearMoment = moment(); + const decrementYear = moment().subtract(1, 'year').format('Y'); + spyOn(component, 'decrement').and.callThrough(); + + component.decrement(); + + expect(component.decrement).toHaveBeenCalled(); + expect(component.selectedYearMoment.format('Y')).toEqual(decrementYear); + }); + + + it('selectMonth should call selectDates', () => { + spyOn(component, 'selectDate'); + + component.selectMonth(8); + + expect(component.selectDate).toHaveBeenCalledWith(8, 2020); + }); + }); diff --git a/src/app/modules/shared/components/month-picker/month-picker.component.ts b/src/app/modules/shared/components/month-picker/month-picker.component.ts index 1a404f058..ccea2b70d 100644 --- a/src/app/modules/shared/components/month-picker/month-picker.component.ts +++ b/src/app/modules/shared/components/month-picker/month-picker.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import * as moment from 'moment'; @Component({ selector: 'app-month-picker', @@ -6,18 +7,62 @@ import { Component, OnInit, Output, EventEmitter } from '@angular/core'; styleUrls: ['./month-picker.component.scss'] }) export class MonthPickerComponent implements OnInit { - @Output() monthSelected = new EventEmitter(); - activeMonth: number; - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + @Output() dateSelected = new EventEmitter<{ + monthIndex: number; + year: number; + }>(); - constructor() { } + selectedMonthMoment: moment.Moment; + selectedMonthIndex: number; + selectedYearMoment: moment.Moment; + selectedYear: number; - ngOnInit(): void { - this.activeMonth = new Date().getMonth(); + selectedYearText: string; + months: Array = []; + years: Array = []; + + constructor() { + this.selectedYearMoment = moment(); + this.selectedMonthMoment = moment(); + this.months = moment.months(); + this.selectedMonthIndex = this.selectedMonthMoment.month(); + this.selectedYear = this.selectedYearMoment.year(); + this.updateYearText(); + } + + ngOnInit() { + this.selectDate(this.selectedMonthIndex, this.selectedYear); + } + + updateYearText() { + this.selectedYearText = moment(this.selectedYearMoment).format('YYYY'); + } + + increment() { + this.selectedYearMoment = this.selectedYearMoment.add(1, 'year'); + this.updateYearText(); } - getMonth(month: number) { - this.monthSelected.emit(month + 1); - this.activeMonth = month; + decrement() { + this.selectedYearMoment = this.selectedYearMoment.subtract(1, 'year'); + this.updateYearText(); + } + + selectMonth(monthIndex: number) { + this.selectedMonthIndex = monthIndex; + this.selectedYear = this.selectedYearMoment.year(); + this.selectDate(this.selectedMonthIndex, this.selectedYear); + } + + isSelectedMonth(monthIndex: number) { + return ( + this.selectedMonthIndex === monthIndex && + this.selectedYear === this.selectedYearMoment.year() + ); + } + + selectDate(monthIndex: number, year: number) { + this.dateSelected.emit({ monthIndex, year }); } } + diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts index ebcc5531d..d5327d8c1 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts @@ -50,7 +50,7 @@ export class EntryFieldsComponent implements OnInit { ngOnInit(): void { this.store.dispatch(new LoadActivities()); - this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1)); + this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1, new Date().getFullYear())); this.actionsSubject$ .pipe(filter((action: any) => action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS)) .subscribe((action) => { diff --git a/src/app/modules/time-clock/services/entry.service.spec.ts b/src/app/modules/time-clock/services/entry.service.spec.ts index 9979c2566..ca2630120 100644 --- a/src/app/modules/time-clock/services/entry.service.spec.ts +++ b/src/app/modules/time-clock/services/entry.service.spec.ts @@ -54,11 +54,13 @@ describe('EntryService', () => { }); it('load all Entries', () => { + const year = new Date().getFullYear(); const month = new Date().getMonth(); + const timezoneOffset = new Date().getTimezoneOffset(); - service.loadEntries(month).subscribe(); + service.loadEntries({ year, month }).subscribe(); - const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}?month=${month}&timezone_offset=${timezoneOffset}`); + const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}?month=${month}&year=${year}&timezone_offset=${timezoneOffset}`); expect(loadEntryRequest.request.method).toBe('GET'); }); diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts index 6f2a96ad2..2506b4a5b 100644 --- a/src/app/modules/time-clock/services/entry.service.ts +++ b/src/app/modules/time-clock/services/entry.service.ts @@ -23,9 +23,9 @@ export class EntryService { return this.http.get(`${this.baseUrl}/running`); } - loadEntries(month): Observable { + loadEntries(date): Observable { const timezoneOffset = new Date().getTimezoneOffset(); - return this.http.get(`${this.baseUrl}?month=${month}&timezone_offset=${timezoneOffset}`); + return this.http.get(`${this.baseUrl}?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`); } createEntry(entryData): Observable { diff --git a/src/app/modules/time-clock/store/entry.actions.ts b/src/app/modules/time-clock/store/entry.actions.ts index abdb9c75f..f58378ffa 100644 --- a/src/app/modules/time-clock/store/entry.actions.ts +++ b/src/app/modules/time-clock/store/entry.actions.ts @@ -99,7 +99,7 @@ export class LoadActiveEntryFail implements Action { export class LoadEntries implements Action { public readonly type = EntryActionTypes.LOAD_ENTRIES; - constructor(public month: number) { + constructor(public month: number, public year: number) { } } diff --git a/src/app/modules/time-clock/store/entry.effects.ts b/src/app/modules/time-clock/store/entry.effects.ts index 806ec9a19..58aa988ad 100644 --- a/src/app/modules/time-clock/store/entry.effects.ts +++ b/src/app/modules/time-clock/store/entry.effects.ts @@ -87,9 +87,9 @@ export class EntryEffects { @Effect() loadEntries$: Observable = this.actions$.pipe( ofType(actions.EntryActionTypes.LOAD_ENTRIES), - map((action: actions.LoadEntries) => action.month), - mergeMap((month) => - this.entryService.loadEntries(month).pipe( + map((action: actions.LoadEntries) => action), + mergeMap((date) => + this.entryService.loadEntries({ month: date.month, year: date.year }).pipe( map((entries) => new actions.LoadEntriesSuccess(entries)), catchError((error) => { this.toastrService.warning(`The data could not be loaded`); @@ -226,7 +226,10 @@ export class EntryEffects { ofType(actions.EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY), map((action: actions.UpdateCurrentOrLastEntry) => action.payload), switchMap((entry) => - this.entryService.loadEntries(new Date().getMonth() + 1).pipe( + this.entryService.loadEntries({ + month : new Date().getMonth() + 1, + year: new Date().getFullYear() + }).pipe( map((entries) => { const lastEntry = entries[1]; const isStartTimeInLastEntry = moment(entry.start_date).isBefore(lastEntry.end_date); diff --git a/src/app/modules/time-clock/store/entry.reducer.spec.ts b/src/app/modules/time-clock/store/entry.reducer.spec.ts index ecc683589..dafce2ee7 100644 --- a/src/app/modules/time-clock/store/entry.reducer.spec.ts +++ b/src/app/modules/time-clock/store/entry.reducer.spec.ts @@ -107,7 +107,9 @@ describe('entryReducer', () => { }); it('on LoadEntries, isLoading is true', () => { - const action = new actions.LoadEntries(new Date().getMonth() + 1); + const month = 12; + const year = 2020; + const action = new actions.LoadEntries(month, year); const state = entryReducer(initialState, action); expect(state.timeEntriesDataSource.isLoading).toEqual(true); }); 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 8928eb656..8fabce8a2 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.html +++ b/src/app/modules/time-entries/pages/time-entries.component.html @@ -13,7 +13,7 @@
- +
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 b2237ce1e..05168742e 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 @@ -127,6 +127,7 @@ describe('TimeEntriesComponent', () => { it('when create time entries, the time entries should be queried', () => { const currentMonth = new Date().getMonth() + 1; + const year = new Date().getFullYear(); const entryToSave = { entry: { project_id: 'project-id', @@ -141,7 +142,7 @@ describe('TimeEntriesComponent', () => { component.saveEntry(entryToSave); expect(store.dispatch).toHaveBeenCalledWith(new entryActions.CreateEntry(entryToSave.entry)); - expect(store.dispatch).toHaveBeenCalledWith(new entryActions.LoadEntries(currentMonth)); + expect(store.dispatch).toHaveBeenCalledWith(new entryActions.LoadEntries(currentMonth, year)); }); it('when creating a new entry, then entryId should be null', () => { @@ -322,11 +323,13 @@ describe('TimeEntriesComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(new entryActions.DeleteEntry('id')); }); - it('should get the entry List by Month', () => { - const month = 1; + it('should get the entry List by Month and year', () => { + const month = new Date().getMonth(); + const year = new Date().getFullYear(); + spyOn(store, 'dispatch'); - component.getMonth(month); - expect(store.dispatch).toHaveBeenCalledWith(new entryActions.LoadEntries(month)); + component.dateSelected({monthIndex: month, year: year}); + expect(store.dispatch).toHaveBeenCalledWith(new entryActions.LoadEntries(month + 1, year)); }); it('doSave when activeTimeEntry === null', waitForAsync(() => { 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 9d564946c..6e86b6067 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.ts +++ b/src/app/modules/time-entries/pages/time-entries.component.ts @@ -8,6 +8,7 @@ import { SaveEntryEvent } from '../../shared/components/details-fields/save-entr import { Entry } from '../../shared/models'; import { DataSource } from '../../shared/models/data-source.model'; import * as entryActions from '../../time-clock/store/entry.actions'; +import * as moment from 'moment'; import { EntryState } from '../../time-clock/store/entry.reducer'; import { EntryActionTypes } from './../../time-clock/store/entry.actions'; import { getActiveTimeEntry, getTimeEntriesDataSource } from './../../time-clock/store/entry.selectors'; @@ -26,6 +27,9 @@ export class TimeEntriesComponent implements OnInit, OnDestroy { entriesSubscription: Subscription; canMarkEntryAsWIP = true; timeEntriesDataSource$: Observable>; + selectedYearAsText: string; + selectedMonthIndex: number; + selectedMonthAsText: string; constructor(private store: Store, private toastrService: ToastrService, private actionsSubject$: ActionsSubject) { this.timeEntriesDataSource$ = this.store.pipe(delay(0), select(getTimeEntriesDataSource)); @@ -36,7 +40,7 @@ export class TimeEntriesComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1)); + this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1, new Date().getFullYear())); this.loadActiveEntry(); this.entriesSubscription = this.actionsSubject$.pipe( @@ -48,7 +52,7 @@ export class TimeEntriesComponent implements OnInit, OnDestroy { ) ).subscribe((action) => { this.loadActiveEntry(); - this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1)); + this.store.dispatch(new entryActions.LoadEntries(this.selectedMonthIndex, new Date().getFullYear())); }); } @@ -161,8 +165,11 @@ export class TimeEntriesComponent implements OnInit, OnDestroy { this.showModal = false; } - getMonth(month: number) { - this.store.dispatch(new entryActions.LoadEntries(month)); + dateSelected(event: { monthIndex: number; year: number }) { + this.selectedYearAsText = event.year.toString(); + this.selectedMonthIndex = event.monthIndex + 1; + this.selectedMonthAsText = moment().month(event.monthIndex).format('MMMM'); + this.store.dispatch(new entryActions.LoadEntries(this.selectedMonthIndex, event.year)); } openModal(item: any) {