|
|
|
@ -6,7 +6,6 @@ import { |
|
|
|
|
EventEmitter, |
|
|
|
|
Injector, |
|
|
|
|
Input, |
|
|
|
|
OnInit, |
|
|
|
|
Output, |
|
|
|
|
SecurityContext, |
|
|
|
|
ViewChild, |
|
|
|
@ -20,9 +19,7 @@ import {StateService} from "@uirouter/core"; |
|
|
|
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
|
|
|
|
import {DomSanitizer} from "@angular/platform-browser"; |
|
|
|
|
import timeGrid from '@fullcalendar/timegrid'; |
|
|
|
|
import {Duration, EventApi, EventInput, View} from '@fullcalendar/core'; |
|
|
|
|
import {EventSourceError} from '@fullcalendar/core/structs/event-source'; |
|
|
|
|
import {ToolbarInput} from '@fullcalendar/core/types/input-types'; |
|
|
|
|
import {CalendarOptions, Duration, EventApi, EventInput} from '@fullcalendar/core'; |
|
|
|
|
import {ConfigurationService} from "core-app/modules/common/config/configuration.service"; |
|
|
|
|
import {FilterOperator} from "core-components/api/api-v3/api-v3-filter-builder"; |
|
|
|
|
import {TimeEntryResource} from "core-app/modules/hal/resources/time-entry-resource"; |
|
|
|
@ -40,7 +37,6 @@ import {APIV3Service} from "core-app/modules/apiv3/api-v3.service"; |
|
|
|
|
interface CalendarViewEvent { |
|
|
|
|
el:HTMLElement; |
|
|
|
|
event:EventApi; |
|
|
|
|
jsEvent:MouseEvent; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interface CalendarMoveEvent { |
|
|
|
@ -49,8 +45,6 @@ interface CalendarMoveEvent { |
|
|
|
|
oldEvent:EventApi; |
|
|
|
|
delta:Duration; |
|
|
|
|
revert:() => void; |
|
|
|
|
jsEvent:Event; |
|
|
|
|
view:View; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// An array of all the days that are displayed. The zero index represents Monday.
|
|
|
|
@ -74,13 +68,15 @@ const ADD_ENTRY_PROHIBITED_CLASS_NAME = '-prohibited'; |
|
|
|
|
HalResourceEditingService |
|
|
|
|
] |
|
|
|
|
}) |
|
|
|
|
export class TimeEntryCalendarComponent implements OnInit, AfterViewInit { |
|
|
|
|
export class TimeEntryCalendarComponent implements AfterViewInit { |
|
|
|
|
@ViewChild(FullCalendarComponent) ucCalendar:FullCalendarComponent; |
|
|
|
|
@Input() projectIdentifier:string; |
|
|
|
|
@Input() static:boolean = false; |
|
|
|
|
|
|
|
|
|
@Input() set displayedDays(days:DisplayedDays) { |
|
|
|
|
this.setHiddenDays(days); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Output() entries = new EventEmitter<CollectionResource<TimeEntryResource>>(); |
|
|
|
|
|
|
|
|
|
// Not used by the calendar but rather is the maximum/minimum of the graph.
|
|
|
|
@ -89,24 +85,7 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit { |
|
|
|
|
public labelIntervalHours = 2; |
|
|
|
|
public scaleRatio = 1; |
|
|
|
|
|
|
|
|
|
public calendarPlugins = [timeGrid, interactionPlugin]; |
|
|
|
|
public calendarEvents:Function; |
|
|
|
|
public calendarHeader:ToolbarInput|boolean = { |
|
|
|
|
right: '', |
|
|
|
|
center: 'title', |
|
|
|
|
left: 'prev,next today' |
|
|
|
|
}; |
|
|
|
|
public calendarSlotLabelFormat = (info:any) => (this.maxHour - info.date.hour) / this.scaleRatio; |
|
|
|
|
public calendarSlotLabelInterval = `${this.labelIntervalHours}:00:00`; |
|
|
|
|
public calendarContentHeight = 605; |
|
|
|
|
public calendarAllDaySlot = false; |
|
|
|
|
public calendarDisplayEventTime = false; |
|
|
|
|
public calendarSlotEventOverlap = false; |
|
|
|
|
public calendarEditable = false; |
|
|
|
|
public calendarMinTime = `${this.minHour - 1}:00:00`; |
|
|
|
|
public calendarMaxTime = `${this.maxHour}:00:00`; |
|
|
|
|
public calendarEventOverlap = (stillEvent:any) => !stillEvent.classNames.includes(TIME_ENTRY_CLASS_NAME); |
|
|
|
|
|
|
|
|
|
protected memoizedTimeEntries:{ start:Date, end:Date, entries:Promise<CollectionResource<TimeEntryResource>> }; |
|
|
|
|
public memoizedCreateAllowed:boolean = false; |
|
|
|
|
public hiddenDays:number[] = []; |
|
|
|
@ -115,6 +94,31 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit { |
|
|
|
|
logTime: this.i18n.t('js.button_log_time') |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
calendarOptions:CalendarOptions = { |
|
|
|
|
editable: false, |
|
|
|
|
locale: this.i18n.locale, |
|
|
|
|
fixedWeekCount: false, |
|
|
|
|
headerToolbar: { |
|
|
|
|
right: '', |
|
|
|
|
center: 'title', |
|
|
|
|
left: 'prev,next today' |
|
|
|
|
}, |
|
|
|
|
initialView: 'timeGridWeek', |
|
|
|
|
firstDay: this.configuration.startOfWeek(), |
|
|
|
|
hiddenDays: [], |
|
|
|
|
contentHeight: 605, |
|
|
|
|
slotEventOverlap: false, |
|
|
|
|
slotLabelInterval: `${this.labelIntervalHours}:00:00`, |
|
|
|
|
slotLabelFormat: (info:any) => ((this.maxHour - info.date.hour) / this.scaleRatio).toString(), |
|
|
|
|
allDaySlot: false, |
|
|
|
|
displayEventTime: false, |
|
|
|
|
slotMinTime: `${this.minHour - 1}:00:00`, |
|
|
|
|
slotMaxTime: `${this.maxHour}:00:00`, |
|
|
|
|
events: this.calendarEventsFunction.bind(this), |
|
|
|
|
eventOverlap: (stillEvent:any) => !stillEvent.classNames.includes(TIME_ENTRY_CLASS_NAME), |
|
|
|
|
plugins: [timeGrid, interactionPlugin] |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
constructor(readonly states:States, |
|
|
|
|
readonly apiV3Service:APIV3Service, |
|
|
|
|
readonly $state:StateService, |
|
|
|
@ -131,18 +135,14 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit { |
|
|
|
|
private browserDetector:BrowserDetector) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ngOnInit() { |
|
|
|
|
this.initializeCalendar(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ngAfterViewInit() { |
|
|
|
|
// The full-calendar component's outputs do not seem to work
|
|
|
|
|
// see: https://github.com/fullcalendar/fullcalendar-angular/issues/228#issuecomment-523505044
|
|
|
|
|
// Therefore, setting the outputs via the underlying API
|
|
|
|
|
this.ucCalendar.getApi().setOption('eventRender', (event:CalendarViewEvent) => { |
|
|
|
|
this.ucCalendar.getApi().setOption('eventDidMount', (event:CalendarViewEvent) => { |
|
|
|
|
this.alterEventEntry(event); |
|
|
|
|
}); |
|
|
|
|
this.ucCalendar.getApi().setOption('eventDestroy', (event:CalendarViewEvent) => { |
|
|
|
|
this.ucCalendar.getApi().setOption('eventWillUnmount', (event:CalendarViewEvent) => { |
|
|
|
|
this.beforeEventRemove(event); |
|
|
|
|
}); |
|
|
|
|
this.ucCalendar.getApi().setOption('eventClick', (event:CalendarViewEvent) => { |
|
|
|
@ -155,7 +155,7 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit { |
|
|
|
|
|
|
|
|
|
public calendarEventsFunction(fetchInfo:{ start:Date, end:Date }, |
|
|
|
|
successCallback:(events:EventInput[]) => void, |
|
|
|
|
failureCallback:(error:EventSourceError) => void):void|PromiseLike<EventInput[]> { |
|
|
|
|
failureCallback:(error:unknown) => void):void|PromiseLike<EventInput[]> { |
|
|
|
|
|
|
|
|
|
this.fetchTimeEntries(fetchInfo.start, fetchInfo.end) |
|
|
|
|
.then((collection) => { |
|
|
|
@ -212,7 +212,8 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit { |
|
|
|
|
// But it will trigger repainting the calendar.
|
|
|
|
|
// Weirdly, this.ucCalendar.getApi().rerender() does not.
|
|
|
|
|
this.ucCalendar.getApi().setOption('slotLabelFormat', (info:any) => { |
|
|
|
|
return (this.maxHour - info.date.hour) / this.scaleRatio; |
|
|
|
|
let val = (this.maxHour - info.date.hour) / this.scaleRatio; |
|
|
|
|
return val.toString(); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -330,30 +331,6 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit { |
|
|
|
|
['user_id', '=', ['me']] as [string, FilterOperator, [string]]]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private initializeCalendar() { |
|
|
|
|
this.calendarEvents = this.calendarEventsFunction.bind(this); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public get calendarEventLimit() { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public get calendarLocale() { |
|
|
|
|
return this.i18n.locale; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public get calendarFixedWeekCount() { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public get calendarDefaultView() { |
|
|
|
|
return 'timeGridWeek'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public get calendarFirstDay() { |
|
|
|
|
return this.configuration.startOfWeek(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private get calendarElement() { |
|
|
|
|
return jQuery(this.element.nativeElement).find('.fc-view-container'); |
|
|
|
|
} |
|
|
|
@ -603,12 +580,16 @@ export class TimeEntryCalendarComponent implements OnInit, AfterViewInit { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected setHiddenDays(displayedDays:DisplayedDays) { |
|
|
|
|
this.hiddenDays = Array.from(displayedDays, (value, index) => { |
|
|
|
|
if (!value) { |
|
|
|
|
return (index + 1) % 7; |
|
|
|
|
} else { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
}).filter((value) => value !== null) as number[]; |
|
|
|
|
let hiddenDays:number[] = Array |
|
|
|
|
.from(displayedDays, (value, index) => { |
|
|
|
|
if (!value) { |
|
|
|
|
return (index + 1) % 7; |
|
|
|
|
} else { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
.filter((value) => value !== null) as number[]; |
|
|
|
|
|
|
|
|
|
this.calendarOptions = { ...this.calendarOptions, hiddenDays }; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|