Skip to content

Commit 4a49635

Browse files
committed
fix: #395 Make the activity required when clocking-out
1 parent 8afbfe7 commit 4a49635

File tree

5 files changed

+92
-33
lines changed

5 files changed

+92
-33
lines changed

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

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,39 @@
33
<div class="input-group-prepend">
44
<span class="input-group-text span-width">Activity</span>
55
</div>
6-
<select id="activitiesSelect" (blur)="onSubmit()" class="form-control" formControlName="activity_id">
6+
<select
7+
id="activitiesSelect"
8+
(blur)="onSubmit()"
9+
class="form-control"
10+
formControlName="activity_id"
11+
[class.is-invalid]="activity_id.invalid && activity_id.touched"
12+
required
13+
>
714
<option value="-1"></option>
8-
<option *ngFor="let activity of activities" value="{{activity.id}}">{{ activity.name }}</option>
15+
<option *ngFor="let activity of activities" value="{{ activity.id }}">{{ activity.name }}</option>
916
</select>
1017
</div>
1118
<div class="input-group input-group-sm mb-3">
1219
<div class="input-group-prepend">
1320
<span class="input-group-text span-width">Ticket URI</span>
1421
</div>
15-
<input (blur)="onSubmit()" type="text" id="uri" formControlName="uri" class="form-control" aria-label="Small"
16-
aria-describedby="inputGroup-sizing-sm"/>
22+
<input
23+
(blur)="onSubmit()"
24+
type="text"
25+
id="uri"
26+
formControlName="uri"
27+
class="form-control"
28+
aria-label="Small"
29+
aria-describedby="inputGroup-sizing-sm"
30+
/>
1731
</div>
18-
<app-technologies (technologyAdded)="onTechnologyAdded($event)"
19-
(technologyRemoved)="onTechnologyRemoved($event)"
20-
[selectedTechnologies]="selectedTechnologies">
32+
<app-technologies
33+
(technologyAdded)="onTechnologyAdded($event)"
34+
(technologyRemoved)="onTechnologyRemoved($event)"
35+
[selectedTechnologies]="selectedTechnologies"
36+
>
2137
</app-technologies>
2238

23-
2439
<div class="input-group input-group-sm mb-3">
2540
<div class="input-group-prepend">
2641
<span class="input-group-text span-width">Description</span>
@@ -31,8 +46,8 @@
3146
formControlName="description"
3247
class="form-control"
3348
id="NotesTextarea"
34-
rows="2">
49+
rows="2"
50+
>
3551
</textarea>
3652
</div>
37-
3853
</form>

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

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import {getActiveTimeEntry} from './../../store/entry.selectors';
2-
import {Component, OnInit} from '@angular/core';
3-
import {FormBuilder, FormGroup} from '@angular/forms';
4-
import {select, Store} from '@ngrx/store';
1+
import { getActiveTimeEntry } from './../../store/entry.selectors';
2+
import { Component, OnInit } from '@angular/core';
3+
import { FormBuilder, FormGroup } from '@angular/forms';
4+
import { select, Store } from '@ngrx/store';
55

6-
import {Activity, NewEntry} from '../../../shared/models';
7-
import {ProjectState} from '../../../customer-management/components/projects/components/store/project.reducer';
8-
import {TechnologyState} from '../../../shared/store/technology.reducers';
9-
import {ActivityState, allActivities, LoadActivities} from '../../../activities-management/store';
6+
import { Activity, NewEntry } from '../../../shared/models';
7+
import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
8+
import { TechnologyState } from '../../../shared/store/technology.reducers';
9+
import { ActivityState, allActivities, LoadActivities } from '../../../activities-management/store';
1010

1111
import * as entryActions from '../../store/entry.actions';
1212

@@ -18,7 +18,6 @@ type Merged = TechnologyState & ProjectState & ActivityState;
1818
styleUrls: ['./entry-fields.component.scss'],
1919
})
2020
export class EntryFieldsComponent implements OnInit {
21-
2221
entryForm: FormGroup;
2322
selectedTechnologies: string[] = [];
2423
activities: Activity[] = [];
@@ -29,7 +28,7 @@ export class EntryFieldsComponent implements OnInit {
2928
this.entryForm = this.formBuilder.group({
3029
description: '',
3130
uri: '',
32-
activity_id: '-1'
31+
activity_id: '-1',
3332
});
3433
}
3534

@@ -58,6 +57,10 @@ export class EntryFieldsComponent implements OnInit {
5857
});
5958
}
6059

60+
get activity_id() {
61+
return this.entryForm.get('activity_id');
62+
}
63+
6164
setDataToUpdate(entryData: NewEntry) {
6265
if (entryData) {
6366
this.entryForm.patchValue({
@@ -73,17 +76,19 @@ export class EntryFieldsComponent implements OnInit {
7376
}
7477
}
7578

79+
entryFormIsValidate() {
80+
return this.entryForm.valid;
81+
}
82+
7683
onSubmit() {
77-
this.store.dispatch(new entryActions.UpdateEntryRunning({...this.newData, ...this.entryForm.value}));
84+
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value }));
7885
}
7986

8087
onTechnologyAdded($event: string[]) {
81-
this.store.dispatch(new entryActions.UpdateEntryRunning({...this.newData, technologies: $event})
82-
);
88+
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event }));
8389
}
8490

8591
onTechnologyRemoved($event: string[]) {
86-
this.store.dispatch(new entryActions.UpdateEntryRunning({...this.newData, technologies: $event}));
92+
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event }));
8793
}
88-
8994
}

src/app/modules/time-clock/pages/time-clock.component.spec.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ import { ProjectState } from '../../customer-management/components/projects/comp
88
import { ProjectListHoverComponent } from '../components';
99
import { FilterProjectPipe } from '../../shared/pipes';
1010
import { AzureAdB2CService } from '../../login/services/azure.ad.b2c.service';
11+
import { EntryFieldsComponent } from '../components/entry-fields/entry-fields.component';
12+
import { ToastrService } from 'ngx-toastr';
1113

1214
describe('TimeClockComponent', () => {
1315
let component: TimeClockComponent;
1416
let fixture: ComponentFixture<TimeClockComponent>;
1517
let store: MockStore<ProjectState>;
1618
let azureAdB2CService: AzureAdB2CService;
19+
let injectedToastrService;
20+
const toastrService = {
21+
error: () => {},
22+
};
1723
const state = {
1824
projects: {
1925
projects: [{ id: 'id', name: 'name', project_type_id: '' }],
@@ -41,11 +47,12 @@ describe('TimeClockComponent', () => {
4147
beforeEach(async(() => {
4248
TestBed.configureTestingModule({
4349
imports: [HttpClientTestingModule],
44-
declarations: [TimeClockComponent, ProjectListHoverComponent, FilterProjectPipe],
50+
declarations: [TimeClockComponent, ProjectListHoverComponent, FilterProjectPipe, EntryFieldsComponent],
4551
providers: [
4652
FormBuilder,
4753
AzureAdB2CService,
4854
provideMockStore({ initialState: state }),
55+
{ provide: ToastrService, useValue: toastrService },
4956
],
5057
}).compileComponents();
5158
store = TestBed.inject(MockStore);
@@ -56,6 +63,7 @@ describe('TimeClockComponent', () => {
5663
component = fixture.componentInstance;
5764
fixture.detectChanges();
5865
azureAdB2CService = TestBed.inject(AzureAdB2CService);
66+
injectedToastrService = TestBed.inject(ToastrService);
5967
});
6068

6169
it('should be created', () => {
@@ -78,11 +86,27 @@ describe('TimeClockComponent', () => {
7886
expect(azureAdB2CService.getName).toHaveBeenCalledTimes(0);
7987
});
8088

81-
it('clockOut dispatch a StopTimeEntryRunning action', () => {
89+
it('stopEntry dispatch a StopTimeEntryRunning action', () => {
8290
spyOn(store, 'dispatch');
8391

92+
component.stopEntry();
93+
expect(store.dispatch).toHaveBeenCalledWith(new StopTimeEntryRunning('id'));
94+
});
95+
96+
it('clockOut dispatch a StopTimeEntryRunning action', () => {
97+
spyOn(store, 'dispatch');
98+
spyOn(component.entryFieldsComponent, 'entryFormIsValidate').and.returnValue(true);
8499
component.clockOut();
85100

86101
expect(store.dispatch).toHaveBeenCalledWith(new StopTimeEntryRunning('id'));
87102
});
103+
104+
it('clockOut set error Activity is required', () => {
105+
spyOn(store, 'dispatch');
106+
spyOn(injectedToastrService, 'error');
107+
spyOn(component.entryFieldsComponent, 'entryFormIsValidate').and.returnValue(false);
108+
component.clockOut();
109+
110+
expect(injectedToastrService.error).toHaveBeenCalled();
111+
});
88112
});

src/app/modules/time-clock/pages/time-clock.component.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,29 @@ import { getActiveTimeEntry } from './../store/entry.selectors';
22
import { StopTimeEntryRunning } from './../store/entry.actions';
33
import { Entry } from './../../shared/models/entry.model';
44
import { Store, select } from '@ngrx/store';
5-
import { Component, OnInit } from '@angular/core';
5+
import { Component, OnInit, ViewChild } from '@angular/core';
66
import { AzureAdB2CService } from '../../login/services/azure.ad.b2c.service';
77
import { Subscription } from 'rxjs';
8-
8+
import { EntryFieldsComponent } from '../components/entry-fields/entry-fields.component';
9+
import { ToastrService } from 'ngx-toastr';
910
@Component({
1011
selector: 'app-time-clock',
1112
templateUrl: './time-clock.component.html',
1213
styleUrls: ['./time-clock.component.scss'],
1314
})
1415
export class TimeClockComponent implements OnInit {
16+
@ViewChild(EntryFieldsComponent)
17+
entryFieldsComponent: EntryFieldsComponent;
1518
username: string;
1619
areFieldsVisible = false;
1720
activeTimeEntry: Entry;
1821
actionsSubscription: Subscription;
1922

20-
constructor(private azureAdB2CService: AzureAdB2CService, private store: Store<Entry>) {
21-
}
23+
constructor(
24+
private azureAdB2CService: AzureAdB2CService,
25+
private store: Store<Entry>,
26+
private toastrService: ToastrService
27+
) {}
2228

2329
ngOnInit() {
2430
this.username = this.azureAdB2CService.isLogin() ? this.azureAdB2CService.getName() : '';
@@ -32,8 +38,17 @@ export class TimeClockComponent implements OnInit {
3238
});
3339
}
3440

35-
clockOut() {
41+
stopEntry() {
3642
this.store.dispatch(new StopTimeEntryRunning(this.activeTimeEntry.id));
3743
this.areFieldsVisible = false;
3844
}
45+
46+
clockOut() {
47+
if (this.entryFieldsComponent.entryFormIsValidate()) {
48+
this.stopEntry();
49+
} else {
50+
this.entryFieldsComponent.entryForm.get('activity_id').markAsTouched();
51+
this.toastrService.error('Activity is required');
52+
}
53+
}
3954
}

src/app/modules/time-entries/pages/time-entries.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class TimeEntriesComponent implements OnInit {
9292

9393
openModal(item: any) {
9494
this.idToDelete = item.id;
95-
this.message = `Are you sure you want to delete ${item.activity_name}`;
95+
this.message = `Are you sure you want to delete ${item.activity_name}?`;
9696
this.showModal = true;
9797
}
9898
}

0 commit comments

Comments
 (0)