490 lines
20 KiB
JavaScript
490 lines
20 KiB
JavaScript
leantime.calendarController = (function () {
|
|
|
|
var closeModal = false;
|
|
|
|
// Latest todo-draggable initializer, refreshed on each initWidgetCalendar() call. The single
|
|
// global htmx.onLoad handler (registered once) calls THIS, so reloading the calendar widget
|
|
// rewires drag/drop to the current calendar instance instead of a stale closure.
|
|
var latestTodoDraggableInit = null;
|
|
|
|
//Functions
|
|
var initCalendar = function (userEvents) {
|
|
|
|
var date = new Date();
|
|
var d = date.getDate();
|
|
var m = date.getMonth();
|
|
var y = date.getFullYear();
|
|
|
|
var heightWindow = jQuery("body").height() - 260;
|
|
|
|
var calendar = jQuery('#calendar').fullCalendar({
|
|
timeZone: leantime.i18n.__("usersettings.timezone"),
|
|
height: heightWindow,
|
|
header: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'month,agendaWeek,agendaDay,listDay'
|
|
},
|
|
titleFormat: {
|
|
month: 'MMMM yyyy',
|
|
week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
|
|
day: 'dddd, MMM d, yyyy'
|
|
},
|
|
columnFormat: {
|
|
month: leantime.i18n.__("language.columnFormatMonth"),
|
|
week: leantime.i18n.__("language.columnFormatWeek"),
|
|
day: leantime.i18n.__("language.columnFormatday")
|
|
},
|
|
timeFormat: { // for event elements
|
|
'': leantime.dateHelper.getFormatFromSettings("timeformat", "luxon")
|
|
},
|
|
// locale
|
|
isRTL: leantime.i18n.__("language.isRTL") == "false" ? 0 : 1,
|
|
firstDay: leantime.i18n.__("language.firstDayOfWeek"),
|
|
monthNames: leantime.i18n.__("language.monthNames").split(","),
|
|
monthNamesShort: leantime.i18n.__("language.monthNamesShort").split(","),
|
|
dayNames: leantime.i18n.__("language.dayNames").split(","),
|
|
dayNamesShort: leantime.i18n.__("language.dayNamesShort").split(","),
|
|
buttonText: {
|
|
prev: '«',
|
|
next: '»',
|
|
prevYear: ' << ',
|
|
nextYear: ' >> ',
|
|
today: leantime.i18n.__("buttons.today"),
|
|
month: leantime.i18n.__("buttons.month"),
|
|
week: leantime.i18n.__("buttons.week"),
|
|
day: leantime.i18n.__("buttons.day")
|
|
},
|
|
select: function (start, end, allDay) {
|
|
var title = prompt(leantime.i18n.__("label.event_title"));
|
|
if (title) {
|
|
calendar.fullCalendar(
|
|
'renderEvent',
|
|
{
|
|
title: title,
|
|
start: start,
|
|
end: end,
|
|
allDay: allDay
|
|
},
|
|
true // make the event "stick"
|
|
);
|
|
}
|
|
calendar.fullCalendar('unselect');
|
|
},
|
|
events: userEvents,
|
|
eventColor: '#0866c6'
|
|
});
|
|
};
|
|
|
|
var initEventDatepickers = function () {
|
|
|
|
jQuery(document).ready(function () {
|
|
|
|
Date.prototype.addDays = function (days) {
|
|
this.setDate(this.getDate() + days);
|
|
return this;
|
|
};
|
|
jQuery.datepicker.setDefaults(
|
|
{ beforeShow: function (i) {
|
|
if (jQuery(i).attr('readonly')) {
|
|
return false; } } }
|
|
);
|
|
|
|
var dateFormat = leantime.dateHelper.getFormatFromSettings("dateformat", "jquery");
|
|
|
|
from = jQuery("#event_date_from")
|
|
.datepicker(
|
|
{
|
|
numberOfMonths: 1,
|
|
dateFormat: leantime.dateHelper.getFormatFromSettings("dateformat", "jquery"),
|
|
dayNames: leantime.i18n.__("language.dayNames").split(","),
|
|
dayNamesMin: leantime.i18n.__("language.dayNamesMin").split(","),
|
|
dayNamesShort: leantime.i18n.__("language.dayNamesShort").split(","),
|
|
monthNames: leantime.i18n.__("language.monthNames").split(","),
|
|
currentText: leantime.i18n.__("language.currentText"),
|
|
closeText: leantime.i18n.__("language.closeText"),
|
|
buttonText: leantime.i18n.__("language.buttonText"),
|
|
isRTL: leantime.i18n.__("language.isRTL") === "true" ? 1 : 0,
|
|
nextText: leantime.i18n.__("language.nextText"),
|
|
prevText: leantime.i18n.__("language.prevText"),
|
|
weekHeader: leantime.i18n.__("language.weekHeader"),
|
|
firstDay: leantime.i18n.__("language.firstDayOfWeek"),
|
|
}
|
|
)
|
|
.on(
|
|
"change",
|
|
function (date) {
|
|
to.datepicker("option", "minDate", getDate(this));
|
|
|
|
if (jQuery("#event_date_to").val() == '') {
|
|
jQuery("#event_date_to").val(jQuery("#event_date_from").val());
|
|
}
|
|
}
|
|
),
|
|
|
|
to = jQuery("#event_date_to").datepicker(
|
|
{
|
|
numberOfMonths: 1,
|
|
dateFormat: leantime.dateHelper.getFormatFromSettings("dateformat", "jquery"),
|
|
dayNames: leantime.i18n.__("language.dayNames").split(","),
|
|
dayNamesMin: leantime.i18n.__("language.dayNamesMin").split(","),
|
|
dayNamesShort: leantime.i18n.__("language.dayNamesShort").split(","),
|
|
monthNames: leantime.i18n.__("language.monthNames").split(","),
|
|
currentText: leantime.i18n.__("language.currentText"),
|
|
closeText: leantime.i18n.__("language.closeText"),
|
|
buttonText: leantime.i18n.__("language.buttonText"),
|
|
isRTL: leantime.i18n.__("language.isRTL") === "true" ? 1 : 0,
|
|
nextText: leantime.i18n.__("language.nextText"),
|
|
prevText: leantime.i18n.__("language.prevText"),
|
|
weekHeader: leantime.i18n.__("language.weekHeader"),
|
|
firstDay: leantime.i18n.__("language.firstDayOfWeek"),
|
|
}
|
|
)
|
|
.on(
|
|
"change",
|
|
function () {
|
|
from.datepicker("option", "maxDate", getDate(this));
|
|
}
|
|
);
|
|
|
|
function getDate( element )
|
|
{
|
|
var date;
|
|
try {
|
|
date = jQuery.datepicker.parseDate(dateFormat, element.value);
|
|
} catch ( error ) {
|
|
date = null;
|
|
console.log(error);
|
|
}
|
|
return date;
|
|
}
|
|
});
|
|
|
|
|
|
};
|
|
|
|
var initExportModal = function () {
|
|
|
|
var exportModalConfig = {
|
|
sizes: {
|
|
minW: 400,
|
|
minH: 350
|
|
},
|
|
resizable: true,
|
|
autoSizable: true,
|
|
callbacks: {
|
|
afterShowCont: function () {
|
|
|
|
jQuery(".formModal").nyroModal(exportModalConfig);
|
|
},
|
|
beforeClose: function () {
|
|
location.reload();
|
|
}
|
|
|
|
|
|
},
|
|
titleFromIframe: true
|
|
};
|
|
jQuery(".exportModal").nyroModal(exportModalConfig);
|
|
|
|
}
|
|
|
|
var initWidgetCalendar = function (element, initialView) {
|
|
|
|
let calendarEl = document.querySelector(element);
|
|
let userDateFormat = leantime.dateHelper.getFormatFromSettings("dateformat", "luxon");
|
|
let userTimeFormat = leantime.dateHelper.getFormatFromSettings("timeformat", "luxon");
|
|
|
|
|
|
const calendar = new FullCalendar.Calendar(calendarEl, {
|
|
timeZone: leantime.i18n.__("usersettings.timezone"),
|
|
height: 'calc(100% - 65px)',
|
|
stickyHeaderDates: true,
|
|
initialView: initialView,
|
|
eventStartEditable: true,
|
|
dayHeaderFormat: userDateFormat,
|
|
eventTimeFormat: userTimeFormat,
|
|
slotLabelFormat: userTimeFormat,
|
|
firstDay: leantime.i18n.__("language.firstDayOfWeek"),
|
|
views: {
|
|
multiMonthOneMonth: {
|
|
type: 'multiMonth',
|
|
duration: {months: 1},
|
|
multiMonthTitleFormat: {month: 'long', year: 'numeric'},
|
|
dayHeaderFormat: {weekday: 'short'},
|
|
},
|
|
timeGridDay: {
|
|
dayHeaders: false
|
|
},
|
|
listWeek: {
|
|
listDayFormat: {weekday: 'long'},
|
|
listDaySideFormat: leantime.dateHelper.getFormatFromSettings("dateformat", "luxon"),
|
|
}
|
|
},
|
|
droppable: true,
|
|
eventSources: eventSources,
|
|
|
|
editable: true,
|
|
headerToolbar: false,
|
|
nowIndicator: true,
|
|
bootstrapFontAwesome: {
|
|
close: 'fa-times',
|
|
prev: 'fa-chevron-left',
|
|
next: 'fa-chevron-right',
|
|
prevYear: 'fa-angle-double-left',
|
|
nextYear: 'fa-angle-double-right'
|
|
},
|
|
eventDrop: function (event) {
|
|
if (event.event.extendedProps.enitityType == "ticket") {
|
|
leantime.rpc('Tickets.Tickets.patchTicket', {
|
|
id: event.event.extendedProps.enitityId,
|
|
values: {
|
|
editFrom: luxon.DateTime.fromJSDate(event.event.start).toFormat(userDateFormat),
|
|
timeFrom: luxon.DateTime.fromJSDate(event.event.start).toFormat(userTimeFormat),
|
|
editTo: luxon.DateTime.fromJSDate(event.event.end).toFormat(userDateFormat),
|
|
timeTo: luxon.DateTime.fromJSDate(event.event.end).toFormat(userTimeFormat),
|
|
}
|
|
}).catch(function (error) {
|
|
jQuery.growl({ message: (error && error.message) ? error.message : leantime.i18n.__("short_notifications.not_saved"), style: "error" });
|
|
event.revert();
|
|
console.error('Could not update ticket dates', error);
|
|
});
|
|
} else if (event.event.extendedProps.enitityType == "event") {
|
|
leantime.rpc('Calendar.Calendar.patch', {
|
|
id: event.event.extendedProps.enitityId,
|
|
params: {
|
|
dateFrom: event.event.startStr,
|
|
dateTo: event.event.endStr
|
|
}
|
|
}).then(function (success) {
|
|
// Denied/failed update resolves to false — undo the visual move.
|
|
if (! success) { event.revert(); }
|
|
}).catch(function (error) {
|
|
console.error('Could not update event dates', error);
|
|
event.revert();
|
|
})
|
|
}
|
|
},
|
|
eventResize: function (event) {
|
|
if (event.event.extendedProps.enitityType == "ticket") {
|
|
leantime.rpc('Tickets.Tickets.patchTicket', {
|
|
id: event.event.extendedProps.enitityId,
|
|
values: {
|
|
editFrom: luxon.DateTime.fromJSDate(event.event.start).toFormat(userDateFormat),
|
|
timeFrom: luxon.DateTime.fromJSDate(event.event.start).toFormat(userTimeFormat),
|
|
editTo: luxon.DateTime.fromJSDate(event.event.end).toFormat(userDateFormat),
|
|
timeTo: luxon.DateTime.fromJSDate(event.event.end).toFormat(userTimeFormat),
|
|
}
|
|
}).catch(function (error) {
|
|
jQuery.growl({ message: (error && error.message) ? error.message : leantime.i18n.__("short_notifications.not_saved"), style: "error" });
|
|
event.revert();
|
|
console.error('Could not update ticket dates', error);
|
|
});
|
|
} else if (event.event.extendedProps.enitityType == "event") {
|
|
leantime.rpc('Calendar.Calendar.patch', {
|
|
id: event.event.extendedProps.enitityId,
|
|
params: {
|
|
dateFrom: event.event.startStr,
|
|
dateTo: event.event.endStr
|
|
}
|
|
}).then(function (success) {
|
|
// Denied/failed update resolves to false — undo the visual move.
|
|
if (! success) { event.revert(); }
|
|
}).catch(function (error) {
|
|
console.error('Could not update event dates', error);
|
|
event.revert();
|
|
})
|
|
}
|
|
|
|
},
|
|
eventReceive: function (event) {
|
|
|
|
leantime.rpc('Tickets.Tickets.patchTicket', {
|
|
id: event.event.id,
|
|
values: {
|
|
editFrom: luxon.DateTime.fromJSDate(event.event.start).toFormat(userDateFormat),
|
|
timeFrom: luxon.DateTime.fromJSDate(event.event.start).toFormat(userTimeFormat),
|
|
editTo: luxon.DateTime.fromJSDate(event.event.end).toFormat(userDateFormat),
|
|
timeTo: luxon.DateTime.fromJSDate(event.event.end).toFormat(userTimeFormat),
|
|
}
|
|
}).catch(function (error) {
|
|
jQuery.growl({ message: (error && error.message) ? error.message : leantime.i18n.__("short_notifications.not_saved"), style: "error" });
|
|
event.revert();
|
|
console.error('Could not update ticket dates', error);
|
|
});
|
|
|
|
},
|
|
eventDragStart: function (event) {
|
|
|
|
},
|
|
eventDidMount: function (info) {
|
|
|
|
if (info.isDraggable === false) {
|
|
jQuery(info.el).addClass("locked");
|
|
}
|
|
|
|
if (info.event.extendedProps.location != null
|
|
&& info.event.extendedProps.location != ""
|
|
&& info.event.extendedProps.location.indexOf("http") == 0
|
|
) {
|
|
//jQuery(info.el).prepend("<div class='pull-right'><a href='"+info.event.extendedProps.location+"'>Join Call</a></div>")
|
|
jQuery(info.el).attr("href", info.event.extendedProps.location);
|
|
jQuery(info.el).attr("target", "_blank");
|
|
}
|
|
}
|
|
});
|
|
|
|
jQuery(document).ready(function () {
|
|
//let tickets = jQuery("#yourToDoContainer")[0];
|
|
|
|
// Set up draggable for each ticket box
|
|
// setupDraggableTickets();
|
|
//
|
|
// Initialize the ThirdPartyDraggable for the todo container
|
|
|
|
if(jQuery("#yourToDoContainer").length > 0) {
|
|
initializeThirdPartyDraggable(jQuery("#yourToDoContainer")[0]);
|
|
}
|
|
|
|
initButtons();
|
|
|
|
calendar.scrollToTime(Date.now());
|
|
|
|
|
|
});
|
|
|
|
// function setupDraggableTickets() {
|
|
// jQuery("#yourToDoContainer").find(".ticketBox").each(function () {
|
|
// setupTicketDraggable(jQuery(this));
|
|
// });
|
|
// }
|
|
//
|
|
// function setupTicketDraggable(ticketElement) {
|
|
// var currentTicket = ticketElement;
|
|
// currentTicket.data('event', {
|
|
// id: currentTicket.attr("data-val"),
|
|
// title: currentTicket.find(".titleContainer strong").text(),
|
|
// color: 'var(--accent2)',
|
|
// enitityType: "ticket",
|
|
// url: '#/tickets/showTicket/' + currentTicket.attr("data-val"),
|
|
// });
|
|
//
|
|
// currentTicket.draggable({
|
|
//
|
|
// zIndex: 999999,
|
|
// revert: true, // will cause the event to go back to its
|
|
// revertDuration: 0, // original position after the drag
|
|
// helper: "clone",
|
|
// appendTo: '.maincontent',
|
|
// cursor: "grab",
|
|
// cursorAt: {bottom: 5, right: 5},
|
|
// distance: 10, // Minimum distance before drag starts
|
|
// delay: 150, // Small delay to allow for sortable to initialize first
|
|
// });
|
|
// }
|
|
|
|
function initializeThirdPartyDraggable(element) {
|
|
|
|
var tickets = element;
|
|
if (tickets) {
|
|
new FullCalendar.ThirdPartyDraggable(tickets, {
|
|
itemSelector: '.draggable-todo',
|
|
mirrorClass: 'dragging-mirror',
|
|
eventDragMinDistance: 10,
|
|
mirrorSelector: function (el) {
|
|
return el.closest('.ticketBox');
|
|
},
|
|
eventData: function (eventEl) {
|
|
|
|
let ticketEventData = jQuery(eventEl).data("event");
|
|
|
|
return {
|
|
id: ticketEventData.id,
|
|
title: ticketEventData.title,
|
|
color: ticketEventData.color,
|
|
enitityType: "ticket",
|
|
duration: '01:00',
|
|
url: ticketEventData.url,
|
|
};
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
calendar.scrollToTime(Date.now());
|
|
};
|
|
|
|
// Point the shared reference at THIS init's closure (bound to the current calendar instance).
|
|
latestTodoDraggableInit = initializeThirdPartyDraggable;
|
|
|
|
function initButtons() {
|
|
|
|
calendar.setOption('locale', leantime.i18n.__("language.code"));
|
|
calendar.render();
|
|
|
|
calendar.scrollToTime(Date.now());
|
|
|
|
jQuery('.minCalendar .fc-prev-button').click(function () {
|
|
calendar.prev();
|
|
calendar.getCurrentData()
|
|
});
|
|
jQuery('.minCalendar .fc-next-button').click(function () {
|
|
calendar.next();
|
|
});
|
|
jQuery('.minCalendar .fc-today-button').click(function () {
|
|
calendar.today();
|
|
});
|
|
jQuery(".minCalendar .calendarViewSelect").on("click", function (e) {
|
|
|
|
var newView = jQuery(this).data("value");
|
|
calendar.changeView(newView);
|
|
|
|
// Show the day selector only in day view
|
|
if (newView === 'timeGridDay') {
|
|
jQuery('.day-selector').show();
|
|
} else {
|
|
jQuery('.day-selector').hide();
|
|
}
|
|
|
|
leantime.rpc('Api.Api.setSubmenuState', {
|
|
submenu: "dashboardCalendarView",
|
|
state: newView
|
|
}).catch(function (e) { console.error('Could not update submenu state', e); });
|
|
|
|
});
|
|
|
|
// Initialize day selector buttons (only active in day view)
|
|
jQuery('.day-button').on('click', function() {
|
|
var date = jQuery(this).data('date');
|
|
calendar.gotoDate(date);
|
|
|
|
// Update active state
|
|
jQuery('.day-button').removeClass('active');
|
|
jQuery(this).addClass('active');
|
|
});
|
|
}
|
|
|
|
// Register once. This runs inside an init function that HTMX re-invokes on every
|
|
// swap, so without the guard each load stacked another global onLoad handler
|
|
// (handler leak → compounding churn on the dashboard).
|
|
if (!window.leantime._calendarTodoOnLoadRegistered) {
|
|
window.leantime._calendarTodoOnLoadRegistered = true;
|
|
htmx.onLoad(function (content) {
|
|
// Find any todo containers that were loaded via HTMX. Call the LATEST initializer so
|
|
// drag/drop binds to the current calendar instance, not the one from first init.
|
|
if (content.id == "yourToDoContainer" && latestTodoDraggableInit) {
|
|
latestTodoDraggableInit(content);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// Make public what you want to have public, everything else is private
|
|
return {
|
|
initCalendar:initCalendar,
|
|
initEventDatepickers:initEventDatepickers,
|
|
initExportModal:initExportModal,
|
|
initWidgetCalendar:initWidgetCalendar
|
|
};
|
|
})();
|