Skip to content

Commit 7daab28

Browse files
committed
fix: #364 stop current working time
1 parent 8ce5bea commit 7daab28

File tree

6 files changed

+137
-24
lines changed

6 files changed

+137
-24
lines changed

src/app/modules/customer-management/components/projects/components/store/project.effects.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class ProjectEffects {
5555
mergeMap((project) =>
5656
this.projectService.createProject(project).pipe(
5757
map((projectData) => {
58-
this.toastrService.success(INFO_DELETE_SUCCESSFULLY);
58+
this.toastrService.success(INFO_SAVED_SUCCESSFULLY);
5959
return new actions.CreateProjectSuccess(projectData);
6060
}),
6161
catchError((error) => {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,7 @@ export class ProjectListHoverComponent implements OnInit {
6262
this.store.dispatch(new entryActions.CreateEntry(newEntry));
6363
}
6464
this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1 ));
65+
this.loadActiveTimeEntry();
6566
}
67+
6668
}

src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.spec.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { interval } from 'rxjs';
2+
import { EntryActionTypes } from './../../store/entry.actions';
13
import { EntryState } from './../../store/entry.reducer';
24
import { TimeDetailsPipe } from './../../pipes/time-details.pipe';
35
import { provideMockStore, MockStore } from '@ngrx/store/testing';
@@ -6,6 +8,7 @@ import { TimeDetails } from './../../models/time.entry.summary';
68
import { async, ComponentFixture, TestBed, tick, fakeAsync, discardPeriodicTasks } from '@angular/core/testing';
79

810
import { TimeEntriesSummaryComponent } from './time-entries-summary.component';
11+
import { ActionsSubject } from '@ngrx/store';
912

1013
describe('TimeEntriesSummaryComponent', () => {
1114
let component: TimeEntriesSummaryComponent;
@@ -17,6 +20,7 @@ describe('TimeEntriesSummaryComponent', () => {
1720

1821
const timeTwoHoursBehind = new Date();
1922
timeTwoHoursBehind.setHours(timeTwoHoursBehind.getHours() - 2);
23+
const actionSub: ActionsSubject = new ActionsSubject();
2024

2125
const timeEntry = {
2226
id: '123',
@@ -44,8 +48,7 @@ describe('TimeEntriesSummaryComponent', () => {
4448
beforeEach(async(() => {
4549
TestBed.configureTestingModule({
4650
declarations: [ TimeEntriesSummaryComponent, TimeDetailsPipe ],
47-
providers: [provideMockStore({ initialState: state })
48-
],
51+
providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }],
4952
})
5053
.compileComponents();
5154
store = TestBed.inject(MockStore);
@@ -62,6 +65,64 @@ describe('TimeEntriesSummaryComponent', () => {
6265
expect(component).toBeTruthy();
6366
});
6467

68+
69+
const params = [
70+
{ actionType: EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL },
71+
{ actionType: EntryActionTypes.STOP_TIME_ENTRY_RUNNING_SUCCESS },
72+
{ actionType: EntryActionTypes.CREATE_ENTRY_SUCCESS },
73+
];
74+
75+
params.map((param) => {
76+
it(`calls blankCurrentWorkingTime when ${param.actionType}`, () => {
77+
const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
78+
const action = {
79+
type: param.actionType,
80+
};
81+
spyOn(component, 'blankCurrentWorkingTime');
82+
83+
actionSubject.next(action);
84+
85+
expect(component.blankCurrentWorkingTime).toHaveBeenCalled();
86+
});
87+
});
88+
89+
it('sets destroyed to false when timeInterval is not null', () => {
90+
spyOn(component.destroyed$, 'next');
91+
component.timeInterval = interval(1).subscribe();
92+
93+
component.updateCurrentWorkingHours(timeEntry);
94+
95+
expect(component.destroyed$.next).toHaveBeenCalledWith(false);
96+
});
97+
98+
it('does not change currentWorkingTime if entry is null', () => {
99+
const initialWorkingTime = 'foo';
100+
component.currentWorkingTime = initialWorkingTime;
101+
102+
component.updateCurrentWorkingHours(null);
103+
104+
expect(component.currentWorkingTime).toBe(initialWorkingTime);
105+
});
106+
107+
it('sets --:-- to currentWorkingTime', () => {
108+
component.blankCurrentWorkingTime();
109+
110+
expect(component.currentWorkingTime).toBe('--:--');
111+
});
112+
113+
it('calls updateCurrentWorkingHours when LOAD_ACTIVE_ENTRY_SUCCESS', () => {
114+
const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
115+
const action = {
116+
type: EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS,
117+
payload: timeEntry
118+
};
119+
spyOn(component, 'updateCurrentWorkingHours');
120+
121+
actionSubject.next(action);
122+
123+
expect(component.updateCurrentWorkingHours).toHaveBeenCalledWith(timeEntry);
124+
});
125+
65126
it('dispatches two actions on ngOnInit', () => {
66127
spyOn(store, 'dispatch');
67128

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,75 @@
1+
import { filter, takeUntil } from 'rxjs/operators';
12
import { SubstractDatePipe } from './../../../shared/pipes/substract-date/substract-date.pipe';
2-
import { interval } from 'rxjs';
3+
import { interval, Subscription, Subject } from 'rxjs';
34
import { Entry } from './../../../shared/models/entry.model';
4-
import { getEntriesSummary, getActiveTimeEntry } from './../../store/entry.selectors';
5+
import { getEntriesSummary } from './../../store/entry.selectors';
56
import { TimeEntriesSummary } from '../../models/time.entry.summary';
6-
import { LoadEntriesSummary, LoadActiveEntry } from './../../store/entry.actions';
7+
import { LoadEntriesSummary, LoadActiveEntry, EntryActionTypes } from './../../store/entry.actions';
78
import { EntryState } from './../../store/entry.reducer';
8-
import { Store, select } from '@ngrx/store';
9-
import { Component, OnInit } from '@angular/core';
9+
import { Store, select, ActionsSubject } from '@ngrx/store';
10+
import { Component, OnInit, OnDestroy } from '@angular/core';
1011

1112
@Component({
1213
selector: 'app-time-entries-summary',
1314
templateUrl: './time-entries-summary.component.html',
1415
styleUrls: ['./time-entries-summary.component.css']
1516
})
16-
export class TimeEntriesSummaryComponent implements OnInit {
17+
export class TimeEntriesSummaryComponent implements OnInit, OnDestroy {
1718

1819
timeEntriesSummary: TimeEntriesSummary;
1920
currentWorkingTime: string;
21+
destroyed$ = new Subject<boolean>();
2022

21-
constructor(private store: Store<EntryState>) { }
23+
loadActiveEntry: Subscription;
24+
loadActiveEntryLost: Subscription;
25+
stopEntry: Subscription;
26+
startEntry: Subscription;
27+
timeInterval;
28+
29+
constructor(private store: Store<EntryState>, private actionsSubject$: ActionsSubject) { }
30+
31+
ngOnDestroy(): void {
32+
this.loadActiveEntry.unsubscribe();
33+
this.loadActiveEntryLost.unsubscribe();
34+
this.stopEntry.unsubscribe();
35+
this.startEntry.unsubscribe();
36+
}
2237

2338
ngOnInit(): void {
2439
this.store.dispatch(new LoadActiveEntry());
25-
const activeTimeEntry$ = this.store.pipe(select(getActiveTimeEntry));
26-
activeTimeEntry$.subscribe((response) => {
27-
if (response) {
28-
this.updateCurrentWorkingHours(response);
29-
}
40+
41+
this.loadActiveEntryLost = this.actionsSubject$.pipe(
42+
filter((action: any) => (
43+
action.type === EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL
44+
))
45+
).subscribe(() => {
46+
this.blankCurrentWorkingTime();
47+
});
48+
49+
this.loadActiveEntry = this.actionsSubject$.pipe(
50+
filter((action: any) => (
51+
action.type === EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS
52+
))
53+
).subscribe((action) => {
54+
this.updateCurrentWorkingHours(action.payload);
55+
});
56+
57+
this.stopEntry = this.actionsSubject$.pipe(
58+
filter((action: any) => (
59+
action.type === EntryActionTypes.STOP_TIME_ENTRY_RUNNING_SUCCESS
60+
))
61+
).subscribe(() => {
62+
this.destroyed$.next(true);
63+
this.blankCurrentWorkingTime();
64+
});
65+
66+
this.startEntry = this.actionsSubject$.pipe(
67+
filter((action: any) => (
68+
action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS
69+
))
70+
).subscribe(() => {
71+
this.store.dispatch(new LoadActiveEntry());
72+
this.blankCurrentWorkingTime();
3073
});
3174

3275
this.store.dispatch(new LoadEntriesSummary());
@@ -36,13 +79,20 @@ export class TimeEntriesSummaryComponent implements OnInit {
3679
});
3780
}
3881

82+
blankCurrentWorkingTime() {
83+
this.currentWorkingTime = '--:--';
84+
}
3985

4086
updateCurrentWorkingHours(entry: Entry) {
41-
const timeInterval = interval(1000);
42-
timeInterval.subscribe(() => {
43-
this.currentWorkingTime =
44-
new SubstractDatePipe()
45-
.transform(new Date(), new Date(entry.start_date));
46-
});
87+
if (this.timeInterval) {
88+
this.destroyed$.next(false);
89+
}
90+
if (entry) {
91+
this.timeInterval = interval(1000).pipe(
92+
takeUntil(this.destroyed$)
93+
).subscribe(() => {
94+
this.currentWorkingTime = new SubstractDatePipe().transform(new Date(), new Date(entry.start_date));
95+
});
96+
}
4797
}
4898
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export class StopTimeEntryRunning implements Action {
137137

138138
export class StopTimeEntryRunningSuccess implements Action {
139139
public readonly type = EntryActionTypes.STOP_TIME_ENTRY_RUNNING_SUCCESS;
140-
constructor(readonly payload: string) {}
140+
constructor(readonly payload) {}
141141
}
142142

143143
export class StopTimeEntryRunningFail implements Action {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ export class EntryEffects {
137137
map((action: actions.StopTimeEntryRunning) => action.payload),
138138
mergeMap((timeEntryId) =>
139139
this.entryService.stopEntryRunning(timeEntryId).pipe(
140-
map(() => {
140+
map((response) => {
141141
this.toastrService.success('You clocked-out successfully');
142-
return new actions.StopTimeEntryRunningSuccess(timeEntryId);
142+
return new actions.StopTimeEntryRunningSuccess(response);
143143
}),
144144
catchError((error) => {
145145
this.toastrService.error(error.error.message);

0 commit comments

Comments
 (0)