Skip to content

Commit 2f6fb79

Browse files
committed
fix: #227 Enhance entries modal
1 parent cd98930 commit 2f6fb79

File tree

10 files changed

+270
-21
lines changed

10 files changed

+270
-21
lines changed

src/app/modules/shared/components/details-fields/details-fields.component.html

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
<div class="input-group-prepend">
44
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Project</span>
55
</div>
6-
<div class="ng-autocomplete">
6+
<div class="ng-autocomplete" [ngClass]="{'validation': projectError}" >
77
<ng-autocomplete
88
class="autocomplete"
99
name="projectName"
1010
ngDefaultControl
11-
[(ngModel)]="projectName"
11+
[ngModel]="projectName"
12+
(selected)='selectEvent($event)'
1213
[data]="listProjects && listProjects"
1314
[searchKeyword]="keyword"
1415
[itemTemplate]="itemTemplate"
@@ -28,7 +29,13 @@
2829
<div class="input-group-prepend">
2930
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Activity</span>
3031
</div>
31-
<select id="activitiesSelect" class="form-control" formControlName="activity">
32+
<select
33+
[class.is-invalid]="activity.invalid && activity.touched"
34+
required
35+
id="activity"
36+
class="form-control"
37+
formControlName="activity"
38+
>
3239
<option *ngFor="let activity of activities">{{ activity.name }}</option>
3340
</select>
3441
</div>
@@ -39,6 +46,7 @@
3946
</div>
4047
<input
4148
formControlName="uri"
49+
id="uri"
4250
type="text"
4351
class="form-control"
4452
aria-label="Small"
@@ -52,19 +60,25 @@
5260
</div>
5361
<input
5462
formControlName="start_date"
63+
id="start_date"
5564
type="date"
5665
class="form-control"
5766
aria-label="Small"
5867
aria-describedby="inputGroup-sizing-sm"
68+
[class.is-invalid]="start_date.invalid && start_date.touched"
69+
required
5970
/>
6071
<div class="input-group-prepend">
6172
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Start/Hour</span>
6273
</div>
6374
<input
6475
formControlName="start_hour"
76+
id="start_hour"
6577
type="text"
6678
class="form-control"
6779
aria-label="Small"
80+
[class.is-invalid]="start_hour.invalid && start_hour.touched"
81+
required
6882
aria-describedby="inputGroup-sizing-sm"
6983
/>
7084
</div>
@@ -74,9 +88,12 @@
7488
</div>
7589
<input
7690
formControlName="end_date"
91+
id="end_date"
7792
type="date"
7893
class="form-control"
7994
aria-label="Small"
95+
[class.is-invalid]="end_date.invalid && end_date.touched"
96+
required
8097
aria-describedby="inputGroup-sizing-sm"
8198
/>
8299
<div class="input-group-prepend">
@@ -85,8 +102,11 @@
85102
<input
86103
formControlName="end_hour"
87104
type="text"
105+
id="end_hour"
88106
class="form-control"
89107
aria-label="Small"
108+
[class.is-invalid]="end_hour.invalid && end_hour.touched"
109+
required
90110
aria-describedby="inputGroup-sizing-sm"
91111
/>
92112
</div>
@@ -126,7 +146,7 @@
126146
<textarea formControlName="description" class="form-control" id="NotesTextarea" rows="3"></textarea>
127147
</div>
128148
<div class="modal-footer">
129-
<button type="submit" class="btn btn-primary">Save</button>
149+
<button type="submit" class="btn btn-primary" [disabled]="!entryForm.valid && !projectError">Save</button>
130150
<button #closeModal type="button" class="btn btn-secondary" data-dismiss="modal">
131151
Close
132152
</button>

src/app/modules/shared/components/details-fields/details-fields.component.scss

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@
8080
}
8181
}
8282

83+
.validation::ng-deep {
84+
.autocomplete .autocomplete-container {
85+
border: 1px solid red;
86+
}
87+
}
88+
89+
8390
input[type="date"]::-webkit-calendar-picker-indicator {
8491
position: absolute;
8592
top: 0;
@@ -98,5 +105,5 @@ input[type="date"]::-webkit-inner-spin-button {
98105
}
99106

100107
input[type="date"]::-webkit-clear-button {
101-
z-index: 1;
108+
display: none;
102109
}

src/app/modules/shared/components/details-fields/details-fields.component.spec.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ import { DetailsFieldsComponent } from './details-fields.component';
99
import * as actions from '../../store/technology.actions';
1010
import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
1111
import { getCustomerProjects } from '../../../customer-management/components/projects/components/store/project.selectors';
12+
import { EntryState } from '../../../time-clock/store/entry.reducer';
13+
import { getUpdateError, getCreateError } from 'src/app/modules/time-clock/store/entry.selectors';
1214

1315
describe('DetailsFieldsComponent', () => {
14-
type Merged = TechnologyState & ProjectState;
16+
type Merged = TechnologyState & ProjectState & EntryState;
1517
let component: DetailsFieldsComponent;
1618
let fixture: ComponentFixture<DetailsFieldsComponent>;
1719
let store: MockStore<Merged>;
1820
let mockTechnologySelector;
1921
let mockProjectsSelector;
20-
let length;
22+
let mockEntriesUpdateErrorSelector;
23+
let mockEntriesCreateErrorSelector;
2124

2225
const state = {
2326
projects: {
@@ -37,6 +40,10 @@ describe('DetailsFieldsComponent', () => {
3740
message: 'Data fetch successfully!',
3841
activityIdToEdit: '',
3942
},
43+
Entries: {
44+
createError: null,
45+
updateError: null,
46+
},
4047
};
4148

4249
const initialData = {
@@ -58,6 +65,8 @@ describe('DetailsFieldsComponent', () => {
5865
store = TestBed.inject(MockStore);
5966
mockTechnologySelector = store.overrideSelector(allTechnologies, state.technologies);
6067
mockProjectsSelector = store.overrideSelector(getCustomerProjects, state.projects);
68+
mockEntriesUpdateErrorSelector = store.overrideSelector(getUpdateError, state.Entries.updateError);
69+
mockEntriesCreateErrorSelector = store.overrideSelector(getCreateError, state.Entries.createError);
6170
}));
6271

6372
beforeEach(() => {
@@ -238,7 +247,7 @@ describe('DetailsFieldsComponent', () => {
238247
end_hour: '00:00',
239248
description: '',
240249
});
241-
component.projectName = { id: 'abc', name: 'name' };
250+
component.project = { id: 'abc', name: 'name' };
242251
component.activities = [
243252
{ id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', name: 'activity1', description: '' },
244253
];

src/app/modules/shared/components/details-fields/details-fields.component.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ import { TechnologyState } from '../../store/technology.reducers';
2222
import { LoadActivities, ActivityState, allActivities } from '../../../activities-management/store';
2323
import { getProjects } from '../../../customer-management/components/projects/components/store/project.selectors';
2424
import * as projectActions from '../../../customer-management/components/projects/components/store/project.actions';
25+
import { EntryState } from '../../../time-clock/store/entry.reducer';
26+
import * as entryActions from '../../../time-clock/store/entry.actions';
27+
import { getUpdateError, getCreateError } from 'src/app/modules/time-clock/store/entry.selectors';
2528

26-
type Merged = TechnologyState & ProjectState & ActivityState;
29+
type Merged = TechnologyState & ProjectState & ActivityState & EntryState;
2730

2831
@Component({
2932
selector: 'app-details-fields',
@@ -45,7 +48,8 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
4548
keyword = 'name';
4649
showlist: boolean;
4750
project: any = {};
48-
projectName: any = {};
51+
projectName: string;
52+
projectError: boolean;
4953

5054
constructor(private formBuilder: FormBuilder, private store: Store<Merged>, private renderer: Renderer2) {
5155
this.renderer.listen('window', 'click', (e: Event) => {
@@ -66,6 +70,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
6670
}
6771

6872
ngOnInit(): void {
73+
this.projectError = true;
6974
const technologies$ = this.store.pipe(select(allTechnologies));
7075
technologies$.subscribe((response) => {
7176
this.isLoading = response.isLoading;
@@ -83,6 +88,21 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
8388
activities$.subscribe((response) => {
8489
this.activities = response;
8590
});
91+
92+
const updateError$ = this.store.pipe(select(getUpdateError));
93+
updateError$.subscribe((updateError) => {
94+
if (updateError != null && !updateError) {
95+
this.closeEntryModal();
96+
this.store.dispatch(new entryActions.CleanEntryUpdateError(null));
97+
}
98+
});
99+
const createError$ = this.store.pipe(select(getCreateError));
100+
createError$.subscribe((createError) => {
101+
if (createError != null && !createError) {
102+
this.closeEntryModal();
103+
this.store.dispatch(new entryActions.CleanEntryCreateError(null));
104+
}
105+
});
86106
}
87107

88108
ngOnChanges(): void {
@@ -91,6 +111,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
91111
this.project = this.listProjects.find((p) => p.id === this.entryToEdit.project_id);
92112
const activity = this.activities.find((a) => a.id === this.entryToEdit.activity_id);
93113
this.projectName = this.project.name;
114+
this.projectError = false;
94115
this.entryForm.setValue({
95116
activity: activity ? activity.name : '',
96117
description: this.entryToEdit.description,
@@ -136,9 +157,43 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
136157
this.selectedTechnology.splice(index, 1);
137158
}
138159

160+
get uri() {
161+
return this.entryForm.get('uri');
162+
}
163+
164+
get activity() {
165+
return this.entryForm.get('activity');
166+
}
167+
168+
get start_date() {
169+
return this.entryForm.get('start_date');
170+
}
171+
get start_hour() {
172+
return this.entryForm.get('start_hour');
173+
}
174+
get end_date() {
175+
return this.entryForm.get('end_date');
176+
}
177+
get end_hour() {
178+
return this.entryForm.get('end_hour');
179+
}
180+
181+
selectEvent($event) {
182+
this.projectError = false;
183+
this.project = $event;
184+
this.projectName = this.project.name;
185+
}
186+
187+
closeEntryModal() {
188+
this.selectedTechnology = [];
189+
this.project = '';
190+
this.projectName = '';
191+
this.entryForm.reset();
192+
this.closeModal.nativeElement.click();
193+
}
194+
139195
onSubmit() {
140196
const activity = this.activities.find((a) => a.name === this.entryForm.value.activity);
141-
this.project = this.projectName.id ? this.projectName : this.project;
142197
const entry = {
143198
project_id: this.project.id,
144199
activity_id: activity ? activity.id : null,
@@ -149,7 +204,5 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
149204
uri: this.entryForm.value.uri,
150205
};
151206
this.saveEntry.emit(entry);
152-
this.ngOnChanges();
153-
this.closeModal.nativeElement.click();
154207
}
155208
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export enum EntryActionTypes {
2121
STOP_TIME_ENTRY_RUNNING_SUCCESS = '[Entry] STOP_TIME_ENTRIES_RUNNING_SUCCESS',
2222
STOP_TIME_ENTRY_RUNNING_FAILED = '[Entry] STOP_TIME_ENTRIES_RUNNING_FAILED',
2323
DEFAULT_ENTRY = '[Entry] DEFAULT_ENTRY',
24+
CLEAN_ENTRY_CREATE_ERROR = '[Entry] CLEAN_ENTRY_CREATE_ERROR',
25+
CLEAN_ENTRY_UPDATE_ERROR = '[Entry] CLEAN_ENTRY_UPDATE_ERROR',
2426
}
2527

2628
export class LoadActiveEntry implements Action {
@@ -120,6 +122,16 @@ export class StopTimeEntryRunningFail implements Action {
120122
public readonly type = EntryActionTypes.STOP_TIME_ENTRY_RUNNING_FAILED;
121123
constructor(public error: string) {}
122124
}
125+
126+
export class CleanEntryCreateError implements Action {
127+
public readonly type = EntryActionTypes.CLEAN_ENTRY_CREATE_ERROR;
128+
constructor(public error: boolean) {}
129+
}
130+
131+
export class CleanEntryUpdateError implements Action {
132+
public readonly type = EntryActionTypes.CLEAN_ENTRY_UPDATE_ERROR;
133+
constructor(public error: boolean) {}
134+
}
123135
export class DefaultEntry implements Action {
124136
public readonly type = EntryActionTypes.DEFAULT_ENTRY;
125137
}
@@ -143,4 +155,6 @@ export type EntryActions =
143155
| StopTimeEntryRunning
144156
| StopTimeEntryRunningSuccess
145157
| StopTimeEntryRunningFail
158+
| CleanEntryCreateError
159+
| CleanEntryUpdateError
146160
| DefaultEntry;

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { Injectable } from '@angular/core';
22
import { ofType, Actions, Effect } from '@ngrx/effects';
33
import { Action } from '@ngrx/store';
44
import { of, Observable } from 'rxjs';
5+
import { ToastrService } from 'ngx-toastr';
56
import { catchError, map, mergeMap } from 'rxjs/operators';
67
import { EntryService } from '../services/entry.service';
78
import * as actions from './entry.actions';
89

910
@Injectable()
1011
export class EntryEffects {
11-
constructor(private actions$: Actions, private entryService: EntryService) {}
12+
constructor(private actions$: Actions, private entryService: EntryService, private toastrService: ToastrService) {}
1213

1314
@Effect()
1415
loadActiveEntry$: Observable<Action> = this.actions$.pipe(
@@ -41,9 +42,13 @@ export class EntryEffects {
4142
mergeMap((entry) =>
4243
this.entryService.createEntry(entry).pipe(
4344
map((entryData) => {
45+
this.toastrService.success('Entry was saved successfully');
4446
return new actions.CreateEntrySuccess(entryData);
4547
}),
46-
catchError((error) => of(new actions.CreateEntryFail(error.error.message)))
48+
catchError((error) => {
49+
this.toastrService.error(error.error.message);
50+
return of(new actions.CreateEntryFail(error.error.message));
51+
})
4752
)
4853
)
4954
);
@@ -67,9 +72,13 @@ export class EntryEffects {
6772
mergeMap((project) =>
6873
this.entryService.updateActiveEntry(project).pipe(
6974
map((projectData) => {
75+
this.toastrService.success('Entry was updated successfully');
7076
return new actions.UpdateActiveEntrySuccess(projectData);
7177
}),
72-
catchError((error) => of(new actions.UpdateActiveEntryFail(error)))
78+
catchError((error) => {
79+
this.toastrService.error(error.error.message);
80+
return of(new actions.UpdateActiveEntryFail(error));
81+
})
7382
)
7483
)
7584
);

0 commit comments

Comments
 (0)