Skip to content

Commit a5fa364

Browse files
refactor: TTA-49 change date picker
1 parent ac6a633 commit a5fa364

17 files changed

+727
-2
lines changed

src/app/app.module.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { StoreDevtoolsModule } from '@ngrx/store-devtools';
1313
import { DragDropModule } from '@angular/cdk/drag-drop';
1414
import { MatDatepickerModule } from '@angular/material/datepicker';
1515
import { MatInputModule } from '@angular/material/input';
16+
import { MatIconModule } from '@angular/material/icon';
17+
import { MatCardModule } from '@angular/material/card';
18+
import { MatNativeDateModule } from '@angular/material/core';
1619
import { MatMomentDateModule } from '@angular/material-moment-adapter';
1720
import { NgxPaginationModule } from 'ngx-pagination';
1821
import { AutocompleteLibModule } from 'angular-ng-autocomplete';
@@ -90,6 +93,9 @@ import { DarkModeComponent } from './modules/shared/components/dark-mode/dark-mo
9093
import { SocialLoginModule, SocialAuthServiceConfig } from 'angularx-social-login';
9194
import { GoogleLoginProvider } from 'angularx-social-login';
9295
import { SearchUserComponent } from './modules/shared/components/search-user/search-user.component';
96+
import { TimeRangeCustomComponent } from './modules/reports/components/time-range-custom/time-range-custom.component';
97+
import { TimeRangeHeaderComponent } from './modules/reports/components/time-range-custom/time-range-header/time-range-header.component';
98+
import { TimeRangePanelComponent } from './modules/reports/components/time-range-custom/time-range-panel/time-range-panel.component';
9399

94100
const maskConfig: Partial<IConfig> = {
95101
validation: false,
@@ -146,6 +152,9 @@ const maskConfig: Partial<IConfig> = {
146152
CalendarComponent,
147153
DropdownComponent,
148154
DarkModeComponent,
155+
TimeRangeCustomComponent,
156+
TimeRangeHeaderComponent,
157+
TimeRangePanelComponent,
149158
],
150159
imports: [
151160
NgxMaskModule.forRoot(maskConfig),
@@ -165,6 +174,9 @@ const maskConfig: Partial<IConfig> = {
165174
NgxMaterialTimepickerModule,
166175
UiSwitchModule,
167176
DragDropModule,
177+
MatIconModule,
178+
MatCardModule,
179+
MatNativeDateModule,
168180
StoreModule.forRoot(reducers, {
169181
metaReducers,
170182
}),
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<form (ngSubmit)="onSubmit()" class="date-range-form">
2+
<label class="col-form-label my-1">Select your Date Range:</label>
3+
<mat-form-field appearance="fill">
4+
<mat-label>Enter a date range</mat-label>
5+
<mat-date-range-input [formGroup]="range" [rangePicker]="picker">
6+
<input matStartDate formControlName="start" placeholder="Start date" />
7+
<input matEndDate formControlName="end" placeholder="End date" />
8+
</mat-date-range-input>
9+
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
10+
<mat-date-range-picker
11+
#picker
12+
[calendarHeaderComponent]="TimeRangeHeader"
13+
></mat-date-range-picker>
14+
</mat-form-field>
15+
<div class="col-12 col-md-2 my-1">
16+
<button type="submit" class="btn btn-primary">Search</button>
17+
</div>
18+
</form>
19+
20+
21+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
:host {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: center;
5+
justify-content: center;
6+
height: 100%;
7+
}
8+
9+
.date-range-form{
10+
display: flex;
11+
justify-content: space-between;
12+
width: 100%;
13+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { SimpleChange } from '@angular/core';
2+
import { ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4+
import { MockStore, provideMockStore } from '@ngrx/store/testing';
5+
import * as moment from 'moment';
6+
import { ToastrService } from 'ngx-toastr';
7+
import { EntryState } from 'src/app/modules/time-clock/store/entry.reducer';
8+
import * as entryActions from '../../../time-clock/store/entry.actions';
9+
10+
11+
import { TimeRangeCustomComponent } from './time-range-custom.component';
12+
13+
describe('TimeRangeCustomComponent', () => {
14+
let component: TimeRangeCustomComponent;
15+
let fixture: ComponentFixture<TimeRangeCustomComponent>;
16+
let store: MockStore<EntryState>;
17+
const toastrServiceStub = {
18+
error: () => {}
19+
};
20+
21+
const timeEntry = {
22+
id: '1',
23+
start_date: new Date(),
24+
end_date: new Date(),
25+
activity_id: '1',
26+
technologies: ['react', 'redux'],
27+
comments: 'any comment',
28+
uri: 'http://jira-test/TT-123',
29+
project_id: '1'
30+
};
31+
32+
const state = {
33+
active: timeEntry,
34+
entryList: [timeEntry],
35+
isLoading: false,
36+
message: 'test',
37+
createError: false,
38+
updateError: false,
39+
timeEntriesSummary: null,
40+
entriesForReport: [timeEntry],
41+
};
42+
43+
beforeEach(async () => {
44+
await TestBed.configureTestingModule({
45+
imports: [FormsModule, ReactiveFormsModule],
46+
declarations: [ TimeRangeCustomComponent ],
47+
providers: [
48+
provideMockStore({ initialState: state }),
49+
{ provide: ToastrService, useValue: toastrServiceStub }
50+
],
51+
})
52+
.compileComponents();
53+
store = TestBed.inject(MockStore);
54+
});
55+
56+
beforeEach(() => {
57+
fixture = TestBed.createComponent(TimeRangeCustomComponent);
58+
component = fixture.componentInstance;
59+
fixture.detectChanges();
60+
});
61+
62+
it('should create', () => {
63+
expect(component).toBeTruthy();
64+
});
65+
66+
it('setInitialDataOnScreen on ngOnInit', () => {
67+
spyOn(component, 'setInitialDataOnScreen');
68+
69+
component.ngOnInit();
70+
71+
expect(component.setInitialDataOnScreen).toHaveBeenCalled();
72+
});
73+
74+
it('LoadEntriesByTimeRange action is triggered when start date is before end date', () => {
75+
const end = moment(new Date()).subtract(1, 'days');
76+
const start = moment(new Date());
77+
spyOn(store, 'dispatch');
78+
component.range.controls.start.setValue(end);
79+
component.range.controls.end.setValue(start);
80+
81+
component.onSubmit();
82+
83+
expect(store.dispatch).toHaveBeenCalledWith(new entryActions.LoadEntriesByTimeRange({
84+
start_date: end.startOf('day'),
85+
end_date: start.endOf('day')
86+
}));
87+
});
88+
89+
it('shows an error when the end date is before the start date', () => {
90+
spyOn(toastrServiceStub, 'error');
91+
const yesterday = moment(new Date()).subtract(2, 'days');
92+
const today = moment(new Date());
93+
spyOn(store, 'dispatch');
94+
component.range.controls.start.setValue(today);
95+
component.range.controls.end.setValue(yesterday);
96+
97+
component.onSubmit();
98+
99+
expect(toastrServiceStub.error).toHaveBeenCalled();
100+
});
101+
102+
it('setInitialDataOnScreen sets dates in form', () => {
103+
spyOn(component.range.controls.start, 'setValue');
104+
spyOn(component.range.controls.end, 'setValue');
105+
106+
component.setInitialDataOnScreen();
107+
108+
expect(component.range.controls.start.setValue).toHaveBeenCalled();
109+
expect(component.range.controls.end.setValue).toHaveBeenCalled();
110+
111+
});
112+
113+
it('triggers onSubmit to set initial data', () => {
114+
spyOn(component, 'onSubmit');
115+
116+
component.setInitialDataOnScreen();
117+
118+
expect(component.onSubmit).toHaveBeenCalled();
119+
});
120+
121+
it('When the ngOnChanges method is called, the onSubmit method is called', () => {
122+
const userIdCalled = 'test-user-1';
123+
spyOn(component, 'onSubmit');
124+
125+
component.ngOnChanges({userId: new SimpleChange(null, userIdCalled, false)});
126+
127+
expect(component.onSubmit).toHaveBeenCalled();
128+
});
129+
130+
it('When the ngOnChanges method is the first change, the onSubmit method is not called', () => {
131+
const userIdNotCalled = 'test-user-2';
132+
spyOn(component, 'onSubmit');
133+
134+
component.ngOnChanges({userId: new SimpleChange(null, userIdNotCalled, true)});
135+
136+
expect(component.onSubmit).not.toHaveBeenCalled();
137+
});
138+
139+
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { OnChanges, SimpleChanges, Component, OnInit, Input } from '@angular/core';
2+
import { FormControl, FormGroup } from '@angular/forms';
3+
import { TimeRangeHeaderComponent } from './time-range-header/time-range-header.component';
4+
import { DATE_FORMAT } from 'src/environments/environment';
5+
import { formatDate } from '@angular/common';
6+
import {Store} from '@ngrx/store';
7+
import {EntryState} from '../../../time-clock/store/entry.reducer';
8+
import { ToastrService } from 'ngx-toastr';
9+
10+
import * as entryActions from '../../../time-clock/store/entry.actions';
11+
import * as moment from 'moment';
12+
13+
14+
@Component({
15+
selector: 'app-time-range-custom',
16+
templateUrl: './time-range-custom.component.html',
17+
styleUrls: ['./time-range-custom.component.scss']
18+
})
19+
export class TimeRangeCustomComponent implements OnInit, OnChanges {
20+
@Input() userId: string;
21+
22+
readonly TimeRangeHeader = TimeRangeHeaderComponent;
23+
24+
range = new FormGroup({
25+
start: new FormControl(),
26+
end: new FormControl(),
27+
});
28+
29+
constructor(private store: Store<EntryState>, private toastrService: ToastrService) {
30+
}
31+
ngOnInit(): void {
32+
this.setInitialDataOnScreen();
33+
}
34+
35+
ngOnChanges(changes: SimpleChanges){
36+
if (!changes.userId.firstChange){
37+
this.onSubmit();
38+
}
39+
}
40+
41+
setInitialDataOnScreen() {
42+
this.range.setValue({
43+
start: formatDate(moment().startOf('isoWeek').format('l'), DATE_FORMAT, 'en'),
44+
end: formatDate(moment().format('l'), DATE_FORMAT, 'en')
45+
});
46+
this.onSubmit();
47+
}
48+
49+
onSubmit() {
50+
const startDate = moment(this.range.getRawValue().start).startOf('day');
51+
const endDate = moment(this.range.getRawValue().end).endOf('day');
52+
if (endDate.isBefore(startDate)) {
53+
this.toastrService.error('The end date should be after the start date');
54+
} else {
55+
this.store.dispatch(new entryActions.LoadEntriesByTimeRange({
56+
start_date: moment(this.range.getRawValue().start).startOf('day'),
57+
end_date: moment(this.range.getRawValue().end).endOf('day'),
58+
}, this.userId));
59+
}
60+
}
61+
62+
}
63+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<app-time-range-panel></app-time-range-panel>
2+
3+
<div class="time-range-header">
4+
<button
5+
mat-icon-button
6+
class="time-range-double-arrow"
7+
(click)="previousClicked('year')"
8+
>
9+
<mat-icon>keyboard_arrow_left</mat-icon>
10+
<mat-icon>keyboard_arrow_left</mat-icon>
11+
</button>
12+
<button mat-icon-button class="time-range-month" (click)="previousClicked('month')">
13+
<mat-icon>keyboard_arrow_left</mat-icon>
14+
</button>
15+
16+
<span class="time-range-header-label">{{ periodLabel }}</span>
17+
18+
<button mat-icon-button class="time-range-month-next" (click)="nextClicked('month')">
19+
<mat-icon>keyboard_arrow_right</mat-icon>
20+
</button>
21+
<button
22+
mat-icon-button
23+
class="time-range-double-arrow time-range-double-arrow-next"
24+
(click)="nextClicked('year')"
25+
>
26+
<mat-icon>keyboard_arrow_right</mat-icon>
27+
<mat-icon>keyboard_arrow_right</mat-icon>
28+
</button>
29+
</div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.time-range-header {
2+
display: flex;
3+
align-items: center;
4+
padding: 0.5em;
5+
}
6+
7+
.time-range-header-label {
8+
flex: 1;
9+
height: 1em;
10+
font-weight: 500;
11+
text-align: center;
12+
}
13+
14+
.time-range-double-arrow .mat-icon {
15+
margin: -22%;
16+
}

0 commit comments

Comments
 (0)