Skip to content

Commit 8fa17b2

Browse files
feat: TT-296 add scroll to the current time in calendar component (#715)
* feat: TT-296 add scroll to the current time in calendar component * feat: TT-296 The code was modified with the suggestions in the PR * feat: TT-296 The Code Smells were corrected
1 parent eeff634 commit 8fa17b2

File tree

4 files changed

+135
-76
lines changed

4 files changed

+135
-76
lines changed

src/app/modules/time-entries/components/calendar/calendar.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@
128128
</div>
129129
</div>
130130

131-
<div [ngSwitch]="calendarView">
131+
<div class="switch-calendar-view" [ngSwitch]="calendarView" #scrollContainer>
132132
<mwl-calendar-month-view
133133
*ngSwitchCase="CalendarViewEnum.Month"
134134
[viewDate]="currentDate"

src/app/modules/time-entries/components/calendar/calendar.component.scss

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,29 @@
88
@return darken(saturate(adjust-hue($text-color, 6), 46.19), 40.98);
99
}
1010

11+
.switch-calendar-view {
12+
height: calc(100vh - 320px);
13+
overflow-y: auto;
14+
}
15+
16+
::-webkit-scrollbar {
17+
width: 5px;
18+
}
19+
20+
::-webkit-scrollbar-track {
21+
background: #f1f1f1;
22+
border-radius: 5px;
23+
}
24+
25+
::-webkit-scrollbar-thumb {
26+
background: transparent;
27+
}
28+
29+
::-webkit-scrollbar-thumb:hover {
30+
background: #888;
31+
border-radius: 5px;
32+
}
33+
1134
.container-time-entries {
1235
margin: 2px;
1336
div.time-entries {

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

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,41 @@ describe('CalendarComponent', () => {
1414
let fakeEntry: Entry;
1515
let fakeEntryRunning: Entry;
1616

17-
beforeEach(waitForAsync( () => {
18-
TestBed.configureTestingModule({
19-
declarations: [ CalendarComponent ]
17+
beforeEach(
18+
waitForAsync(() => {
19+
TestBed.configureTestingModule({
20+
declarations: [CalendarComponent],
21+
}).compileComponents();
22+
23+
currentDate = moment();
24+
fakeEntry = {
25+
id: 'entry_1',
26+
project_id: 'abc',
27+
project_name: 'Time-tracker',
28+
start_date: new Date('2020-02-05T15:36:15.887Z'),
29+
end_date: new Date('2020-02-05T18:36:15.887Z'),
30+
customer_name: 'ioet Inc.',
31+
activity_id: 'development',
32+
technologies: ['Angular', 'TypeScript'],
33+
description: 'No comments',
34+
uri: 'EY-25',
35+
};
36+
fakeEntryRunning = {
37+
id: 'entry_1',
38+
project_id: 'abc',
39+
project_name: 'Time-tracker',
40+
start_date: new Date('2020-02-05T15:36:15.887Z'),
41+
end_date: null,
42+
customer_name: 'ioet Inc.',
43+
activity_id: 'development',
44+
technologies: ['Angular', 'TypeScript'],
45+
description: 'No comments',
46+
uri: 'EY-25',
47+
};
48+
49+
jasmine.clock().mockDate(currentDate.toDate());
2050
})
21-
.compileComponents();
22-
23-
currentDate = moment();
24-
fakeEntry = {
25-
id: 'entry_1',
26-
project_id: 'abc',
27-
project_name: 'Time-tracker',
28-
start_date: new Date('2020-02-05T15:36:15.887Z'),
29-
end_date: new Date('2020-02-05T18:36:15.887Z'),
30-
customer_name: 'ioet Inc.',
31-
activity_id: 'development',
32-
technologies: ['Angular', 'TypeScript'],
33-
description: 'No comments',
34-
uri: 'EY-25',
35-
};
36-
fakeEntryRunning = {
37-
id: 'entry_1',
38-
project_id: 'abc',
39-
project_name: 'Time-tracker',
40-
start_date: new Date('2020-02-05T15:36:15.887Z'),
41-
end_date: null,
42-
customer_name: 'ioet Inc.',
43-
activity_id: 'development',
44-
technologies: ['Angular', 'TypeScript'],
45-
description: 'No comments',
46-
uri: 'EY-25',
47-
};
48-
49-
jasmine.clock().mockDate(currentDate.toDate());
50-
}));
51+
);
5152

5253
beforeEach(() => {
5354
fixture = TestBed.createComponent(CalendarComponent);
@@ -83,11 +84,11 @@ describe('CalendarComponent', () => {
8384
end: fakeEntry.end_date,
8485
title: fakeEntry.description,
8586
id: fakeEntry.id,
86-
meta: fakeEntry
87+
meta: fakeEntry,
8788
};
8889
const fakeDatasource = {
8990
isLoading: false,
90-
data: [fakeEntry]
91+
data: [fakeEntry],
9192
};
9293
const fakeTimeEntries = of(fakeDatasource);
9394
const expectedtimeEntriesAsEvent = [fakeTimeEntryAsEvent];
@@ -104,11 +105,11 @@ describe('CalendarComponent', () => {
104105
end: fakeEntryRunning.end_date,
105106
title: fakeEntryRunning.description,
106107
id: fakeEntryRunning.id,
107-
meta: fakeEntryRunning
108+
meta: fakeEntryRunning,
108109
};
109110
const fakeDatasource = {
110111
isLoading: false,
111-
data: [fakeEntryRunning]
112+
data: [fakeEntryRunning],
112113
};
113114
const fakeTimeEntries = of(fakeDatasource);
114115
const expectedtimeEntriesAsEvent = [fakeTimeEntryAsEvent];
@@ -141,7 +142,7 @@ describe('CalendarComponent', () => {
141142
end: fakeEntry.end_date,
142143
title: fakeEntry.description,
143144
id: fakeEntry.id,
144-
meta: fakeEntry
145+
meta: fakeEntry,
145146
};
146147
const fakeValueEmit = {
147148
id: fakeEntry.id,
@@ -159,7 +160,7 @@ describe('CalendarComponent', () => {
159160
end: fakeEntry.end_date,
160161
title: fakeEntry.description,
161162
id: fakeEntry.id,
162-
meta: fakeEntry
163+
meta: fakeEntry,
163164
};
164165
const fakeValueEmit = {
165166
timeEntry: fakeEntry,
@@ -195,6 +196,13 @@ describe('CalendarComponent', () => {
195196
expect(component.calendarView).toEqual(fakeCalendarView);
196197
});
197198

199+
it('set srcoll to current time marker in calendarView when is call scrollToCurrentTimeMarker', () => {
200+
const fakeCalendarView: CalendarView = CalendarView.Week;
201+
spyOn(component, 'scrollToCurrentTimeMarker');
202+
component.changeCalendarView(fakeCalendarView);
203+
expect(component.scrollToCurrentTimeMarker).toHaveBeenCalled();
204+
});
205+
198206
it('set false in nextDateDisabled when call navigationEnable and calendarView != Month and currentDate + 1 day is not greater to initialDate', () => {
199207
component.currentDate = moment().subtract(2, 'day').toDate();
200208
component.initialDate = moment().toDate();

src/app/modules/time-entries/components/calendar/calendar.component.ts

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
1-
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
21
import {
3-
CalendarEvent,
4-
CalendarView,
5-
} from 'angular-calendar';
2+
ChangeDetectorRef,
3+
Component,
4+
ElementRef,
5+
EventEmitter,
6+
Input,
7+
OnInit,
8+
Output,
9+
ViewChild,
10+
} from '@angular/core';
11+
import { CalendarEvent, CalendarView } from 'angular-calendar';
612
import { Observable } from 'rxjs';
713
import * as moment from 'moment';
814
import { DataSource } from '../../../shared/models/data-source.model';
915
import { Entry } from 'src/app/modules/shared/models';
1016
import { map } from 'rxjs/operators';
1117
import { SubstractDatePipe } from 'src/app/modules/shared/pipes/substract-date/substract-date.pipe';
18+
import { differenceInMinutes, startOfDay, startOfHour } from 'date-fns';
1219

1320
@Component({
1421
selector: 'app-calendar',
1522
templateUrl: './calendar.component.html',
1623
styleUrls: ['./calendar.component.scss'],
1724
})
1825
export class CalendarComponent implements OnInit {
19-
@Input() set timeEntries$(timeEntries: Observable<DataSource<Entry>>){
26+
readonly DEFAULT_HEADER_HEIGHT = 52;
27+
readonly VARIATION_HEIGHT: number = 2;
28+
29+
@ViewChild('scrollContainer') scrollContainer: ElementRef<HTMLElement>;
30+
31+
@Input() set timeEntries$(timeEntries: Observable<DataSource<Entry>>) {
2032
this.castEntryToCalendarEvent(timeEntries);
2133
}
2234
@Input() calendarView: CalendarView = CalendarView.Month;
@@ -25,7 +37,7 @@ export class CalendarComponent implements OnInit {
2537
@Output() viewModal: EventEmitter<any> = new EventEmitter<string>();
2638
@Output() deleteTimeEntry: EventEmitter<any> = new EventEmitter<string>();
2739
@Output() changeDate: EventEmitter<any> = new EventEmitter<{
28-
date: Date
40+
date: Date;
2941
}>();
3042

3143
initialDate: Date;
@@ -34,97 +46,113 @@ export class CalendarComponent implements OnInit {
3446
timeEntriesAsEvent: CalendarEvent[];
3547
nextDateDisabled: boolean;
3648

37-
constructor() {
49+
constructor(private referenceChangeDetector: ChangeDetectorRef) {
3850
this.initialDate = new Date();
3951
this.previusDate = new Date();
4052
this.isToday = false;
4153
this.timeEntriesAsEvent = [];
4254
this.nextDateDisabled = true;
43-
}
55+
}
4456

4557
ngOnInit(): void {
4658
this.isToday = this.isVisibleForCurrentDate();
4759
this.navigationEnable(this.calendarView);
4860
}
4961

50-
get CalendarViewEnum(): typeof CalendarView{
62+
get CalendarViewEnum(): typeof CalendarView {
5163
return CalendarView;
5264
}
5365

66+
scrollToCurrentTimeMarker() {
67+
if (this.calendarView === CalendarView.Week || CalendarView.Day) {
68+
const minutesSinceStartOfDay = differenceInMinutes(startOfHour(this.currentDate), startOfDay(this.currentDate));
69+
const headerHeight = this.calendarView === CalendarView.Week ? this.DEFAULT_HEADER_HEIGHT : 0;
70+
this.scrollContainer.nativeElement.scrollTop = minutesSinceStartOfDay * this.VARIATION_HEIGHT + headerHeight;
71+
}
72+
}
73+
5474
castEntryToCalendarEvent(timeEntries$: Observable<DataSource<Entry>>) {
55-
timeEntries$.pipe(
56-
map((timeEntriesDatasorce) => timeEntriesDatasorce.data.map(
57-
(timeEntries) => ({
58-
start: new Date(timeEntries.start_date),
59-
end: timeEntries.end_date ? new Date(timeEntries.end_date) : null ,
60-
title: timeEntries.description,
61-
id: timeEntries.id,
62-
meta: timeEntries
63-
} as CalendarEvent)
75+
timeEntries$
76+
.pipe(
77+
map((timeEntriesDatasorce) =>
78+
timeEntriesDatasorce.data.map(
79+
(timeEntries) =>
80+
({
81+
start: new Date(timeEntries.start_date),
82+
end: timeEntries.end_date ? new Date(timeEntries.end_date) : null,
83+
title: timeEntries.description,
84+
id: timeEntries.id,
85+
meta: timeEntries,
86+
} as CalendarEvent)
87+
)
6488
)
6589
)
66-
)
67-
.subscribe((timeEntriesAsEvent) => {
90+
.subscribe((timeEntriesAsEvent) => {
6891
this.timeEntriesAsEvent = [...timeEntriesAsEvent].reverse();
69-
});
92+
});
7093
}
7194

7295
handleEditEvent(timeEntryAsEvent: CalendarEvent): void {
73-
this.viewModal.emit( {
74-
id: timeEntryAsEvent.id
96+
this.viewModal.emit({
97+
id: timeEntryAsEvent.id,
7598
});
7699
}
77100

78101
handleDeleteEvent(timeEntryAsEvent: CalendarEvent): void {
79102
this.deleteTimeEntry.emit({
80-
timeEntry: timeEntryAsEvent.meta
103+
timeEntry: timeEntryAsEvent.meta,
81104
});
82105
}
83106

84-
handleChangeDateEvent(): void{
107+
handleChangeDateEvent(): void {
85108
const date = this.currentDate;
86109
this.isToday = this.isVisibleForCurrentDate();
87110
this.navigationEnable(this.calendarView);
88-
this.changeDate.emit({date});
111+
this.changeDate.emit({ date });
89112
}
90113

91-
changeCalendarView(calendarView: CalendarView){
114+
changeCalendarView(calendarView: CalendarView) {
92115
this.calendarView = calendarView;
116+
this.scrollContainer.nativeElement.scrollTop = 0;
117+
if (this.calendarView !== CalendarView.Month) {
118+
this.referenceChangeDetector.detectChanges();
119+
this.scrollToCurrentTimeMarker();
120+
}
93121
}
94122

95-
navigationEnable(calendarView: CalendarView){
123+
navigationEnable(calendarView: CalendarView) {
96124
let enable = false;
97125
const currentDate = moment(this.currentDate);
98126
const initialDate = moment(this.initialDate);
99-
if (calendarView === CalendarView.Month){
127+
if (calendarView === CalendarView.Month) {
100128
if (currentDate.month() === initialDate.month() && currentDate.year() === initialDate.year()) {
101129
enable = true;
102130
}
103131
}
104132
currentDate.add(1, 'day');
105-
if (currentDate > initialDate){
133+
if (currentDate > initialDate) {
106134
enable = true;
107135
}
108136
this.nextDateDisabled = enable;
109137
}
110138

111-
getTimeWork(startDate: Date, endDate: Date): number{
112-
if (!endDate){
139+
getTimeWork(startDate: Date, endDate: Date): number {
140+
if (!endDate) {
113141
return 30;
114142
}
115-
return new SubstractDatePipe().transformInMinutes( endDate , startDate);
143+
return new SubstractDatePipe().transformInMinutes(endDate, startDate);
116144
}
117145

118-
timeIsGreaterThan(startDate: Date, endDate: Date, timeRange: number ): boolean{
146+
timeIsGreaterThan(startDate: Date, endDate: Date, timeRange: number): boolean {
119147
const timeWorkInMinutes = this.getTimeWork(startDate, endDate);
120148
return timeWorkInMinutes > timeRange;
121149
}
122150

123-
isVisibleForCurrentView(currentCalendarView: CalendarView, desiredView: CalendarView ): boolean{
151+
isVisibleForCurrentView(currentCalendarView: CalendarView, desiredView: CalendarView): boolean {
124152
return currentCalendarView === desiredView;
125153
}
126154

127-
isVisibleForCurrentDate(): boolean{
155+
isVisibleForCurrentDate(): boolean {
128156
const currentDate: Date = new Date(this.currentDate);
129157
const initialDate: Date = new Date(this.initialDate);
130158
return currentDate.setHours(0, 0, 0, 0) === initialDate.setHours(0, 0, 0, 0);

0 commit comments

Comments
 (0)