Skip to content

Commit b2815e7

Browse files
committed
feat: #405 loading my last entry
1 parent 0507238 commit b2815e7

File tree

8 files changed

+99
-35
lines changed

8 files changed

+99
-35
lines changed

src/app/modules/shared/models/entry.model.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
export interface Entry {
22
running?: boolean;
3-
id: string;
3+
id?: string;
44
start_date: Date;
55
end_date?: Date;
66
activity_id?: string;
7-
technologies: string[];
7+
technologies?: string[];
88
uri?: string;
99
activity_name?: string;
1010
description?: string;

src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { getActiveTimeEntry } from './../../store/entry.selectors';
1+
import { ActivityManagementActionTypes } from './../../../activities-management/store/activity-management.actions';
2+
import { EntryActionTypes, LoadActiveEntry } from './../../store/entry.actions';
3+
import { filter } from 'rxjs/operators';
24
import { Component, OnInit } from '@angular/core';
35
import { FormBuilder, FormGroup } from '@angular/forms';
4-
import { select, Store } from '@ngrx/store';
6+
import { Store, ActionsSubject } from '@ngrx/store';
57

68
import { Activity, NewEntry } from '../../../shared/models';
79
import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
810
import { TechnologyState } from '../../../shared/store/technology.reducers';
9-
import { ActivityState, allActivities, LoadActivities } from '../../../activities-management/store';
11+
import { ActivityState, LoadActivities } from '../../../activities-management/store';
1012

1113
import * as entryActions from '../../store/entry.actions';
1214

@@ -24,37 +26,44 @@ export class EntryFieldsComponent implements OnInit {
2426
activeEntry;
2527
newData;
2628

27-
constructor(private formBuilder: FormBuilder, private store: Store<Merged>) {
29+
constructor(private formBuilder: FormBuilder, private store: Store<Merged>, private actionsSubject$: ActionsSubject) {
2830
this.entryForm = this.formBuilder.group({
2931
description: '',
3032
uri: '',
31-
activity_id: '-1',
33+
activity_id: '',
3234
});
3335
}
3436

3537
ngOnInit(): void {
3638
this.store.dispatch(new LoadActivities());
37-
const activities$ = this.store.pipe(select(allActivities));
38-
activities$.subscribe((response) => {
39-
this.activities = response;
40-
this.loadActiveEntry();
39+
40+
this.actionsSubject$.pipe(
41+
filter((action: any) => (action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS))
42+
).subscribe((action) => {
43+
this.activities = action.payload;
44+
this.store.dispatch(new LoadActiveEntry());
4145
});
42-
}
4346

44-
loadActiveEntry() {
45-
const activeEntry$ = this.store.pipe(select(getActiveTimeEntry));
46-
activeEntry$.subscribe((response) => {
47-
if (response) {
48-
this.activeEntry = response;
49-
this.setDataToUpdate(this.activeEntry);
50-
this.newData = {
51-
id: this.activeEntry.id,
52-
project_id: this.activeEntry.project_id,
53-
uri: this.activeEntry.uri,
54-
activity_id: this.activeEntry.activity_id,
55-
};
47+
this.actionsSubject$.pipe(
48+
filter((action: any) => (action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS))
49+
).subscribe((action) => {
50+
if (!action.payload.end_date) {
51+
this.store.dispatch(new LoadActiveEntry());
5652
}
5753
});
54+
55+
this.actionsSubject$.pipe(
56+
filter((action: any) => ( action.type === EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS ))
57+
).subscribe((action) => {
58+
this.activeEntry = action.payload;
59+
this.setDataToUpdate(this.activeEntry);
60+
this.newData = {
61+
id: this.activeEntry.id,
62+
project_id: this.activeEntry.project_id,
63+
uri: this.activeEntry.uri,
64+
activity_id: this.activeEntry.activity_id,
65+
};
66+
});
5867
}
5968

6069
get activity_id() {

src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { HttpClientTestingModule } from '@angular/common/http/testing';
2-
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
1+
import { ToastrService, IndividualConfig } from 'ngx-toastr';
2+
import { SwitchTimeEntry, ClockIn } from './../../store/entry.actions';
33
import { FormBuilder } from '@angular/forms';
4-
import { MockStore, provideMockStore } from '@ngrx/store/testing';
4+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
5+
import { provideMockStore, MockStore } from '@ngrx/store/testing';
6+
import { HttpClientTestingModule } from '@angular/common/http/testing';
57
import { AutocompleteLibModule } from 'angular-ng-autocomplete';
6-
import { IndividualConfig, ToastrService } from 'ngx-toastr';
78
import { Subscription } from 'rxjs';
89
import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
910
import { getCustomerProjects } from '../../../customer-management/components/projects/components/store/project.selectors';
1011
import { FilterProjectPipe } from '../../../shared/pipes';
11-
import { CreateEntry, UpdateEntryRunning } from '../../store/entry.actions';
12-
import { SwitchTimeEntry } from './../../store/entry.actions';
12+
import { UpdateEntryRunning } from '../../store/entry.actions';
1313
import { ProjectListHoverComponent } from './project-list-hover.component';
1414

1515
describe('ProjectListHoverComponent', () => {
@@ -68,7 +68,7 @@ describe('ProjectListHoverComponent', () => {
6868

6969
component.clockIn(1, 'customer', 'project');
7070

71-
expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(CreateEntry));
71+
expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(ClockIn));
7272
});
7373

7474
it('dispatch a UpdateEntryRunning action on updateProject', () => {
@@ -118,6 +118,7 @@ describe('ProjectListHoverComponent', () => {
118118
.toHaveBeenCalledWith({ project_id: 'customer - xyz'});
119119
});
120120

121+
121122
// TODO Fix this test since it is throwing this error
122123
// Expected spy dispatch to have been called with:
123124
// [CreateEntry({ payload: Object({ project_id: '1', start_date: '2020-07-27T22:30:26.743Z', timezone_offset: 300 }),

src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy {
8383
start_date: new Date().toISOString(),
8484
timezone_offset: new Date().getTimezoneOffset(),
8585
};
86-
this.store.dispatch(new entryActions.CreateEntry(entry));
86+
this.store.dispatch(new entryActions.ClockIn(entry));
8787
this.projectsForm.setValue( { project_id: `${customerName} - ${name}`, } );
8888
}
8989

src/app/modules/time-clock/services/entry.service.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,13 @@ describe('EntryService', () => {
123123
const restartEntryRequest = httpMock.expectOne( `${service.baseUrl}/${entry}/restart`);
124124
expect(restartEntryRequest.request.method).toBe('POST');
125125
});
126+
127+
it('entries are found by project id with a limit 2 by default', () => {
128+
const projectId = 'project-id';
129+
130+
service.findEntriesByProjectId(projectId).subscribe();
131+
132+
const restartEntryRequest = httpMock.expectOne( `${service.baseUrl}?limit=2&project_id=${projectId}`);
133+
expect(restartEntryRequest.request.method).toBe('GET');
134+
});
126135
});

src/app/modules/time-clock/services/entry.service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ export class EntryService {
5757
return this.http.get<TimeEntriesSummary>(summaryUrl);
5858
}
5959

60+
findEntriesByProjectId(projectId: string): Observable<Entry[]> {
61+
const findEntriesByProjectURL = `${this.baseUrl}?limit=2&project_id=${projectId}`;
62+
return this.http.get<Entry[]>(findEntriesByProjectURL);
63+
}
64+
6065
loadEntriesByTimeRange(range: TimeEntriesTimeRange, userId: string): Observable<any> {
6166
const MAX_NUMBER_OF_ENTRIES_FOR_REPORTS = 9999;
6267
return this.http.get(this.baseUrl,

src/app/modules/time-clock/store/entry.actions.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export enum EntryActionTypes {
1313
LOAD_ENTRIES = '[Entry] LOAD_ENTRIES',
1414
LOAD_ENTRIES_SUCCESS = '[Entry] LOAD_ENTRIES_SUCCESS',
1515
LOAD_ENTRIES_FAIL = '[Entry] LOAD_ENTRIES_FAIL',
16+
CLOCK_IN = '[Entry] CLOCK_IN',
17+
CLOCK_IN_SUCCESS = '[Entry] CLOCK_IN_SUCCESS',
1618
CREATE_ENTRY = '[Entry] CREATE_ENTRY',
1719
CREATE_ENTRY_SUCCESS = '[Entry] CREATE_ENTRY_SUCCESS',
1820
CREATE_ENTRY_FAIL = '[Entry] CREATE_ENTRY_FAIL',
@@ -39,6 +41,16 @@ export enum EntryActionTypes {
3941
RESTART_ENTRY_FAIL = '[Entry] RESTART_ENTRY_FAIL',
4042
}
4143

44+
export class ClockIn implements Action {
45+
public readonly type = EntryActionTypes.CLOCK_IN;
46+
constructor(readonly payload: NewEntry) {}
47+
}
48+
49+
export class ClockInSuccess implements Action {
50+
public readonly type = EntryActionTypes.CLOCK_IN_SUCCESS;
51+
constructor() {}
52+
}
53+
4254
export class SwitchTimeEntry implements Action {
4355
public readonly type = EntryActionTypes.SWITCH_TIME_ENTRY;
4456
constructor(readonly idEntrySwitching: string, readonly idProjectSwitching) {}
@@ -250,6 +262,8 @@ export class RestartEntryFail implements Action {
250262
}
251263

252264
export type EntryActions =
265+
| ClockIn
266+
| ClockInSuccess
253267
| LoadEntriesSummary
254268
| LoadEntriesSummarySuccess
255269
| LoadEntriesSummaryFail

src/app/modules/time-clock/store/entry.effects.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { NewEntry } from './../../shared/models/entry.model';
2+
import { INFO_DELETE_SUCCESSFULLY, INFO_SAVED_SUCCESSFULLY } from './../../shared/messages';
13
import { Injectable } from '@angular/core';
24
import { Actions, Effect, ofType } from '@ngrx/effects';
35
import { Action } from '@ngrx/store';
46
import { ToastrService } from 'ngx-toastr';
57
import { Observable, of } from 'rxjs';
68
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
79
import { EntryService } from '../services/entry.service';
8-
import { INFO_DELETE_SUCCESSFULLY, INFO_SAVED_SUCCESSFULLY } from './../../shared/messages';
910
import * as actions from './entry.actions';
1011

1112
@Injectable()
@@ -20,7 +21,7 @@ export class EntryEffects {
2021
this.entryService.stopEntryRunning(action.idEntrySwitching).pipe(
2122
map((response) => {
2223
const stopDateForEntry = new Date(response.end_date);
23-
stopDateForEntry.setSeconds(stopDateForEntry.getSeconds() + 1 );
24+
stopDateForEntry.setSeconds(stopDateForEntry.getSeconds() + 1);
2425
return new actions.CreateEntry({
2526
project_id: action.idProjectSwitching,
2627
start_date: stopDateForEntry.toISOString(),
@@ -116,6 +117,31 @@ export class EntryEffects {
116117
)
117118
);
118119

120+
@Effect()
121+
clockIn$: Observable<Action> = this.actions$.pipe(
122+
ofType(actions.EntryActionTypes.CLOCK_IN),
123+
map((action: actions.CreateEntry) => action.payload),
124+
mergeMap((entry: NewEntry) =>
125+
this.entryService.findEntriesByProjectId(entry.project_id).pipe(
126+
map((entriesFound) => {
127+
if (entriesFound && entriesFound.length > 0) {
128+
const dataToUse = entriesFound[0];
129+
entry = { ...entry };
130+
entry.description = dataToUse.description;
131+
entry.technologies = dataToUse.technologies ? dataToUse.technologies : [];
132+
entry.uri = dataToUse.uri;
133+
entry.activity_id = dataToUse.activity_id;
134+
}
135+
return new actions.CreateEntry(entry);
136+
}),
137+
catchError((error) => {
138+
this.toastrService.error('We could not clock in you, try again later.');
139+
return of(new actions.CreateEntryFail('Error'));
140+
})
141+
)
142+
)
143+
);
144+
119145
@Effect()
120146
deleteEntry$: Observable<Action> = this.actions$.pipe(
121147
ofType(actions.EntryActionTypes.DELETE_ENTRY),
@@ -214,7 +240,7 @@ export class EntryEffects {
214240
return new actions.RestartEntrySuccess(entryResponse);
215241
}),
216242
catchError((error) => {
217-
this.toastrService.error( error.error.message, 'This entry could not be restarted');
243+
this.toastrService.error(error.error.message, 'This entry could not be restarted');
218244
return of(new actions.RestartEntryFail(error));
219245
})
220246
)

0 commit comments

Comments
 (0)