summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views
diff options
context:
space:
mode:
authorMichael Mattiello (mcm) <mcm@odoo.com>2020-07-31 13:56:45 +0000
committerMichael Mattiello (mcm) <mcm@odoo.com>2020-08-11 12:32:54 +0000
commit441d9f65b3b90c37ce812d2addbe5b62d2d88229 (patch)
tree1a66c5fdaa27a2d0f5b6369299d334cf66426951 /addons/web/static/src/js/views
parent9a53b032395aba59cee9a2e5660ebf8d6136b978 (diff)
[IMP] base,web: add year scale to calendar
This commit adds a new year scale to the calendar view. It comes with a new view in the calendar, this view displays a 4x3 grid which cells are month views. Task: 2193921
Diffstat (limited to 'addons/web/static/src/js/views')
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js3
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_model.js23
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_renderer.js179
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_view.js1
4 files changed, 185 insertions, 21 deletions
diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index cddd25ee56a..2483199fcf0 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -44,6 +44,7 @@ var CalendarController = AbstractController.extend({
'click button.o_calendar_button_day': '_onButtonScale',
'click button.o_calendar_button_week': '_onButtonScale',
'click button.o_calendar_button_month': '_onButtonScale',
+ 'click button.o_calendar_button_year': '_onButtonScale',
}),
/**
* @override
@@ -277,7 +278,7 @@ var CalendarController = AbstractController.extend({
*/
_onOpenCreate: function (event) {
var self = this;
- if (this.model.get().scale === "month") {
+ if (["year", "month"].includes(this.model.get().scale)) {
event.data.allDay = true;
}
var data = this.model.calendarEventToRecord(event.data);
diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js
index 530f4775aa1..3cca3f3452d 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -12,7 +12,8 @@ var _t = core._t;
var scales = [
'day',
'week',
- 'month'
+ 'month',
+ 'year',
];
function dateToServer (date) {
@@ -273,6 +274,19 @@ return AbstractModel.extend({
this.data.highlight_date = this.data.target_date = start.clone();
this.data.start_date = this.data.end_date = start;
switch (this.data.scale) {
+ case 'year': {
+ const yearStart = this.data.start_date.clone().startOf('year');
+ let yearStartDay = this.week_start;
+ if (yearStart.day() < yearStartDay) {
+ // the 1st of January is before our week start (e.g. week start is Monday, and
+ // 01/01 is Sunday), so we go one week back
+ yearStartDay -= 7;
+ }
+ this.data.start_date = yearStart.day(yearStartDay).startOf('day');
+ this.data.end_date = this.data.end_date.clone()
+ .endOf('year').day(this.week_stop).endOf('day');
+ break;
+ }
case 'month':
var monthStart = this.data.start_date.clone().startOf('month');
@@ -426,7 +440,12 @@ return AbstractModel.extend({
hour12: false,
};
return {
- defaultView: (this.mode === "month")? "dayGridMonth" : ((this.mode === "week")? "timeGridWeek" : ((this.mode === "day")? "timeGridDay" : "timeGridWeek")),
+ defaultView: {
+ 'year': 'dayGridYear',
+ 'month': 'dayGridMonth',
+ 'week': 'timeGridWeek',
+ 'day': 'timeGridDay',
+ }[this.mode || 'week'],
header: false,
selectable: this.creatable && this.create_right,
selectMirror: true,
diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js
index 8a2b89d6287..2df67e8892f 100644
--- a/addons/web/static/src/js/views/calendar/calendar_renderer.js
+++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js
@@ -3,7 +3,6 @@ odoo.define('web.CalendarRenderer', function (require) {
var AbstractRenderer = require('web.AbstractRenderer');
var CalendarPopover = require('web.CalendarPopover');
-var config = require('web.config');
var core = require('web.core');
var Dialog = require('web.Dialog');
var field_utils = require('web.field_utils');
@@ -11,6 +10,7 @@ var FieldManagerMixin = require('web.FieldManagerMixin');
var relational_fields = require('web.relational_fields');
var session = require('web.session');
var Widget = require('web.Widget');
+const { createYearCalendarView } = require('/web/static/src/js/libs/fullcalendar.js');
var _t = core._t;
var qweb = core.qweb;
@@ -18,7 +18,8 @@ var qweb = core.qweb;
var scales = {
day: 'timeGridDay',
week: 'timeGridWeek',
- month: 'dayGridMonth'
+ month: 'dayGridMonth',
+ year: 'dayGridYear',
};
var SidebarFilterM2O = relational_fields.FieldMany2One.extend({
@@ -192,6 +193,7 @@ return AbstractRenderer.extend({
this.hideDate = params.hideDate;
this.hideTime = params.hideTime;
this.canDelete = params.canDelete;
+ this.canCreate = params.canCreate;
this._isInDOM = false;
},
/**
@@ -386,7 +388,7 @@ return AbstractRenderer.extend({
*/
_getFullCalendarOptions: function (fcOptions) {
var self = this;
- return Object.assign({}, this.state.fc_options, {
+ const options = Object.assign({}, this.state.fc_options, {
plugins: [
'moment',
'interaction',
@@ -408,27 +410,60 @@ return AbstractRenderer.extend({
$(self.calendarElement).find(_.str.sprintf('[data-event-id=%s]', eventData.id)).addClass('o_cw_custom_highlight');
self._renderEventPopover(eventData, $(eventClickInfo.el));
},
+ yearDateClick: function (info) {
+ self._unselectEvent();
+ info.view.unselect();
+ if (!info.events.length) {
+ if (info.selectable) {
+ const data = {
+ start: info.date,
+ allDay: true,
+ };
+ if (self.state.context.default_name) {
+ data.title = self.state.context.default_name;
+ }
+ self.trigger_up('openCreate', self._convertEventToFC3Event(data));
+ }
+ } else {
+ self._renderYearEventPopover(info.date, info.events, $(info.dayEl));
+ }
+ },
select: function (selectionInfo) {
// Clicking on the view, dispose any visible popover. Otherwise create a new event.
if (self.$('.o_cw_popover').length) {
self._unselectEvent();
+ }
+ var data = {start: selectionInfo.start, end: selectionInfo.end, allDay: selectionInfo.allDay};
+ if (self.state.context.default_name) {
+ data.title = self.state.context.default_name;
+ }
+ self.trigger_up('openCreate', self._convertEventToFC3Event(data));
+ if (self.state.scale === 'year') {
+ self.calendar.view.unselect();
} else {
- var data = {start: selectionInfo.start, end: selectionInfo.end, allDay: selectionInfo.allDay};
- if (self.state.context.default_name) {
- data.title = self.state.context.default_name;
- }
- self.trigger_up('openCreate', self._convertEventToFC3Event(data));
+ self.calendar.unselect();
}
- self.calendar.unselect();
},
eventRender: function (info) {
var event = info.event;
var element = $(info.el);
var view = info.view;
+ element.attr('data-event-id', event.id);
+ if (view.type === 'dayGridYear') {
+ const color = this.getColor(event.extendedProps.color_index);
+ if (typeof color === 'string') {
+ element.css({
+ backgroundColor: color,
+ });
+ } else if (typeof color === 'number') {
+ element.addClass(`o_calendar_color_${color}`);
+ } else {
+ element.addClass('o_calendar_color_1');
+ }
+ } else {
var $render = $(self._eventRender(event));
element.find('.fc-content').html($render.html());
element.addClass($render.attr('class'));
- element.attr('data-event-id', event.id);
// Add background if doesn't exist
if (!element.find('.fc-bg').length) {
@@ -455,10 +490,17 @@ return AbstractRenderer.extend({
element.on('dblclick', function () {
self.trigger_up('edit_event', {id: event.id});
});
+ }
},
datesRender: function (info) {
- // compute mode from view.type which is either 'dayGridMonth', 'timeGridWeek' or 'timeGridDay'
- var mode = info.view.type === 'dayGridMonth' ? 'month' : (info.view.type === 'timeGridWeek' ? 'week' : 'day');
+ // compute mode from view.type which is either
+ // 'dayGridYear', 'dayGridMonth', 'timeGridWeek' or 'timeGridDay'
+ const mode = {
+ dayGridYear: 'year',
+ dayGridMonth: 'month',
+ timeGridWeek: 'week',
+ timeGridDay: 'day',
+ }[info.view.type];
self.trigger_up('viewUpdated', {
mode: mode,
title: info.view.title,
@@ -509,6 +551,8 @@ return AbstractRenderer.extend({
successCB(self.state.data);
},
}, fcOptions);
+ options.plugins.push(createYearCalendarView(FullCalendar, options));
+ return options;
},
/**
* Initialize the main calendar
@@ -582,6 +626,7 @@ return AbstractRenderer.extend({
.removeClass('o_color o_selected_range');
var $a;
switch (this.state.scale) {
+ case 'year': $a = this.$small_calendar.find('td'); break;
case 'month': $a = this.$small_calendar.find('td'); break;
case 'week': $a = this.$small_calendar.find('tr:has(.ui-state-active)'); break;
case 'day': $a = this.$small_calendar.find('a.ui-state-active'); break;
@@ -706,6 +751,26 @@ return AbstractRenderer.extend({
return _t.database.parameters.time_format.search('%H') !== -1 ? 'HH:mm' : 'hh:mm a';
},
/**
+ * Returns event's formatted date for popovers.
+ *
+ * @private
+ * @param {moment} start
+ * @param {moment} end
+ * @param {boolean} showDayName
+ */
+ _getFormattedDate: function (start, end, showDayName) {
+ const isSameDayEvent = start.clone().add(1, 'minute')
+ .isSame(end.clone().subtract(1, 'minute'), 'day');
+ if (!isSameDayEvent && start.isSame(end, 'month')) {
+ // Simplify date-range if an event occurs into the same month (eg. '4-5 August 2019')
+ return start.clone().format('MMMM D') + '-' + end.clone().format('D, YYYY');
+ } else {
+ return isSameDayEvent ?
+ start.clone().format(showDayName ? 'dddd, LL' : 'LL') :
+ start.clone().format('LL') + ' - ' + end.clone().format('LL');
+ }
+ },
+ /**
* Prepare context to display in the popover.
*
* @private
@@ -748,12 +813,7 @@ return AbstractRenderer.extend({
}
if (!this.hideDate) {
- if (!isSameDayEvent && start.isSame(end, 'month')) {
- // Simplify date-range if an event occurs into the same month (eg. '4-5 August 2019')
- context.eventDate.date = start.clone().format('MMMM D') + '-' + end.clone().format('D, YYYY');
- } else {
- context.eventDate.date = isSameDayEvent ? start.clone().format('dddd, LL') : start.clone().format('LL') + ' - ' + end.clone().format('LL');
- }
+ context.eventDate.date = this._getFormattedDate(start, end, true);
if (eventData.extendedProps.record.allday && isSameDayEvent) {
context.eventDate.duration = _t("All day");
@@ -808,6 +868,89 @@ return AbstractRenderer.extend({
});
},
/**
+ * Render year event popover
+ *
+ * @private
+ * @param {Date} date
+ * @param {Object[]} events
+ * @param {jQueryElement} $el
+ */
+ _renderYearEventPopover: function (date, events, $el) {
+ const groupKeys = [];
+ const groupedEvents = {};
+ for (const event of events) {
+ const start = moment(event.extendedProps.r_start);
+ const end = moment(event.extendedProps.r_end);
+ const key = this._getFormattedDate(start, end, false);
+ if (!(key in groupedEvents)) {
+ groupedEvents[key] = [];
+ groupKeys.push({
+ key: key,
+ start: event.extendedProps.r_start,
+ end: event.extendedProps.r_end,
+ isSameDayEvent: start.clone().add(1, 'minute')
+ .isSame(end.clone().subtract(1, 'minute'), 'day'),
+ });
+ }
+ groupedEvents[key].push(event);
+ }
+
+ const popoverContent = qweb.render('CalendarView.yearEvent.popover', {
+ groupedEvents,
+ groupKeys: groupKeys
+ .sort((a, b) => {
+ if (a.isSameDayEvent) {
+ // if isSameDayEvent then put it before the others
+ return Number.MIN_SAFE_INTEGER;
+ } else if (b.isSameDayEvent) {
+ return Number.MAX_SAFE_INTEGER;
+ } else if (a.start.getTime() - b.start.getTime() === 0) {
+ return a.end.getTime() - b.end.getTime();
+ }
+ return a.start.getTime() - b.start.getTime();
+ })
+ .map(x => x.key),
+ canCreate: this.canCreate,
+ });
+
+ $el.popover({
+ animation: false,
+ delay: {
+ show: 50,
+ hide: 100
+ },
+ trigger: 'manual',
+ html: true,
+ content: popoverContent,
+ template: qweb.render('CalendarView.yearEvent.popover.placeholder'),
+ container: '.fc-dayGridYear-view',
+ }).on('shown.bs.popover', () => {
+ $('.o_cw_popover .o_cw_popover_close').on('click', () => this._unselectEvent());
+ $('.o_cw_popover .o_cw_popover_create').on('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ this._unselectEvent();
+ const data = {
+ start: date,
+ allDay: true,
+ };
+ if (this.state.context.default_name) {
+ data.title = this.state.context.default_name;
+ }
+ this.trigger_up('openCreate', this._convertEventToFC3Event(data));
+ });
+ $('.o_cw_popover .o_cw_popover_link').on('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ this._unselectEvent();
+ this.trigger_up('openEvent', {
+ _id: parseInt(e.target.dataset.id, 10),
+ title: e.target.dataset.title,
+ });
+ });
+ }).popover('show');
+ },
+ /**
* Scroll to the time set in the FullCalendar parameter
* @private
*/
diff --git a/addons/web/static/src/js/views/calendar/calendar_view.js b/addons/web/static/src/js/views/calendar/calendar_view.js
index 592fffbafee..7a9073adf6e 100644
--- a/addons/web/static/src/js/views/calendar/calendar_view.js
+++ b/addons/web/static/src/js/views/calendar/calendar_view.js
@@ -162,6 +162,7 @@ var CalendarView = AbstractView.extend({
this.rendererParams.hideDate = utils.toBoolElse(attrs.hide_date || '', false);
this.rendererParams.hideTime = utils.toBoolElse(attrs.hide_time || '', false);
this.rendererParams.canDelete = this.controllerParams.activeActions.delete;
+ this.rendererParams.canCreate = this.controllerParams.activeActions.create;
this.loadParams.fieldNames = _.uniq(fieldNames);
this.loadParams.mapping = mapping;