Files
ERP/sophal/js/view/view.calendar.js

770 lines
18 KiB
JavaScript

/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/* global d3: true */
"use strict";
var ui = angular.module('axelor.ui');
ui.controller('CalendarViewCtrl', CalendarViewCtrl);
CalendarViewCtrl.$inject = ['$scope', '$element'];
function CalendarViewCtrl($scope, $element) {
ui.DSViewCtrl('calendar', $scope, $element);
var ds = $scope._dataSource;
var view = {};
var colors = {};
var initialized = false;
$scope.onShow = function(viewPromise) {
if (initialized) {
return $scope.refresh();
}
viewPromise.then(function(){
var schema = $scope.schema;
initialized = true;
view = {
start: schema.eventStart,
stop: schema.eventStop,
length: parseInt(schema.eventLength) || 0,
color: schema.colorBy,
title: schema.items[0].name
};
$scope._viewResolver.resolve(schema, $element);
$scope.updateRoute();
});
};
$scope.isAgenda = function() {
var field = this.fields[view.start];
return field && field.type === "datetime";
};
var d3_colors = d3.scale.category10().range().concat(
d3.scale.category20().range());
function nextColor(n) {
if (n === undefined || n < 0 || n >= d3_colors.length) {
n = _.random(0, d3_colors.length);
}
var c = d3.rgb(d3_colors[n]);
return {
bg: "" + c,
fg: "" + c.brighter(99),
bc: "" + c.darker(0.9)
};
}
$scope.fetchItems = function(start, end, callback) {
var fields = _.pluck(this.fields, 'name');
var criteria = {
operator: "and",
criteria: [{
fieldName: view.start,
operator: ">=",
value: start
}, {
fieldName: view.start,
operator: "<=",
value: end
}]
};
// make sure to include items whose end date falls in current range
if (view.stop) {
criteria = {
operator: "or",
criteria: [criteria, {
operator: "and",
criteria: [{
fieldName: view.stop,
operator: ">=",
value: start
}, {
fieldName: view.stop,
operator: "<=",
value: end
}]
}]
};
}
// consider stored filter
if (ds._filter) {
if (ds._filter.criteria) {
criteria = {
operator: "and",
criteria: [criteria].concat(ds._filter.criteria)
};
}
if (_.size(ds._filter._domains) > 0) {
criteria._domains = ds._filter._domains;
}
}
var opts = {
fields: fields,
filter: criteria,
domain: this._domain,
context: this._context,
store: false,
limit: -1
};
ds.search(opts).success(function(records) {
var items = _.clone(records);
items.sort(function (x, y) { return x.id - y.id; });
updateColors(items, true);
callback(records);
});
};
function updateColors(records, reset) {
var colorBy = view.color;
var colorField = $scope.fields[colorBy];
if (!colorField) {
return colors;
}
if (reset) {
colors = {};
}
_.each(records, function(record) {
var item = record[colorBy];
if (item === null || item === undefined) {
return;
}
var key = $scope.getColorKey(record, item);
var title = colorField.targetName ? item[colorField.targetName] : item;
if (colorField.selectionList) {
var select = _.find(colorField.selectionList, function (select) {
return ("" + select.value) === ("" + title);
});
if (select) {
title = select.title;
}
}
if (!colors[key]) {
colors[key] = {
item: item,
title: title || _t('Unknown'),
color: nextColor(_.size(colors))
};
}
record.$colorKey = key;
});
return colors;
}
$scope.getColors = function() {
return colors;
};
$scope.getColor = function(record) {
var key = this.getColorKey(record);
if (key && !colors[key]) {
updateColors([record], false);
}
if (colors[key]) {
return colors[key].color;
}
return nextColor(0);
};
$scope.getColorKey = function(record, key) {
if (key) {
return "" + (key.id || key);
}
if (record) {
return this.getColorKey(null, record[view.color]);
}
return null;
};
$scope.getEventInfo = function(record) {
var info = {},
value;
value = record[view.start];
info.start = value ? moment(value) : moment();
value = record[view.stop];
info.end = value ? moment(value) : moment(info.start).add(view.length || 1, "hours");
var title = this.fields[view.title];
var titleText = null;
if (title) {
value = record[title.name];
if (title.targetName) {
value = value[title.targetName];
} else if (title.selectionList) {
var select = _.find(title.selectionList, function (select) {
return ("" + select.value) === ("" + value);
});
if (select) {
titleText = select.title;
}
}
titleText = titleText || value || _t('Unknown');
}
info.title = ("" + titleText);
info.allDay = isAllDay(info);
info.className = info.allDay ? "calendar-event-allDay" : "calendar-event-day";
return info;
};
function isAllDay(event) {
if($scope.fields[view.start] && $scope.fields[view.start].type === 'date') {
return true;
}
var start = moment(event.start);
var end = moment(event.end);
if (start.format("HH:mm") !== "00:00") {
return false;
}
return !event.end || end.format("HH:mm") === "00:00";
}
$scope.onEventChange = function(event, delta) {
var record = _.clone(event.record);
var start = event.start;
var end = event.end;
if (isAllDay(event)) {
start = start.clone().startOf("day").local();
end = (end || start).clone().startOf("day").local();
}
record[view.start] = start;
record[view.stop] = end;
$scope.record = record;
function reset() {
$scope.record = null;
}
function doSave() {
return ds.save(record).success(function(res){
return $scope.refresh();
});
}
var handler = $scope.onChangeHandler;
if (handler) {
var promise = handler.onChange().then(function () {
return doSave();
});
promise.success = function(fn) {
promise.then(fn);
return promise;
};
promise.error = function (fn) {
promise.then(null, fn);
return promise;
};
return promise;
}
return doSave();
};
$scope.removeEvent = function(event, callback) {
ds.remove(event.record).success(callback);
};
$scope.select = function() {
};
$scope.refresh = function() {
};
$scope.getRouteOptions = function() {
var args = [],
query = {};
return {
mode: 'calendar',
args: args,
query: query
};
};
$scope.setRouteOptions = function(options) {
var opts = options || {};
if (opts.mode === "calendar") {
return $scope.updateRoute();
}
var params = $scope._viewParams;
if (params.viewType !== "calendar") {
return $scope.show();
}
};
$scope.canNext = function() {
return true;
};
$scope.canPrev = function() {
return true;
};
}
angular.module('axelor.ui').directive('uiViewCalendar', ['ViewService', 'ActionService', function(ViewService, ActionService) {
function link(scope, element, attrs, controller) {
var main = element.children('.calendar-main');
var mini = element.find('.calendar-mini');
var legend = element.find('.calendar-legend');
var ctx = (scope._viewParams.context||{});
var params = (scope._viewParams.params||{});
var schema = scope.schema;
var mode = ctx.calendarMode || params.calendarMode || schema.mode || "month";
var date = ctx.calendarDate || params.calendarDate;
var editable = schema.editable === undefined ? true : schema.editable;
var calRange = {};
var RecordManager = (function () {
var records = [],
current = [];
function add(record) {
if (!record || _.isArray(record)) {
records = record || [];
return filter();
}
var found = _.findWhere(records, {
id: record.id
});
if (!found) {
found = record;
records.push(record);
}
if (found !== record) {
_.extend(found, record);
}
return filter();
}
function remove(record) {
records = _.filter(records, function (item) {
return record.id !== item.id;
});
return filter();
}
function filter() {
var selected = [];
_.each(scope.getColors(), function (color) {
if (color.checked) {
selected.push(scope.getColorKey(null, color.item));
}
});
main.fullCalendar('removeEventSource', current);
current = records;
if (selected.length) {
current = _.filter(records, function(record) {
return _.contains(selected, record.$colorKey);
});
}
main.fullCalendar('addEventSource', current);
adjustSize();
}
function events(start, end, timezone, callback) {
calRange.start = start;
calRange.end = end;
scope._viewPromise.then(function(){
scope.fetchItems(start, end, function(items) {
callback([]);
add(items);
});
});
}
return {
add: add,
remove: remove,
filter: filter,
events: events
};
}());
if (date) {
date = moment(date).toDate();
}
mini.datepicker({
showOtherMonths: true,
selectOtherMonths: true,
onSelect: function(dateStr) {
main.fullCalendar('gotoDate', mini.datepicker('getDate'));
}
});
if (date) {
mini.datepicker('setDate', date);
}
var lang = axelor.config["user.lang"] || 'en';
var options = {
header: false,
timeFormat: 'h(:mm)t',
axisFormat: 'h(:mm)t',
timezone: 'local',
lang: lang,
editable: editable,
selectable: editable,
selectHelper: editable,
select: function(start, end) {
var event = {
start: start,
end: end
};
// all day
if (!start.hasTime() && !end.hasTime()) {
event.start = start.clone().startOf("day").local();
event.end = end.clone().startOf("day").local();
}
scope.$applyAsync(function(){
scope.showEditor(event);
});
main.fullCalendar('unselect');
},
defaultDate: date,
events: RecordManager,
eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
hideBubble();
scope.onEventChange(event, delta).error(function(){
revertFunc();
});
},
eventResize: function( event, delta, revertFunc, jsEvent, ui, view ) {
scope.onEventChange(event, delta).error(function(){
revertFunc();
});
},
eventClick: function(event, jsEvent, view) {
showBubble(event, jsEvent.target);
},
eventDataTransform: function(record) {
return updateEvent(null, record);
},
viewDisplay: function(view) {
hideBubble();
mini.datepicker('setDate', main.fullCalendar('getDate'));
},
allDayText: _t('All Day')
};
if (lang.indexOf('fr') === 0) {
_.extend(options, {
timeFormat: 'H:mm',
axisFormat: 'H:mm',
firstDay: 1,
views: {
week: {
titleFormat: 'D MMM YYYY',
columnFormat: 'ddd DD/MM'
},
day: {
titleFormat: 'D MMM YYYY'
}
}
});
}
main.fullCalendar(options);
var editor = null;
var bubble = null;
function hideBubble() {
if (bubble) {
bubble.popover('destroy');
bubble = null;
}
}
function showBubble(event, elem) {
hideBubble();
bubble = $(elem).popover({
html: true,
title: "<b>" + event.title + "</b>",
placement: "top",
container: 'body',
content: function() {
var html = $("<div></div>").addClass("calendar-bubble-content");
var start = event.start;
var end = event.end && event.allDay ? moment(event.end).add(-1, 'second') : event.end;
var singleDay = (event.allDay || !scope.isAgenda()) && (!end || moment(start).isSame(end, 'day'));
var dateFormat = !scope.isAgenda() || event.allDay ? "ddd D MMM" : "ddd D MMM HH:mm";
$("<span>").text(moment(start).format(dateFormat)).appendTo(html);
if (schema.eventStop && end && !singleDay) {
$("<span> - </span>").appendTo(html);
$("<span>").text(moment(end).format(dateFormat)).appendTo(html);
}
$("<hr>").appendTo(html);
if (scope.isEditable()) {
$('<a href="javascript: void(0)" style="margin-right: 5px;"></a>').text(_t("Delete"))
.appendTo(html)
.click(function(e){
hideBubble();
scope.$applyAsync(function(){
scope.removeEvent(event, function() {
RecordManager.remove(event.record);
});
});
});
}
$('<a class="pull-right" href="javascript: void(0)"></a>')
.append(_t("Edit event")).append("<strong> »</strong>")
.appendTo(html)
.click(function(e){
hideBubble();
scope.$applyAsync(function(){
scope.showEditor(event);
});
});
return html;
}
});
bubble.popover('show');
}
$("body").on("mousedown", function(e){
var elem = $(e.target || e.srcElement);
if (!bubble || bubble.is(elem) || bubble.has(elem).length) {
return;
}
if (!elem.parents().is(".popover")) {
hideBubble();
}
});
function updateEvent(event, record) {
if (!event || !event.id) {
var color = scope.getColor(record);
event = {
id: record.id,
record: record,
backgroundColor: color.bg,
borderColor: color.bc,
textColor: color.fg
};
} else {
_.extend(event.record, record);
}
event = _.extend(event, scope.getEventInfo(record));
return event;
}
scope.editorCanSave = true;
scope.showEditor = function(event) {
var view = this.schema;
var record = _.extend({}, event.record);
record[view.eventStart] = event.start;
record[view.eventStop] = event.end;
if (!editor) {
editor = ViewService.compile('<div ui-editor-popup></div>')(scope.$new());
editor.data('$target', element);
}
var popup = editor.isolateScope();
popup.show(record, function(result) {
RecordManager.add(result);
});
popup.waitForActions(function() {
if (!record || !record.id) {
popup.$broadcast("on:new");
} else {
popup.setEditable(scope.isEditable());
}
});
};
scope.isEditable = function() {
return editable;
};
scope.refresh = function(record) {
if (calRange.start && calRange.end) {
return RecordManager.events(calRange.start, calRange.end, options.timezone, function () {});
}
return main.fullCalendar("refetchEvents");
};
scope.filterEvents = function() {
RecordManager.filter();
};
scope.pagerText = function() {
return main.fullCalendar("getView").title;
};
scope.isMode = function(name) {
return mode === name;
};
scope.onMode = function(name) {
mode = name;
if (name === "week") {
name = scope.isAgenda() ? "agendaWeek" : "basicWeek";
}
if (name === "day") {
name = scope.isAgenda() ? "agendaDay" : "basicDay";
}
main.fullCalendar("changeView", name);
};
scope.onRefresh = function () {
scope.refresh();
};
scope.onNext = function() {
main.fullCalendar('next');
};
scope.onPrev = function() {
main.fullCalendar('prev');
};
scope.onToday = function() {
main.fullCalendar('today');
};
scope.onChangeHandler = null;
if (schema.onChange) {
scope.onChangeHandler = ActionService.handler(scope, element, {
action: schema.onChange
});
}
function adjustSize() {
if (main.is(':hidden')) {
return;
}
hideBubble();
main.fullCalendar('render');
main.fullCalendar('option', 'height', element.height());
legend.css("max-height", legend.parent().height() - mini.height()
- (parseInt(legend.css('marginTop')) || 0)
- (parseInt(legend.css('marginBottom')) || 0));
}
scope.$onAdjust(adjustSize, 100);
scope.$callWhen(function () {
return main.is(':visible');
}, function() {
element.parents('.view-container:first').css('overflow', 'inherit');
scope.onMode(mode);
adjustSize();
}, 100);
}
return {
link: function(scope, element, attrs, controller) {
scope._viewPromise.then(function(){
link(scope, element, attrs, controller);
});
},
replace: true,
template:
'<div>'+
'<div class="calendar-main" ui-attach-scroll=".fc-scroller"></div>'+
'<div class="calendar-side">'+
'<div class="calendar-mini"></div>'+
'<form class="form calendar-legend">'+
'<label class="checkbox" ng-repeat="color in getColors()" style="color: {{color.color.bc}}">'+
'<input type="checkbox" ng-click="filterEvents()" ng-model="color.checked"> {{color.title}}</label>'+
'</div>'+
'</div>'+
'</div>'
};
}]);
})();