Skip to content

Commit 6f07cc7

Browse files
committed
fix: #146 Create new time entry
1 parent 4ca6c8e commit 6f07cc7

File tree

14 files changed

+244
-7
lines changed

14 files changed

+244
-7
lines changed

src/app/app.module.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { ProjectTypeListComponent } from './modules/customer-management/componen
5151
// tslint:disable-next-line: max-line-length
5252
import { CreateProjectTypeComponent } from './modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component';
5353
import { CustomerEffects } from './modules/customer-management/store/customer-management.effects';
54+
import { EntryEffects } from './modules/time-clock/store/entry.effects';
5455
import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.token.interceptor';
5556

5657
@NgModule({
@@ -103,13 +104,22 @@ import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.tok
103104
maxAge: 15, // Retains last 15 states
104105
})
105106
: [],
106-
EffectsModule.forRoot([ProjectEffects, ActivityEffects, CustomerEffects, TechnologyEffects, ProjectTypeEffects]),
107+
EffectsModule.forRoot([
108+
ProjectEffects,
109+
ActivityEffects,
110+
CustomerEffects,
111+
TechnologyEffects,
112+
ProjectTypeEffects,
113+
EntryEffects,
114+
]),
115+
],
116+
providers: [
117+
{
118+
provide: HTTP_INTERCEPTORS,
119+
useClass: InjectTokenInterceptor,
120+
multi: true,
121+
},
107122
],
108-
providers: [{
109-
provide: HTTP_INTERCEPTORS,
110-
useClass: InjectTokenInterceptor,
111-
multi: true,
112-
}],
113123
bootstrap: [AppComponent],
114124
})
115125
export class AppModule {}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ export interface Entry {
88
comments?: string;
99
ticket?: string;
1010
}
11+
12+
export interface NewEntry {
13+
project_id: string;
14+
start_date: string;
15+
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { ProjectListHoverComponent } from './project-list-hover.component';
66
import { ProjectState } from '../../../project-management/store/project.reducer';
77
import { allProjects } from '../../../project-management/store/project.selectors';
88
import { FilterProjectPipe } from '../../../shared/pipes';
9+
import { NewEntry } from '../../../shared/models';
10+
import * as action from '../../store/entry.actions';
911

1012
describe('ProjectListHoverComponent', () => {
1113
let component: ProjectListHoverComponent;
@@ -38,9 +40,16 @@ describe('ProjectListHoverComponent', () => {
3840
expect(component).toBeTruthy();
3941
});
4042

41-
it('should set selectedId with Id', () => {
43+
it('should set selectedId with Id and dispatch CreateEntry action', () => {
44+
spyOn(store, 'dispatch');
4245
const id = 'P1';
46+
const entryData: NewEntry = {
47+
project_id: id,
48+
start_date: new Date().toISOString(),
49+
};
4350
component.clockIn(id);
51+
52+
expect(store.dispatch).toHaveBeenCalledWith(new action.CreateEntry(entryData));
4453
expect(component.selectedId).toBe(id);
4554
});
4655

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Project } from 'src/app/modules/shared/models';
44
import { allProjects } from '../../../project-management/store/project.selectors';
55
import { ProjectState } from '../../../project-management/store/project.reducer';
66
import * as actions from '../../../project-management/store/project.actions';
7+
import * as entryActions from '../../store/entry.actions';
78

89
@Component({
910
selector: 'app-project-list-hover',
@@ -33,6 +34,8 @@ export class ProjectListHoverComponent implements OnInit {
3334
}
3435

3536
clockIn(id: string) {
37+
const newEntry = { project_id: id, start_date: new Date().toISOString() };
38+
this.store.dispatch(new entryActions.CreateEntry(newEntry));
3639
this.selectedId = id;
3740
this.showFields.emit(true);
3841
}

src/app/modules/time-clock/services/.gitkeep

Whitespace-only changes.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
2+
import { TestBed, inject } from '@angular/core/testing';
3+
4+
import { EntryService } from './entry.service';
5+
import { NewEntry } from '../../shared/models';
6+
7+
describe('EntryService', () => {
8+
let service: EntryService;
9+
let httpMock: HttpTestingController;
10+
11+
beforeEach(() => {
12+
TestBed.configureTestingModule({ imports: [HttpClientTestingModule] });
13+
service = TestBed.inject(EntryService);
14+
httpMock = TestBed.inject(HttpTestingController);
15+
});
16+
17+
afterEach(() => {
18+
httpMock.verify();
19+
});
20+
21+
it('services are ready to be used', inject(
22+
[HttpClientTestingModule, EntryService],
23+
(httpClient: HttpClientTestingModule, entryService: EntryService) => {
24+
expect(entryService).toBeTruthy();
25+
expect(httpClient).toBeTruthy();
26+
}
27+
));
28+
29+
it('create entry using POST from baseUrl', () => {
30+
const entry: NewEntry[] = [{ project_id: '1', start_date: new Date().toISOString() }];
31+
32+
service.baseUrl = 'time-entries';
33+
34+
service.createEntry(entry).subscribe((response) => {
35+
expect(response.length).toBe(1);
36+
});
37+
38+
const createEntryRequest = httpMock.expectOne(service.baseUrl);
39+
expect(createEntryRequest.request.method).toBe('POST');
40+
createEntryRequest.flush(entry);
41+
});
42+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Injectable } from '@angular/core';
2+
import { HttpClient } from '@angular/common/http';
3+
4+
import { Observable } from 'rxjs';
5+
import { environment } from './../../../../environments/environment';
6+
7+
@Injectable({
8+
providedIn: 'root',
9+
})
10+
export class EntryService {
11+
baseUrl = `${environment.timeTrackerApiUrl}/time-entries`;
12+
13+
constructor(private http: HttpClient) {}
14+
15+
createEntry(entryData): Observable<any> {
16+
return this.http.post(this.baseUrl, entryData);
17+
}
18+
}

src/app/modules/time-clock/store/.gitkeep

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as actions from './entry.actions';
2+
3+
describe('Actions for Entries', () => {
4+
it('CreateEntrySuccess type is EntryActionTypes.CREATE_ENTRY_SUCCESS', () => {
5+
const createEntrySuccess = new actions.CreateEntrySuccess({
6+
project_id: '1',
7+
start_date: '2020-04-21T19:51:36.559000+00:00',
8+
});
9+
expect(createEntrySuccess.type).toEqual(actions.EntryActionTypes.CREATE_ENTRY_SUCCESS);
10+
});
11+
12+
it('CreateEntryFail type is EntryActionTypes.CREATE_ENTRY_FAIL', () => {
13+
const createEntryFail = new actions.CreateEntryFail('error');
14+
expect(createEntryFail.type).toEqual(actions.EntryActionTypes.CREATE_ENTRY_FAIL);
15+
});
16+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Action } from '@ngrx/store';
2+
import { NewEntry } from '../../shared/models';
3+
4+
export enum EntryActionTypes {
5+
CREATE_ENTRY = '[Entry] CREATE_ENTRY',
6+
CREATE_ENTRY_SUCCESS = '[Entry] CREATE_ENTRY_SUCCESS',
7+
CREATE_ENTRY_FAIL = '[Entry] CREATE_ENTRY_FAIL',
8+
}
9+
10+
export class CreateEntry implements Action {
11+
public readonly type = EntryActionTypes.CREATE_ENTRY;
12+
13+
constructor(public payload: NewEntry) {}
14+
}
15+
16+
export class CreateEntrySuccess implements Action {
17+
public readonly type = EntryActionTypes.CREATE_ENTRY_SUCCESS;
18+
19+
constructor(public payload: NewEntry) {}
20+
}
21+
22+
export class CreateEntryFail implements Action {
23+
public readonly type = EntryActionTypes.CREATE_ENTRY_FAIL;
24+
25+
constructor(public error: string) {}
26+
}
27+
28+
export type EntryActions = CreateEntry | CreateEntrySuccess | CreateEntryFail;

0 commit comments

Comments
 (0)