diff options
| author | Michael Mattiello (mcm) <mcm@odoo.com> | 2020-07-31 13:56:45 +0000 |
|---|---|---|
| committer | Michael Mattiello (mcm) <mcm@odoo.com> | 2020-08-11 12:32:54 +0000 |
| commit | 441d9f65b3b90c37ce812d2addbe5b62d2d88229 (patch) | |
| tree | 1a66c5fdaa27a2d0f5b6369299d334cf66426951 /addons/web/static/src/js/views | |
| parent | 9a53b032395aba59cee9a2e5660ebf8d6136b978 (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')
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; |
