Skip to content

Commit 0dcc8d6

Browse files
committed
fix: #238 Add_error_message_next_to_fields
1 parent a43f676 commit 0dcc8d6

File tree

6 files changed

+80
-67
lines changed

6 files changed

+80
-67
lines changed

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

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,42 @@
11
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
22
<div class="input-group input-group-sm mb-3">
33
<div class="input-group-prepend">
4-
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Project</span>
4+
<span class="input-group-text span-width">Project</span>
55
</div>
66
<select
7+
data-toggle="tooltip"
8+
title="{{ project_id.invalid && project_id.touched ? 'Project is required' : '' }}"
79
[class.is-invalid]="project_id.invalid && project_id.touched"
810
required
911
id="project_id"
10-
class="form-control"
12+
class="custom-select"
1113
formControlName="project_id"
1214
>
13-
<option *ngFor="let project of listProjects" value="{{project.id}}">{{ project.name }}</option>
15+
<option value="" selected="selected"></option>
16+
<option *ngFor="let project of listProjects" value="{{ project.id }}">{{ project.name }}</option>
1417
</select>
1518
</div>
19+
1620
<div class="input-group input-group-sm mb-3">
1721
<div class="input-group-prepend">
1822
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Activity</span>
1923
</div>
2024
<select
25+
data-toggle="tooltip"
26+
title="{{ activity_id.invalid && activity_id.touched ? 'Activity is required' : '' }}"
2127
[class.is-invalid]="activity_id.invalid && activity_id.touched"
2228
required
2329
id="activity_id"
24-
class="form-control"
30+
class="custom-select"
2531
formControlName="activity_id"
2632
>
27-
<option *ngFor="let activity of activities" value="{{activity.id}}">{{ activity.name }}</option>
33+
<option value="" selected="selected"></option>
34+
<option *ngFor="let activity of activities" value="{{ activity.id }}">{{ activity.name }}</option>
2835
</select>
36+
<div
37+
class="invalid-feedback"
38+
*ngIf="(activity_id.dirty || activity_id.touched) && activity_id.invalid && activity_id.errors.required"
39+
></div>
2940
</div>
3041

3142
<div class="input-group input-group-sm mb-3">
@@ -53,19 +64,21 @@
5364
class="form-control"
5465
aria-label="Small"
5566
aria-describedby="inputGroup-sizing-sm"
56-
[class.is-invalid]="start_date.invalid && start_date.touched || hoursValidation"
67+
[class.is-invalid]="start_date.invalid && start_date.touched"
5768
required
5869
/>
5970
<div class="input-group-prepend">
6071
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Start/Hour</span>
6172
</div>
6273
<input
74+
data-toggle="tooltip"
75+
title="{{ start_hour.invalid && start_hour.touched ? 'Start hour is required' : '' }}"
6376
formControlName="start_hour"
6477
id="start_hour"
6578
type="text"
6679
class="form-control"
6780
aria-label="Small"
68-
[class.is-invalid]="start_hour.invalid && start_hour.touched || hoursValidation"
81+
[class.is-invalid]="start_hour.invalid && start_hour.touched"
6982
required
7083
aria-describedby="inputGroup-sizing-sm"
7184
/>
@@ -88,29 +101,33 @@
88101
<span class="input-group-text span-width" id="inputGroup-sizing-sm">End/Hour</span>
89102
</div>
90103
<input
104+
data-toggle="tooltip"
105+
title="{{ (end_hour.invalid && end_hour.touched) || errorDate ? 'End hour is required' : '' }}"
91106
formControlName="end_hour"
92107
type="text"
93108
id="end_hour"
94109
class="form-control"
95110
aria-label="Small"
96-
[class.is-invalid]="end_hour.invalid && end_hour.touched"
111+
[class.is-invalid]="(end_hour.invalid && end_hour.touched) || errorDate"
97112
required
98113
aria-describedby="inputGroup-sizing-sm"
99114
/>
100115
</div>
101116

102-
<app-technologies (technologyAdded)="onTechnologiesUpdated($event)"
103-
(technologyRemoved)="onTechnologiesUpdated($event)"
104-
[selectedTechnologies]="selectedTechnologies">
117+
<app-technologies
118+
(technologyAdded)="onTechnologiesUpdated($event)"
119+
(technologyRemoved)="onTechnologiesUpdated($event)"
120+
[selectedTechnologies]="selectedTechnologies"
121+
>
105122
</app-technologies>
106123

107124
<div class="form-group text-left">
108125
<label for="NotesTextarea">Description</label>
109126
<textarea formControlName="description" class="form-control" id="NotesTextarea" rows="3"></textarea>
110127
</div>
111128
<div class="modal-footer">
112-
<button type="submit" class="btn btn-primary">Save</button>
113-
<button #closeModal type="button" class="btn btn-secondary" data-dismiss="modal">
129+
<button type="submit" class="btn btn-primary" [disabled]="!entryForm.valid">Save</button>
130+
<button #closeModal type="button" class="btn btn-secondary" data-dismiss="modal" (click)="close()">
114131
Close
115132
</button>
116133
</div>

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

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ describe('DetailsFieldsComponent', () => {
140140
});
141141

142142
it('should emit ngOnChange with new data', () => {
143+
const childComponent = jasmine.createSpyObj('ChildComponent', ['closeModal']);
144+
component.closeModal = childComponent;
143145
const formValue = {
144146
project_id: '',
145147
activity_id: '',
@@ -157,13 +159,17 @@ describe('DetailsFieldsComponent', () => {
157159
});
158160

159161
it('should call createError ', () => {
162+
const childComponent = jasmine.createSpyObj('ChildComponent', ['closeModal']);
163+
component.closeModal = childComponent;
160164
mockEntriesCreateErrorSelector = store.overrideSelector(getCreateError, false);
161165
spyOn(store, 'dispatch');
162166
component.ngOnInit();
163167
expect(store.dispatch).toHaveBeenCalledWith(new entryActions.CleanEntryCreateError(null));
164168
});
165169

166170
it('should call updateError ', () => {
171+
const childComponent = jasmine.createSpyObj('ChildComponent', ['closeModal']);
172+
component.closeModal = childComponent;
167173
mockEntriesUpdateErrorSelector = store.overrideSelector(getUpdateError, false);
168174
spyOn(store, 'dispatch');
169175
component.ngOnInit();
@@ -172,44 +178,25 @@ describe('DetailsFieldsComponent', () => {
172178

173179
it('should emit saveEntry event', () => {
174180
spyOn(component.saveEntry, 'emit');
175-
component.onSubmit();
176-
const data = {
181+
component.entryForm.setValue({
177182
project_id: '',
178183
activity_id: '',
179-
technologies: [],
180-
description: '',
181-
start_date: 'T00:00',
182-
end_date: 'T00:00',
183184
uri: '',
184-
};
185-
expect(component.saveEntry.emit).toHaveBeenCalledWith(data);
186-
});
187-
188-
it('should emit saveEntry without project and activite fields event', () => {
189-
spyOn(component.saveEntry, 'emit');
190-
component.entryForm.setValue({
191-
project_id: 'id',
192-
activity_id: 'fc5fab41-a21e-4155-9d05-511b956ebd05',
193-
uri: '',
194-
start_date: '',
195-
end_date: '',
185+
start_date: '2020-02-05',
186+
end_date: '2020-02-05',
196187
start_hour: '00:00',
197-
end_hour: '00:00',
188+
end_hour: '00:01',
198189
description: '',
199190
technology: '',
200191
});
201-
component.activities = [
202-
{ id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', name: 'activity1', description: '' },
203-
];
204192
component.onSubmit();
205-
fixture.detectChanges();
206193
const data = {
207-
project_id: 'id',
208-
activity_id: 'fc5fab41-a21e-4155-9d05-511b956ebd05',
194+
project_id: '',
195+
activity_id: '',
209196
technologies: [],
210197
description: '',
211-
start_date: 'T00:00',
212-
end_date: 'T00:00',
198+
start_date: '2020-02-05T00:00',
199+
end_date: '2020-02-05T00:01',
213200
uri: '',
214201
};
215202
expect(component.saveEntry.emit).toHaveBeenCalledWith(data);

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

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import * as projectActions from '../../../customer-management/components/project
2222
import { EntryState } from '../../../time-clock/store/entry.reducer';
2323
import * as entryActions from '../../../time-clock/store/entry.actions';
2424
import { getUpdateError, getCreateError } from 'src/app/modules/time-clock/store/entry.selectors';
25-
25+
import $ from 'jquery';
26+
import 'bootstrap';
2627
type Merged = TechnologyState & ProjectState & ActivityState & EntryState;
2728

2829
@Component({
@@ -35,22 +36,16 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
3536
@Input() formType: string;
3637
@Output() saveEntry = new EventEmitter();
3738
@ViewChild('closeModal') closeModal: ElementRef;
38-
@ViewChild('list') list: ElementRef;
3939
entryForm: FormGroup;
4040
selectedTechnologies: string[] = [];
4141
isLoading = false;
4242
listProjects: Project[] = [];
4343
activities: Activity[] = [];
4444
keyword = 'name';
4545
showlist: boolean;
46-
hoursValidation: boolean;
46+
errorDate: boolean;
4747

4848
constructor(private formBuilder: FormBuilder, private store: Store<Merged>, private renderer: Renderer2) {
49-
this.renderer.listen('window', 'click', (e: Event) => {
50-
if (this.showlist && !this.list.nativeElement.contains(e.target)) {
51-
this.showlist = false;
52-
}
53-
});
5449
this.entryForm = this.formBuilder.group({
5550
project_id: '',
5651
activity_id: '',
@@ -62,10 +57,10 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
6257
uri: '',
6358
technology: '',
6459
});
60+
$('[data-toggle="tooltip"]').tooltip();
6561
}
6662

6763
ngOnInit(): void {
68-
6964
this.store.dispatch(new projectActions.LoadProjects());
7065
const projects$ = this.store.pipe(select(getProjects));
7166
projects$.subscribe((response) => {
@@ -95,7 +90,6 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
9590
}
9691

9792
ngOnChanges(): void {
98-
this.hoursValidation = false;
9993
if (this.entryToEdit) {
10094
this.selectedTechnologies = this.entryToEdit.technologies;
10195
this.entryForm.setValue({
@@ -110,21 +104,25 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
110104
technology: '',
111105
});
112106
} else {
113-
this.selectedTechnologies = [];
114-
this.entryForm.setValue({
115-
project_id: '',
116-
activity_id: '',
117-
description: '',
118-
start_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
119-
start_hour: '00:00',
120-
end_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
121-
end_hour: '00:00',
122-
uri: '',
123-
technology: '',
124-
});
107+
this.cleanForm();
125108
}
126109
}
127110

111+
cleanForm() {
112+
this.selectedTechnologies = [];
113+
this.entryForm.setValue({
114+
project_id: '',
115+
activity_id: '',
116+
description: '',
117+
start_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
118+
start_hour: '00:00',
119+
end_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
120+
end_hour: '00:00',
121+
uri: '',
122+
technology: '',
123+
});
124+
}
125+
128126
onTechnologiesUpdated($event: string[]) {
129127
this.selectedTechnologies = $event;
130128
}
@@ -153,8 +151,14 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
153151
}
154152

155153
closeEntryModal() {
154+
this.close();
155+
this.closeModal.nativeElement.click();
156+
}
157+
158+
close() {
156159
this.entryForm.reset();
157-
this.closeModal?.nativeElement?.click();
160+
this.errorDate = false;
161+
this.cleanForm();
158162
}
159163

160164
onSubmit() {
@@ -167,6 +171,12 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
167171
end_date: `${this.entryForm.value.end_date}T${this.entryForm.value.end_hour}`,
168172
uri: this.entryForm.value.uri,
169173
};
170-
this.saveEntry.emit(entry);
174+
175+
if (new Date(entry.start_date) < new Date(entry.end_date)) {
176+
this.errorDate = false;
177+
this.saveEntry.emit(entry);
178+
} else {
179+
this.errorDate = true;
180+
}
171181
}
172182
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,4 @@ describe('ProjectListHoverComponent', () => {
7373

7474
expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(UpdateActiveEntry));
7575
});
76-
7776
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ describe('TimeEntriesComponent', () => {
127127
};
128128
mockEntriesSelector = store.overrideSelector(allEntries, [newEntry]);
129129
component.ngOnInit();
130-
expect(component.dataByMonth.length).toEqual(1);
130+
expect(component.dataByMonth.length).toEqual(0);
131131
}));
132132

133133
it('should call dataByMonth without new date in ngOnInit()', async(() => {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class TimeEntriesComponent implements OnInit {
2626
dataByMonth$.subscribe((response) => {
2727
this.entryList = response;
2828
this.dataByMonth = this.entryList.reduce((acc: any, entry: any) => {
29-
if (new Date(entry.start_date).getMonth() === new Date().getMonth()) {
29+
if (new Date(entry.start_date).getMonth() === new Date().getMonth() && entry.end_date) {
3030
const item = { ...entry };
3131
return [...acc, item];
3232
}

0 commit comments

Comments
 (0)