[REF] web: refactor the calendar view
Improve design and introducing several usability improvements:
- Removed customization of an event card, just display event title.
- Added popover, popover consist of two sections:
1) Show the title, date and time of an event. Date and time are
automatically computed according to the user timing preferences,
language and event duration. Can be hidden with hide_date/hide_time
attribute respectively.
2) Display all the fields specified inside the <calendar/> element.
fields can be specified with a widget and its options.
e.g. <field name="name" string="Custom Name" widget="char" options="{}"/>
- Show avatar in popover for filters.
- Removed hide sidebar feature.
- Increase readability using a better color selection.
- Better UI in mobile.
And, Adapted test cases
Task 1919926
Co-authored-by: jat-odoo <jat@openerp.com>
This commit is contained in:
committed by
Jigar Patel
co-authored by
jat-odoo
parent
aca0e700ac
commit
369c20a99b
@@ -27,13 +27,13 @@ var CalendarController = AbstractController.extend({
|
||||
custom_events: _.extend({}, AbstractController.prototype.custom_events, {
|
||||
changeDate: '_onChangeDate',
|
||||
changeFilter: '_onChangeFilter',
|
||||
deleteRecord: '_onDeleteRecord',
|
||||
dropRecord: '_onDropRecord',
|
||||
next: '_onNext',
|
||||
openCreate: '_onOpenCreate',
|
||||
openEvent: '_onOpenEvent',
|
||||
prev: '_onPrev',
|
||||
quickCreate: '_onQuickCreate',
|
||||
toggleFullWidth: '_onToggleFullWidth',
|
||||
updateRecord: '_onUpdateRecord',
|
||||
viewUpdated: '_onViewUpdated',
|
||||
}),
|
||||
@@ -200,6 +200,20 @@ var CalendarController = AbstractController.extend({
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
_onDeleteRecord: function (event) {
|
||||
var self = this;
|
||||
Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), {
|
||||
confirm_callback: function () {
|
||||
self.model.deleteRecords([event.data.id], self.modelName).then(function () {
|
||||
self.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
@@ -323,53 +337,22 @@ var CalendarController = AbstractController.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
var open_dialog = function (readonly) {
|
||||
var options = {
|
||||
res_model: self.modelName,
|
||||
res_id: id || null,
|
||||
context: event.context || self.context,
|
||||
readonly: readonly,
|
||||
title: _t("Open: ") + event.data.title,
|
||||
on_saved: function () {
|
||||
if (event.data.on_save) {
|
||||
event.data.on_save();
|
||||
}
|
||||
self.reload();
|
||||
},
|
||||
};
|
||||
if (readonly) {
|
||||
if (self.readonlyFormViewId) {
|
||||
options.view_id = parseInt(self.readonlyFormViewId);
|
||||
var options = {
|
||||
res_model: self.modelName,
|
||||
res_id: id || null,
|
||||
context: event.context || self.context,
|
||||
title: _t("Open: ") + event.data.title,
|
||||
on_saved: function () {
|
||||
if (event.data.on_save) {
|
||||
event.data.on_save();
|
||||
}
|
||||
options.buttons = [
|
||||
{
|
||||
text: _t("Edit"),
|
||||
classes: 'btn-primary',
|
||||
close: true,
|
||||
click: function () { open_dialog(false); }
|
||||
},
|
||||
{
|
||||
text: _t("Delete"),
|
||||
click: function () {
|
||||
Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), {
|
||||
confirm_callback: function () {
|
||||
self.model.deleteRecords([id], self.modelName)
|
||||
.then(function () {
|
||||
self.dialog.destroy();
|
||||
self.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
{text: _t("Close"), close: true}
|
||||
];
|
||||
} else if (self.formViewId) {
|
||||
options.view_id = parseInt(self.formViewId);
|
||||
}
|
||||
self.dialog = new dialogs.FormViewDialog(self, options).open();
|
||||
self.reload();
|
||||
},
|
||||
};
|
||||
open_dialog(true);
|
||||
if (this.formViewId) {
|
||||
options.view_id = parseInt(this.formViewId);
|
||||
}
|
||||
new dialogs.FormViewDialog(this, options).open();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -410,15 +393,6 @@ var CalendarController = AbstractController.extend({
|
||||
self.quickCreating = false;
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Called when we want to open or close the sidebar.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onToggleFullWidth: function () {
|
||||
this.model.toggleFullWidth();
|
||||
this.reload();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
|
||||
@@ -308,13 +308,6 @@ return AbstractModel.extend({
|
||||
today: function () {
|
||||
this.setDate(moment(new Date()));
|
||||
},
|
||||
/**
|
||||
* Toggle the sidebar (containing the mini calendar)
|
||||
*/
|
||||
toggleFullWidth: function () {
|
||||
var fullWidth = this.call('local_storage', 'getItem', 'calendar_fullWidth') !== true;
|
||||
this.call('local_storage', 'setItem', 'calendar_fullWidth', fullWidth);
|
||||
},
|
||||
/**
|
||||
* @param {Object} record
|
||||
* @param {integer} record.id
|
||||
@@ -410,8 +403,10 @@ return AbstractModel.extend({
|
||||
snapMinutes: 15,
|
||||
longPressDelay: 500,
|
||||
eventResizableFromStart: true,
|
||||
nowIndicator: true,
|
||||
weekNumbers: true,
|
||||
weekNumberTitle: _t("W"),
|
||||
weekNumbersWithinDays: true,
|
||||
weekNumberTitle: _t("Week") + " ",
|
||||
allDayText: _t("All day"),
|
||||
monthNames: moment.months(),
|
||||
monthNamesShort: moment.monthsShort(),
|
||||
@@ -444,7 +439,6 @@ return AbstractModel.extend({
|
||||
*/
|
||||
_loadCalendar: function () {
|
||||
var self = this;
|
||||
this.data.fullWidth = this.call('local_storage', 'getItem', 'calendar_fullWidth') === true;
|
||||
this.data.fc_options = this._getFullCalendarOptions();
|
||||
|
||||
var defs = _.map(this.data.filters, this._loadFilter.bind(this));
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
odoo.define('web.CalendarPopover', function (require) {
|
||||
"use strict";
|
||||
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var CalendarPopover = Widget.extend(StandaloneFieldManagerMixin, {
|
||||
template: 'CalendarView.event.popover',
|
||||
events: {
|
||||
'click .o_cw_popover_edit': '_onClickPopoverEdit',
|
||||
'click .o_cw_popover_delete': '_onClickPopoverDelete',
|
||||
},
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Widget} parent
|
||||
* @param {Object} eventInfo
|
||||
*/
|
||||
init: function (parent, eventInfo) {
|
||||
this._super.apply(this, arguments);
|
||||
StandaloneFieldManagerMixin.init.call(this);
|
||||
this.hideDate = eventInfo.hideDate;
|
||||
this.hideTime = eventInfo.hideTime;
|
||||
this.eventTime = eventInfo.eventTime;
|
||||
this.eventDate = eventInfo.eventDate;
|
||||
this.displayFields = eventInfo.displayFields;
|
||||
this.fields = eventInfo.fields;
|
||||
this.event = eventInfo.event;
|
||||
this.modelName = eventInfo.modelName;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
willStart: function () {
|
||||
return Promise.all([this._super.apply(this, arguments), this._processFields()]);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var self = this;
|
||||
_.each(this.$fieldsList, function ($field) {
|
||||
$field.appendTo(self.$('.o_cw_popover_fields_secondary'));
|
||||
});
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Generate fields to render into popover
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_processFields: function () {
|
||||
var self = this;
|
||||
var fieldsToGenerate = [];
|
||||
_.each(this.displayFields, function (displayFieldInfo, fieldName) {
|
||||
var fieldInfo = self.fields[fieldName];
|
||||
var field = {
|
||||
name: fieldName,
|
||||
string: displayFieldInfo.attrs.string || fieldInfo.string,
|
||||
value: self.event.record[fieldName],
|
||||
type: fieldInfo.type,
|
||||
};
|
||||
if (field.type === 'selection') {
|
||||
field.selection = fieldInfo.selection;
|
||||
}
|
||||
if (fieldInfo.relation) {
|
||||
field.relation = fieldInfo.relation;
|
||||
}
|
||||
if (displayFieldInfo.attrs.widget) {
|
||||
field.widget = displayFieldInfo.attrs.widget;
|
||||
} else if (_.contains(['many2many', 'one2many'], field.type)) {
|
||||
field.widget = 'many2many_tags';
|
||||
}
|
||||
if (_.contains(['many2many', 'one2many'], field.type)) {
|
||||
field.fields = [{
|
||||
name: 'id',
|
||||
type: 'integer',
|
||||
}, {
|
||||
name: 'display_name',
|
||||
type: 'char',
|
||||
}];
|
||||
}
|
||||
fieldsToGenerate.push(field);
|
||||
});
|
||||
|
||||
this.$fieldsList = [];
|
||||
return this.model.makeRecord(this.modelName, fieldsToGenerate).then(function (recordID) {
|
||||
var defs = [];
|
||||
|
||||
var record = self.model.get(recordID);
|
||||
_.each(fieldsToGenerate, function (field) {
|
||||
var FieldClass = fieldRegistry.getAny([field.widget, field.type]);
|
||||
var fieldWidget = new FieldClass(self, field.name, record, self.displayFields[field.name]);
|
||||
self._registerWidget(recordID, field.name, fieldWidget);
|
||||
|
||||
var $field = $('<li>', {class: 'list-group-item flex-shrink-0 d-flex flex-wrap'});
|
||||
var $fieldLabel = $('<strong>', {class: 'mr-2', text: _.str.sprintf('%s : ', field.string)});
|
||||
$fieldLabel.appendTo($field);
|
||||
var $fieldContainer = $('<div>', {class: 'flex-grow-1'});
|
||||
$fieldContainer.appendTo($field);
|
||||
|
||||
defs.push(fieldWidget.appendTo($fieldContainer).then(function () {
|
||||
self.$fieldsList.push($field);
|
||||
}));
|
||||
});
|
||||
return Promise.all(defs);
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {jQueryEvent} ev
|
||||
*/
|
||||
_onClickPopoverEdit: function (ev) {
|
||||
ev.preventDefault();
|
||||
this.trigger_up('edit_event', {id: this.event.id});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {jQueryEvent} ev
|
||||
*/
|
||||
_onClickPopoverDelete: function (ev) {
|
||||
ev.preventDefault();
|
||||
this.trigger_up('delete_event', {id: this.event.id});
|
||||
},
|
||||
});
|
||||
|
||||
return CalendarPopover;
|
||||
|
||||
});
|
||||
@@ -2,15 +2,14 @@ odoo.define('web.CalendarRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
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');
|
||||
var FieldManagerMixin = require('web.FieldManagerMixin');
|
||||
var QWeb = require('web.QWeb');
|
||||
var relational_fields = require('web.relational_fields');
|
||||
var session = require('web.session');
|
||||
var utils = require('web.utils');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var _t = core._t;
|
||||
@@ -78,7 +77,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, {
|
||||
{
|
||||
mode: 'edit',
|
||||
attrs: {
|
||||
placeholder: _.str.sprintf(_t("Add %s"), self.title),
|
||||
placeholder: "+ " + _.str.sprintf(_t("Add %s"), self.title),
|
||||
can_create: false
|
||||
},
|
||||
});
|
||||
@@ -98,7 +97,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, {
|
||||
this.many2one.filter_ids = _.without(_.pluck(this.filters, 'value'), 'all');
|
||||
}
|
||||
this.$el.on('click', '.o_remove', this._onFilterRemove.bind(this));
|
||||
this.$el.on('click', '.custom-checkbox input', this._onFilterActive.bind(this));
|
||||
this.$el.on('click', '.o_calendar_filter_items input', this._onFilterActive.bind(this));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -169,8 +168,12 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, {
|
||||
|
||||
return AbstractRenderer.extend({
|
||||
template: "CalendarView",
|
||||
events: _.extend({}, AbstractRenderer.prototype.events, {
|
||||
'click .o_calendar_sidebar_toggler': '_onToggleSidebar',
|
||||
config: {
|
||||
CalendarPopover: CalendarPopover,
|
||||
},
|
||||
custom_events: _.extend({}, AbstractRenderer.prototype.custom_events || {}, {
|
||||
edit_event: '_onEditEvent',
|
||||
delete_event: '_onDeleteEvent',
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -185,11 +188,8 @@ return AbstractRenderer.extend({
|
||||
this.model = params.model;
|
||||
this.filters = [];
|
||||
this.color_map = {};
|
||||
|
||||
if (params.eventTemplate) {
|
||||
this.qweb = new QWeb(config.isDebug(), {_s: session.origin});
|
||||
this.qweb.add_template(utils.json_node_to_xml(params.eventTemplate));
|
||||
}
|
||||
this.hideDate = params.hideDate;
|
||||
this.hideTime = params.hideTime;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -203,6 +203,20 @@ return AbstractRenderer.extend({
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_attach_callback: function () {
|
||||
if (config.device.isMobile) {
|
||||
this.$el.height($(window).height() - this.$el.offset().top);
|
||||
}
|
||||
var scrollTop = this.$calendar.find('.fc-scroller').scrollTop();
|
||||
if (scrollTop) {
|
||||
this.$calendar.fullCalendar('reinitView');
|
||||
} else {
|
||||
this.$calendar.fullCalendar('render');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -325,19 +339,14 @@ return AbstractRenderer.extend({
|
||||
_eventRender: function (event) {
|
||||
var qweb_context = {
|
||||
event: event,
|
||||
fields: this.state.fields,
|
||||
format: this._format.bind(this),
|
||||
isMobile: config.device.isMobile,
|
||||
read_only_mode: this.read_only_mode,
|
||||
record: event.record,
|
||||
user_context: session.user_context,
|
||||
widget: this,
|
||||
color: this.getColor(event.color_index),
|
||||
};
|
||||
this.qweb_context = qweb_context;
|
||||
if (_.isEmpty(qweb_context.record)) {
|
||||
return '';
|
||||
} else {
|
||||
return (this.qweb || qweb).render("calendar-box", qweb_context);
|
||||
return qweb.render("calendar-box", qweb_context);
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -375,40 +384,54 @@ return AbstractRenderer.extend({
|
||||
self.trigger_up('dropRecord', event);
|
||||
},
|
||||
eventResize: function (event) {
|
||||
self._unselectEvent();
|
||||
self.trigger_up('updateRecord', event);
|
||||
},
|
||||
eventClick: function (event) {
|
||||
self.trigger_up('openEvent', event);
|
||||
self.$calendar.fullCalendar('unselect');
|
||||
eventClick: function (eventData, ev) {
|
||||
self._unselectEvent();
|
||||
self.$calendar.find(_.str.sprintf('[data-event-id=%s]', eventData.id)).addClass('o_cw_custom_highlight');
|
||||
self._renderEventPopover(eventData, $(ev.currentTarget));
|
||||
},
|
||||
select: function (target_date, end_date, event, _js_event, _view) {
|
||||
var data = {'start': target_date, 'end': end_date};
|
||||
if (self.state.context.default_name) {
|
||||
data.title = self.state.context.default_name;
|
||||
select: function (startDate, endDate) {
|
||||
// Clicking on the view, dispose any visible popover. Otherwise create a new event.
|
||||
if (self.$('.o_cw_popover').length) {
|
||||
self._unselectEvent();
|
||||
} else {
|
||||
var data = {start: startDate, end: endDate};
|
||||
if (self.state.context.default_name) {
|
||||
data.title = self.state.context.default_name;
|
||||
}
|
||||
self.trigger_up('openCreate', data);
|
||||
}
|
||||
self.trigger_up('openCreate', data);
|
||||
self.$calendar.fullCalendar('unselect');
|
||||
},
|
||||
eventRender: function (event, element) {
|
||||
eventRender: function (event, element, view) {
|
||||
var $render = $(self._eventRender(event));
|
||||
event.title = $render.find('.o_field_type_char:first').text();
|
||||
element.find('.fc-content').html($render.html());
|
||||
element.addClass($render.attr('class'));
|
||||
var display_hour = '';
|
||||
if (!event.allDay) {
|
||||
element.attr('data-event-id', event.id);
|
||||
|
||||
// Add background if doesn't exist
|
||||
if (!element.find('.fc-bg').length) {
|
||||
element.find('.fc-content').after($('<div/>', {class: 'fc-bg'}));
|
||||
}
|
||||
|
||||
// For month view: Show background for all-day/multidate events only
|
||||
if (view.name === 'month' && event.record) {
|
||||
var start = event.r_start || event.start;
|
||||
var end = event.r_end || event.end;
|
||||
var timeFormat = _t.database.parameters.time_format.search("%H") != -1 ? 'HH:mm': 'h:mma';
|
||||
display_hour = start.format(timeFormat) + ' - ' + end.format(timeFormat);
|
||||
if (display_hour === '00:00 - 00:00') {
|
||||
display_hour = _t('All day');
|
||||
// Detect if the event occurs in just one day
|
||||
// note: add & remove 1 min to avoid issues with 00:00
|
||||
var isSameDayEvent = start.clone().add(1, 'minute').isSame(end.clone().subtract(1, 'minute'), 'day');
|
||||
if (!event.record.allday && isSameDayEvent) {
|
||||
element.addClass('o_cw_nobg');
|
||||
}
|
||||
}
|
||||
element.find('.fc-content .fc-time').text(display_hour);
|
||||
},
|
||||
// Dirty hack to ensure a correct first render
|
||||
eventAfterAllRender: function () {
|
||||
$(window).trigger('resize');
|
||||
|
||||
// On double click, edit the event
|
||||
element.on('dblclick', function () {
|
||||
self.trigger_up('edit_event', {id: event.id});
|
||||
});
|
||||
},
|
||||
viewRender: function (view) {
|
||||
// compute mode from view.name which is either 'month', 'agendaWeek' or 'agendaDay'
|
||||
@@ -418,6 +441,39 @@ return AbstractRenderer.extend({
|
||||
title: view.title,
|
||||
});
|
||||
},
|
||||
// Add/Remove a class on hover to style multiple days events.
|
||||
// The css ":hover" selector can't be used because these events
|
||||
// are rendered using multiple elements.
|
||||
eventMouseover: function (eventData) {
|
||||
self.$calendar.find(_.str.sprintf('[data-event-id=%s]', eventData.id)).addClass('o_cw_custom_hover');
|
||||
},
|
||||
eventMouseout: function (eventData) {
|
||||
self.$calendar.find(_.str.sprintf('[data-event-id=%s]', eventData.id)).removeClass('o_cw_custom_hover');
|
||||
},
|
||||
eventDragStart: function () {
|
||||
self._unselectEvent();
|
||||
},
|
||||
eventResizeStart: function () {
|
||||
self._unselectEvent();
|
||||
},
|
||||
eventLimitClick: function () {
|
||||
self._unselectEvent();
|
||||
return 'popover';
|
||||
},
|
||||
windowResize: function () {
|
||||
self._render();
|
||||
},
|
||||
views: {
|
||||
day: {
|
||||
columnFormat: 'LL'
|
||||
},
|
||||
week: {
|
||||
columnFormat: 'ddd D'
|
||||
},
|
||||
month: {
|
||||
columnFormat: config.device.isMobile ? 'ddd' : 'dddd'
|
||||
}
|
||||
},
|
||||
height: 'parent',
|
||||
unselectAuto: false,
|
||||
isRTL: _t.database.parameters.direction === "rtl",
|
||||
@@ -440,6 +496,7 @@ return AbstractRenderer.extend({
|
||||
date: moment(new Date(+obj.currentYear , +obj.currentMonth, +obj.currentDay))
|
||||
});
|
||||
},
|
||||
'showOtherMonths': true,
|
||||
'dayNamesMin' : this.state.fc_options.dayNamesShort,
|
||||
'monthNames': this.state.fc_options.monthNamesShort,
|
||||
'firstDay': this.state.fc_options.firstDay,
|
||||
@@ -466,7 +523,6 @@ return AbstractRenderer.extend({
|
||||
var $calendar = this.$calendar;
|
||||
var $fc_view = $calendar.find('.fc-view');
|
||||
var scrollPosition = $fc_view.scrollLeft();
|
||||
var scrollTop = this.$calendar.find('.fc-scroller').scrollTop();
|
||||
|
||||
$fc_view.scrollLeft(0);
|
||||
$calendar.fullCalendar('unselect');
|
||||
@@ -485,8 +541,8 @@ return AbstractRenderer.extend({
|
||||
.removeClass('o_color o_selected_range');
|
||||
var $a;
|
||||
switch (this.state.scale) {
|
||||
case 'month': $a = this.$small_calendar.find('td a'); break;
|
||||
case 'week': $a = this.$small_calendar.find('tr:has(.ui-state-active) a'); 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;
|
||||
}
|
||||
$a.addClass('o_selected_range');
|
||||
@@ -496,21 +552,8 @@ return AbstractRenderer.extend({
|
||||
|
||||
$fc_view.scrollLeft(scrollPosition);
|
||||
|
||||
var fullWidth = this.state.fullWidth;
|
||||
this.$('.o_calendar_sidebar_toggler')
|
||||
.toggleClass('fa-close', !fullWidth)
|
||||
.toggleClass('fa-chevron-left', fullWidth)
|
||||
.attr('title', !fullWidth ? _('Close Sidebar') : _('Open Sidebar'));
|
||||
this.$sidebar_container.toggleClass('o_sidebar_hidden', fullWidth);
|
||||
this.$sidebar.toggleClass('o_hidden', fullWidth);
|
||||
|
||||
this._unselectEvent();
|
||||
var filterProm = this._renderFilters();
|
||||
this.$calendar.appendTo('body');
|
||||
if (scrollTop) {
|
||||
this.$calendar.fullCalendar('reinitView');
|
||||
} else {
|
||||
this.$calendar.fullCalendar('render');
|
||||
}
|
||||
this._renderEvents();
|
||||
this.$calendar.prependTo(this.$('.o_calendar_view'));
|
||||
|
||||
@@ -532,6 +575,8 @@ return AbstractRenderer.extend({
|
||||
* @returns {Promise} resolved when all filters have been rendered
|
||||
*/
|
||||
_renderFilters: function () {
|
||||
// Dispose of filter popover
|
||||
this.$('.o_calendar_filter_item').popover('dispose');
|
||||
_.each(this.filters || (this.filters = []), function (filter) {
|
||||
filter.destroy();
|
||||
});
|
||||
@@ -565,24 +610,152 @@ return AbstractRenderer.extend({
|
||||
options.fields = this.state.fields;
|
||||
var filter = new SidebarFilter(self, options);
|
||||
prom = filter.appendTo(this.$sidebar).then(function () {
|
||||
// Show filter popover
|
||||
if (options.avatar_field) {
|
||||
_.each(options.filters, function (filter) {
|
||||
if (filter.value !== 'all') {
|
||||
var selector = _.str.sprintf('.o_calendar_filter_item[data-value=%s]', filter.value);
|
||||
self.$sidebar.find(selector).popover({
|
||||
animation: false,
|
||||
trigger: 'hover',
|
||||
html: true,
|
||||
placement: 'top',
|
||||
title: filter.label,
|
||||
delay: {show: 300, hide: 0},
|
||||
content: function () {
|
||||
return $('<img>', {
|
||||
src: _.str.sprintf('/web/image/%s/%s/%s', options.avatar_model, filter.value, options.avatar_field),
|
||||
class: 'mx-auto',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return self._renderFiltersOneByOne(filterIndex + 1);
|
||||
});
|
||||
this.filters.push(filter);
|
||||
}
|
||||
return Promise.resolve(prom);
|
||||
},
|
||||
/**
|
||||
* Prepare context to display in the popover.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} eventData
|
||||
* @returns {Object} context
|
||||
*/
|
||||
_getPopoverContext: function (eventData) {
|
||||
var context = {
|
||||
hideDate: this.hideDate,
|
||||
hideTime: this.hideTime,
|
||||
eventTime: {},
|
||||
eventDate: {},
|
||||
fields: this.state.fields,
|
||||
displayFields: this.displayFields,
|
||||
event: eventData,
|
||||
modelName: this.model,
|
||||
};
|
||||
|
||||
var start = moment(eventData.r_start || eventData.start);
|
||||
var end = moment(eventData.r_end || eventData.end);
|
||||
var isSameDayEvent = start.clone().add(1, 'minute').isSame(end.clone().subtract(1, 'minute'), 'day');
|
||||
|
||||
// Do not display timing if the event occur across multiple days. Otherwise use user's timing preferences
|
||||
if (!this.hideTime && !eventData.record.allday && isSameDayEvent) {
|
||||
// Fetch user's preferences
|
||||
var dbTimeFormat = _t.database.parameters.time_format.search('%H') != -1 ? 'HH:mm': 'hh:mm a';
|
||||
|
||||
context.eventTime.time = start.clone().format(dbTimeFormat) + ' - ' + end.clone().format(dbTimeFormat);
|
||||
|
||||
// Calculate duration and format text
|
||||
var durationHours = moment.duration(end.diff(start)).hours();
|
||||
var durationHoursKey = (durationHours === 1) ? 'h' : 'hh';
|
||||
var durationMinutes = moment.duration(end.diff(start)).minutes();
|
||||
var durationMinutesKey = (durationMinutes === 1) ? 'm' : 'mm';
|
||||
|
||||
var localeData = moment.localeData(); // i18n for 'hours' and "minutes" strings
|
||||
context.eventTime.duration = (durationHours > 0 ? localeData.relativeTime(durationHours, true, durationHoursKey) : '')
|
||||
+ (durationHours > 0 && durationMinutes > 0 ? ', ' : '')
|
||||
+ (durationMinutes > 0 ? localeData.relativeTime(durationMinutes, true, durationMinutesKey) : '');
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
if (eventData.record.allday && isSameDayEvent) {
|
||||
context.eventDate.duration = _t("All day");
|
||||
} else if (eventData.record.allday && !isSameDayEvent) {
|
||||
var daysLocaleData = moment.localeData();
|
||||
var days = moment.duration(end.diff(start)).days();
|
||||
context.eventDate.duration = daysLocaleData.relativeTime(days, true, 'dd');
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
},
|
||||
/**
|
||||
* Render event popover
|
||||
*
|
||||
* @private
|
||||
* @param {Object} eventData
|
||||
* @param {jQueryElement} $eventElement
|
||||
*/
|
||||
_renderEventPopover: function (eventData, $eventElement) {
|
||||
var self = this;
|
||||
$eventElement.popover({
|
||||
animation: false,
|
||||
delay: {
|
||||
show: 50,
|
||||
hide: 100
|
||||
},
|
||||
trigger: 'manual',
|
||||
html: true,
|
||||
title: eventData.record.display_name,
|
||||
template: qweb.render('CalendarView.event.popover.placeholder', {color: this.getColor(eventData.color_index)}),
|
||||
container: eventData.allDay ? '.fc-view' : '.fc-scroller',
|
||||
}).on('shown.bs.popover', function () {
|
||||
var $popover = $($(this).data('bs.popover').tip);
|
||||
$popover.find('.o_cw_popover_close').on('click', self._unselectEvent.bind(self));
|
||||
// Initialize popover widget
|
||||
var calendarPopover = new self.config.CalendarPopover(self, self._getPopoverContext(eventData));
|
||||
calendarPopover.replace($popover.find('.o_cw_body'));
|
||||
}).popover('show');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Toggle the sidebar
|
||||
* Remove highlight classes and dispose of popovers
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onToggleSidebar: function () {
|
||||
this.trigger_up('toggleFullWidth');
|
||||
_unselectEvent: function () {
|
||||
this.$('.fc-event').removeClass('o_cw_custom_highlight');
|
||||
this.$('.o_cw_popover').popover('dispose');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
_onEditEvent: function (event) {
|
||||
this._unselectEvent();
|
||||
this.trigger_up('openEvent', {_id: event.data.id});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
_onDeleteEvent: function (event) {
|
||||
this._unselectEvent();
|
||||
this.trigger_up('deleteRecord', {id: event.data.id});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ var CalendarModel = require('web.CalendarModel');
|
||||
var CalendarController = require('web.CalendarController');
|
||||
var CalendarRenderer = require('web.CalendarRenderer');
|
||||
var core = require('web.core');
|
||||
var pyUtils = require('web.py_utils');
|
||||
var utils = require('web.utils');
|
||||
|
||||
var _lt = core._lt;
|
||||
@@ -66,7 +67,8 @@ var CalendarView = AbstractView.extend({
|
||||
var fieldName = child.attrs.name;
|
||||
fieldNames.push(fieldName);
|
||||
if (!child.attrs.invisible) {
|
||||
displayFields[fieldName] = child.attrs;
|
||||
child.attrs.options = child.attrs.options ? pyUtils.py_eval(child.attrs.options) : {};
|
||||
displayFields[fieldName] = {attrs: child.attrs};
|
||||
|
||||
if (params.sidebar === false) return; // if we have not sidebar, (eg: Dashboard), we don't use the filter "coworkers"
|
||||
|
||||
@@ -102,17 +104,13 @@ var CalendarView = AbstractView.extend({
|
||||
'filters': [],
|
||||
};
|
||||
if (fields[fieldName].relation) {
|
||||
if (['res.users', 'res.partner'].indexOf(fields[fieldName].relation) !== -1) {
|
||||
if (['res.users', 'res.partner', 'hr.employee'].indexOf(fields[fieldName].relation) !== -1) {
|
||||
filters[fieldName].avatar_field = 'image_64';
|
||||
}
|
||||
filters[fieldName].avatar_model = fields[fieldName].relation;
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isEmpty(displayFields)) {
|
||||
displayFields = fields.display_name ? {'display_name': {}} : [];
|
||||
}
|
||||
|
||||
//if quick_add = False, we don't allow quick_add
|
||||
//if quick_add = not specified in view, we use the default widgets.QuickCreate
|
||||
//if quick_add = is NOT False and IS specified in view, we this one for widgets.QuickCreate'
|
||||
@@ -132,15 +130,15 @@ var CalendarView = AbstractView.extend({
|
||||
}
|
||||
}
|
||||
|
||||
this.controllerParams.readonlyFormViewId = !attrs.readonly_form_view_id || !utils.toBoolElse(attrs.readonly_form_view_id, true) ? false : attrs.readonly_form_view_id;
|
||||
this.controllerParams.eventOpenPopup = utils.toBoolElse(attrs.event_open_popup || '', false);
|
||||
this.controllerParams.mapping = mapping;
|
||||
this.controllerParams.context = params.context || {};
|
||||
this.controllerParams.displayName = params.action && params.action.name;
|
||||
|
||||
this.rendererParams.displayFields = displayFields;
|
||||
this.rendererParams.eventTemplate = _.findWhere(arch.children, {'tag': 'templates'});
|
||||
this.rendererParams.model = viewInfo.model;
|
||||
this.rendererParams.hideDate = utils.toBoolElse(attrs.hide_date || '', false);
|
||||
this.rendererParams.hideTime = utils.toBoolElse(attrs.hide_time || '', false);
|
||||
|
||||
this.loadParams.fieldNames = _.uniq(fieldNames);
|
||||
this.loadParams.mapping = mapping;
|
||||
|
||||
@@ -1,187 +1,658 @@
|
||||
// Variables
|
||||
$o-cw-color-today: nth($o-colors, 3);
|
||||
$o-cw-color-today-accent: #FC3D39;
|
||||
$o-cw-popup-avatar-size: 16px;
|
||||
$o-cw-filter-avatar-size: 20px;
|
||||
|
||||
// Animations
|
||||
@keyframes backgroundfade {
|
||||
from {background-color: rgba($o-brand-odoo, 0.5);}
|
||||
to {background-color: #f5f5f5;}
|
||||
from { background-color: rgba($info, 0.5); }
|
||||
to { background-color: rgba($info, 0.1); }
|
||||
}
|
||||
|
||||
.o_calendar_container {
|
||||
height: 100%;
|
||||
@include o-webclient-padding($top: $o-horizontal-padding/2, $bottom: $o-horizontal-padding/2);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@for $index from 1 through $o-nb-calendar-colors {
|
||||
.o_calendar_color_#{$index} {
|
||||
color: #0D0D0D;
|
||||
background-color: adjust-hue(rgb(255, 192, 192), (360/($o-nb-calendar-colors+1) * $index) * 1deg);
|
||||
border-color: adjust-hue(rgb(255, 192, 192), (360/($o-nb-calendar-colors+1) * $index) * 1deg);
|
||||
opacity: 0.7;
|
||||
&.o_event_hightlight {
|
||||
font-weight: bold;
|
||||
opacity: 0.9;
|
||||
}
|
||||
&:hover {
|
||||
color: #666 !important;
|
||||
}
|
||||
.o_calendar_view {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
background-color: gray('100');
|
||||
background: linear-gradient(-45deg, gray('100'), white);
|
||||
|
||||
.fc-event {
|
||||
margin: 0 1px;
|
||||
border-style: solid;
|
||||
border-width: 0 0 0 3px;
|
||||
border-radius: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
|
||||
&:not([href]):not([tabindex]) {
|
||||
color: $body-color;
|
||||
}
|
||||
.o_underline_color_#{$index} {
|
||||
border-bottom: 4px solid adjust-hue(rgba(255, 192, 192, 0.7), (360/($o-nb-calendar-colors+1) * $index) * 1deg);
|
||||
|
||||
.fc-bg {
|
||||
background-color: mix(theme-color('primary'), white); // Used for placeholder events only (on creation)
|
||||
@include size(101%); // Compensate border
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.o_calendar_view {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
.fc-content {
|
||||
white-space: normal;
|
||||
margin: 8px 4px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.o_calendar_widget {
|
||||
height: 100%;
|
||||
background-color: $o-view-background-color;
|
||||
* {
|
||||
z-index: 0; // FIXME
|
||||
}
|
||||
.fc-week-number, .fc-widget-header {
|
||||
background-color: lighten($o-brand-secondary, 30%);
|
||||
}
|
||||
.o_avatar_square {
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
.o_fields img, .o_avatar_square {
|
||||
vertical-align: middle;
|
||||
margin-left: 1px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.o_field_type_char:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
.fc-view {
|
||||
overflow-x: auto;
|
||||
&.o_cw_custom_highlight {
|
||||
z-index: 10!important;
|
||||
|
||||
.fc-bg{
|
||||
opacity: 0.95;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_calendar_sidebar_container {
|
||||
flex: 0 0 $o-horizontal-padding;
|
||||
position: relative;
|
||||
.o_calendar_widget {
|
||||
height: 100%;
|
||||
|
||||
&.o_sidebar_hidden {
|
||||
margin-right: -$o-horizontal-padding; // hack to display the toggler in the gutter
|
||||
.o_calendar_sidebar_toggler {
|
||||
padding: 4px;
|
||||
color: darken($o-webclient-background-color, 30%);
|
||||
// === Adapt calendar table borders ===
|
||||
// =====================================
|
||||
|
||||
td {
|
||||
border-color: gray('200');
|
||||
}
|
||||
|
||||
.fc-time-grid .fc-slats .fc-minor td {
|
||||
border-top-color: gray('300');
|
||||
}
|
||||
|
||||
.fc-widget-content {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.fc-widget-header {
|
||||
border-color: transparent;
|
||||
border-bottom-color: $gray-200;
|
||||
padding: 3px 0 5px;
|
||||
}
|
||||
|
||||
hr.fc-widget-header {
|
||||
padding: 1px;
|
||||
border: 0;
|
||||
background: gray('400');
|
||||
}
|
||||
|
||||
.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton {
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.fc-event-container {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
// ====== Specific agenda types ======
|
||||
// ====================================
|
||||
|
||||
// ====== Both Day and Week agenda
|
||||
.fc-agendaDay-view, .fc-agendaWeek-view {
|
||||
.fc-axis {
|
||||
padding-left: $o-horizontal-padding;
|
||||
}
|
||||
|
||||
// Hide unnecessary borders
|
||||
table td, div.fc-row.fc-week.fc-widget-content {
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
// Reinfornce default border color
|
||||
tbody td {
|
||||
border-top-color: gray('400');
|
||||
}
|
||||
|
||||
// Remove dotted borders (half-hours)
|
||||
.fc-time-grid .fc-slats .fc-minor td {
|
||||
border-top-style: none;
|
||||
}
|
||||
|
||||
// Align labels and timelines
|
||||
.fc-axis.fc-time {
|
||||
border-top-color: transparent;
|
||||
|
||||
span {
|
||||
max-width: 45px;
|
||||
margin-top: -19px;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a small gap on top to show the first time label (0:00)
|
||||
.fc-scroller .fc-time-grid > .fc-slats,
|
||||
.fc-scroller .fc-time-grid > .fc-bg {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
// Row containing "all day" events
|
||||
div.fc-day-grid {
|
||||
background-color: $o-view-background-color;
|
||||
box-shadow: 0 6px 12px -6px rgba(black, 0.16);
|
||||
|
||||
+ hr.fc-widget-header {
|
||||
padding: 1px 0 0;
|
||||
background: gray('300');
|
||||
}
|
||||
|
||||
.fc-content-skeleton tr:not(:first-child) .fc-h-event{
|
||||
margin-top: 3px
|
||||
}
|
||||
}
|
||||
|
||||
// Create a 'preudo-border' for the first row. The actual border
|
||||
// it's hidden because of border-collapse settings.
|
||||
.fc-slats tr:first-child td.fc-widget-content:last-child {
|
||||
box-shadow: inset 0 1px 0 gray('400');
|
||||
}
|
||||
|
||||
.fc-day.fc-widget-content.fc-today.fc-state-highlight {
|
||||
border-left-color: rgba($o-cw-color-today, 0.3);
|
||||
border-right-color: rgba($o-cw-color-today, 0.3);
|
||||
background: rgba($o-cw-color-today, 0.05 );
|
||||
}
|
||||
|
||||
.fc-event {
|
||||
// Prevent events with similar color to visually overlap each other
|
||||
box-shadow: 0 0 0 1px white;
|
||||
|
||||
&.fc-v-event {
|
||||
border-width: 3px 0 0;
|
||||
|
||||
&.fc-not-start {
|
||||
border-width: 0 0 3px;
|
||||
|
||||
&.fc-not-end {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset position to keep the "nowIndicator" line visible
|
||||
.fc-content-col {
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.o_calendar_sidebar_toggler {
|
||||
@include o-position-absolute($top: 0, $right: 0);
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
z-index: 1;
|
||||
&:hover {
|
||||
color: black;
|
||||
// ====== Day only
|
||||
.fc-agendaDay-view .fc-event {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// ====== Week only
|
||||
.fc-agendaWeek-view {
|
||||
.fc-now-indicator {
|
||||
left: $o-horizontal-padding;
|
||||
}
|
||||
|
||||
// Expand tiny events on hover/select
|
||||
.fc-v-event.o_cw_custom_highlight, .fc-v-event.o_cw_custom_hover {
|
||||
transition: margin .1s .3s, left .1s .3s, right .1s .3s;
|
||||
margin: 0!important;
|
||||
right: 1px!important;
|
||||
left: 1px!important;
|
||||
}
|
||||
}
|
||||
|
||||
.o_calendar_sidebar {
|
||||
// ====== Month only
|
||||
.fc-month-view {
|
||||
padding-left: $o-horizontal-padding;
|
||||
|
||||
.fc-widget-header {
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.fc-week-number {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.fc-day-number {
|
||||
margin: 5px;
|
||||
padding: 0.1rem 0.3rem 0.1rem 0;
|
||||
font-size: 1.2rem;
|
||||
color: gray('900');
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.fc-day-top.fc-other-month {
|
||||
opacity: 0.8;
|
||||
|
||||
.fc-day-number {
|
||||
color: gray('500');
|
||||
}
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
.fc-bg .fc-today {
|
||||
background: $o-view-background-color;
|
||||
border-color: gray('300');
|
||||
}
|
||||
|
||||
.fc-content-skeleton .fc-today .fc-day-number {
|
||||
margin-top: 3px;
|
||||
padding: 0.4em 0.4em 0.35em;
|
||||
border-radius: 100%;
|
||||
min-width: 1.1em;
|
||||
background: $o-cw-color-today-accent;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.fc-more-cell {
|
||||
> div, .fc-more {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-event {
|
||||
margin: 0 3px 2px;
|
||||
.o_event_title {
|
||||
@include text-truncate();
|
||||
}
|
||||
|
||||
.fc-content {
|
||||
margin: 4px 4px 3px;
|
||||
}
|
||||
|
||||
&.o_cw_nobg {
|
||||
.fc-bg {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&.o_cw_custom_hover, &.o_cw_custom_highlight, &:hover {
|
||||
.fc-bg {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-not-start {
|
||||
border-right-width: 3px;
|
||||
|
||||
.fc-content {
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-not-end {
|
||||
margin-right: 0;
|
||||
|
||||
.fc-content {
|
||||
padding-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====== RTL layout(s)
|
||||
&.fc-rtl {
|
||||
.fc-agendaWeek-view .fc-event {
|
||||
border-width: 0 3px 0 0;
|
||||
}
|
||||
|
||||
.fc-month-view .fc-event {
|
||||
border-width: 0 3px 0 0;
|
||||
|
||||
&.fc-not-start {
|
||||
margin: 0 0 1px 5px;
|
||||
border-width: 0 0 0 3px;
|
||||
|
||||
.fc-content {
|
||||
padding-right: 6px;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.fc-not-end {
|
||||
margin: 0 5px 1px 0;
|
||||
|
||||
.fc-content {
|
||||
padding-left: 6px;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_calendar_sidebar_container {
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
@include o-webclient-padding($top: $o-horizontal-padding/2);
|
||||
background-color: $o-view-background-color;
|
||||
border-left: 1px solid darken($o-control-panel-background-color, 20%);
|
||||
overflow-y:auto;
|
||||
|
||||
.o_calendar_sidebar {
|
||||
width: 200px;
|
||||
font-size: 14px;
|
||||
|
||||
@include media-breakpoint-up('xl') {
|
||||
width: 250px;
|
||||
margin-left: 8px;
|
||||
background: $o-view-background-color;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-datepicker {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
|
||||
&, td, .ui-datepicker-header, td a, td span {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.ui-datepicker {
|
||||
width: 100%;
|
||||
font-size: 8pt;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
th {
|
||||
padding: .7em .2em;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0;
|
||||
|
||||
.ui-widget-header {
|
||||
display: flex;
|
||||
a, span {
|
||||
padding: .2em .2em .1em;
|
||||
float: right;
|
||||
width: 22px;
|
||||
background: none;
|
||||
background-color: lighten($o-brand-secondary, 30%);
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
color: gray('700');
|
||||
}
|
||||
|
||||
.ui-datepicker-prev, .ui-datepicker-next {
|
||||
flex: 0 0 auto;
|
||||
position: static;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
color: $o-main-text-color;
|
||||
&.ui-state-hover {
|
||||
background: none;
|
||||
border: none;
|
||||
color: black;
|
||||
}
|
||||
.ui-icon {
|
||||
display: none;
|
||||
}
|
||||
&.ui-datepicker-current-day a {
|
||||
background: $info;
|
||||
color: color-yiq($info);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.ui-datepicker-today a {
|
||||
border-radius: 100%;
|
||||
padding-top: 0.3em;
|
||||
width: 25px;
|
||||
background: mix($o-cw-color-today-accent, white, 80%);
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.ui-datepicker-current-day.ui-datepicker-today a {
|
||||
background: $o-cw-color-today-accent;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-datepicker-header {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.ui-datepicker-header {
|
||||
border-radius: 0;
|
||||
|
||||
.ui-icon {
|
||||
background-image: none;
|
||||
text-indent: 0;
|
||||
color: transparent;
|
||||
|
||||
&:before {
|
||||
font: normal normal normal 13px/1 FontAwesome;
|
||||
content: "\f053";
|
||||
color: gray('400');
|
||||
}
|
||||
.ui-datepicker-prev {
|
||||
order: 1;
|
||||
&:after {
|
||||
font-family: FontAwesome;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
.ui-datepicker-next {
|
||||
order: 3;
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
.ui-datepicker-title {
|
||||
flex: 0 0 auto;
|
||||
order: 2;
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
&.ui-icon-circle-triangle-e:before {
|
||||
content: "\f054"
|
||||
}
|
||||
}
|
||||
table {
|
||||
td {
|
||||
padding: 0 2px 2px 0;
|
||||
background-color: $o-view-background-color;
|
||||
a {
|
||||
border: none;
|
||||
background: none;
|
||||
background-color: lighten($o-brand-secondary, 40%);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.ui-state-default {
|
||||
color: $o-brand-odoo;
|
||||
&:hover {
|
||||
background: none;
|
||||
background-color: rgba($o-brand-odoo, 0.5);
|
||||
border-color: $o-brand-odoo;
|
||||
color: $o-view-background-color;
|
||||
};
|
||||
}
|
||||
.ui-state-active {
|
||||
background: none;
|
||||
background-color: $o-brand-odoo;
|
||||
color: $o-view-background-color;
|
||||
}
|
||||
.o_selected_range.o_color {
|
||||
background: none;
|
||||
background-color: $o-brand-odoo;
|
||||
animation: backgroundfade 3s forwards;
|
||||
|
||||
.ui-state-hover.ui-datepicker-next-hover, .ui-state-hover.ui-datepicker-prev-hover {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
span:before {
|
||||
color: gray('800');
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_calendar_filter {
|
||||
padding: 8px;
|
||||
border-top: 1px solid lightgray;
|
||||
.o_field_many2one {
|
||||
width: 100%;
|
||||
|
||||
.o_selected_range.o_color:not(.ui-datepicker-unselectable) {
|
||||
background-color: $info;
|
||||
animation: backgroundfade 2s forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.o_calendar_filter {
|
||||
font-size: 0.9em;
|
||||
padding: 2em 0 1em;
|
||||
|
||||
.o_cw_filter_collapse_icon {
|
||||
transition: all 0.3s ease;
|
||||
@include o-hover-opacity();
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.collapsed .o_cw_filter_collapse_icon {
|
||||
transform: rotate(90deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.o_calendar_filter_item {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
input {
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.o_cw_filter_input_bg {
|
||||
@include size(1.3em);
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-radius: 1px;
|
||||
overflow: hidden;
|
||||
|
||||
&.o_beside_avatar {
|
||||
@include size($o-cw-filter-avatar-size);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
input:not(:checked) + label .o_cw_filter_input_bg {
|
||||
background: transparent!important;
|
||||
|
||||
i.fa {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.o_cw_filter_avatar {
|
||||
@include size($o-cw-filter-avatar-size);
|
||||
border-radius: 2px;
|
||||
|
||||
&.fa {
|
||||
padding: 4px 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_cw_filter_title {
|
||||
line-height: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
button.o_remove {
|
||||
@include o-position-absolute(0,0,0);
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
button.o_remove {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_many2one {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_cw_popover {
|
||||
min-width: 256px;
|
||||
max-width: 328px;
|
||||
font-size: $font-size-base;
|
||||
|
||||
.card-header, .card-header .popover-header {
|
||||
font-size: 1.05em;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.o_cw_popover_close {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.o_calendar_avatars {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.o_calendar_avatars img {
|
||||
margin-right: 0.4rem;
|
||||
@include size($o-cw-popup-avatar-size);
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.o_cw_popover_fields_secondary {
|
||||
max-height: 170px; // Fallback for old browsers
|
||||
max-height: 25vh;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
background: gray('200');
|
||||
width: 6px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: gray('500');
|
||||
}
|
||||
}
|
||||
|
||||
.fc-rtl & {
|
||||
text-align: right;
|
||||
.o_calendar_avatars {
|
||||
> div {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
img {
|
||||
order: 2;
|
||||
margin: 0 0 0 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Generate color classes ===============
|
||||
// These colors are specifically designed for calendar
|
||||
// =============== =============== ======================
|
||||
$o-cw-colors: (
|
||||
#5e0231, // 1:
|
||||
#97743a, // 2:
|
||||
#F4A460, // 3:
|
||||
#F7CD1F, // 4:
|
||||
#6CC1ED, // 5:
|
||||
#4285F4, // 6: Default User
|
||||
#8E24AA, // 7:
|
||||
#2C8397, // 8:
|
||||
#475577, // 9:
|
||||
#D6145F, // 10:
|
||||
#30C381, // 11:
|
||||
#9365B8, // 12:
|
||||
#aa4b6b, // 13:
|
||||
#348F50, // 14:
|
||||
#AA3A38, // 15:
|
||||
#173e43, // 16:
|
||||
#134E5E, // 17:
|
||||
#795548, // 18:
|
||||
#6be585, // 19:
|
||||
#999966, // 20:
|
||||
#e9d362, // 21:
|
||||
#b56969, // 22:
|
||||
#bdc3c7, // 23:
|
||||
#649173, // 24:
|
||||
);
|
||||
|
||||
|
||||
@for $i from 1 to length($o-cw-colors) + 1 {
|
||||
$color: nth($o-cw-colors, $i);
|
||||
|
||||
.o_calendar_view .fc-view {
|
||||
.fc-event.o_calendar_color_#{$i} {
|
||||
border-color: $color;
|
||||
color: darken($color, 35%);
|
||||
|
||||
.fc-bg {
|
||||
background: mix($color, white);
|
||||
}
|
||||
|
||||
&.o_cw_custom_hover, &.o_cw_custom_highlight {
|
||||
box-shadow: 0 12px 12px -5px rgba($color, 0.3);
|
||||
color: color-yiq($color);
|
||||
|
||||
.fc-bg {
|
||||
background: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fc-time-grid-event.fc-short .fc-time:before {
|
||||
content: '';
|
||||
.o_cw_filter_color_#{$i} {
|
||||
.o_cw_filter_input_bg {
|
||||
border-color: $color;
|
||||
background: $color;
|
||||
color: color-yiq($color);
|
||||
}
|
||||
}
|
||||
|
||||
.o_cw_popover.o_calendar_color_#{$i} {
|
||||
$color-subdle: mix(white, $color, 90%);
|
||||
|
||||
.card-header, .card-header .popover-header {
|
||||
background-color: $color-subdle;
|
||||
color: color-yiq($color-subdle);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
border-color: mix($card-border-color, mix(white, $color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_calendar_container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.o_control_panel {
|
||||
.o_calendar_button_today {
|
||||
font-size: 0.7em;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<div class="o_calendar_widget" />
|
||||
</div>
|
||||
<div class="o_calendar_sidebar_container d-none d-md-block">
|
||||
<i class="o_calendar_sidebar_toggler fa"/>
|
||||
<div class="o_calendar_sidebar">
|
||||
<div class="o_calendar_mini"/>
|
||||
</div>
|
||||
@@ -13,58 +12,81 @@
|
||||
</div>
|
||||
|
||||
<t t-name="calendar-box">
|
||||
<t t-set="color" t-value="widget.getColor(event.color_index)"/>
|
||||
<div t-att-style="typeof color === 'string' ? ('background-color:'+color)+';' : ''" t-attf-class="#{!event.color_index ? 'text-white' : ''} #{record.is_highlighted && record.is_highlighted.value ? 'o_event_hightlight' : ''} #{typeof color === 'number' ? 'o_calendar_color_'+color : ''}">
|
||||
<div class="fc-time"/>
|
||||
<div class="o_fields">
|
||||
<t t-foreach="widget.displayFields" t-as="name">
|
||||
<div t-attf-class="o_field_#{name} o_field_type_#{fields[name].type}">
|
||||
<t t-if="widget.displayFields[name].avatar_field">
|
||||
<t t-if="!isMobile"><t t-esc="fields[name].string"/>:</t>
|
||||
<div class="o_calendar_avatars float-right">
|
||||
<t t-foreach="widget.getAvatars(record, name, widget.displayFields[name].avatar_field).slice(0,3)" t-as="image"><t t-raw="image"/></t>
|
||||
<span t-if="record[name].length - 3 > 0">+<t t-esc="record[name].length - 3"/></span>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="format(record, name)"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<div t-att-style="typeof color === 'string' ? ('background-color:' + color) + ';' : ''" t-attf-class="#{!event.color_index ? 'text-white' : ''} #{record.is_highlighted and record.is_highlighted.value ? 'o_event_hightlight' : ''} #{typeof color === 'number' ? _.str.sprintf('o_calendar_color_%s', color) : ''}">
|
||||
<div class="o_event_title" t-esc="record.display_name"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="CalendarView.event.popover.placeholder">
|
||||
<div t-attf-class="o_cw_popover popover card shadow #{typeof color === 'number' ? _.str.sprintf('o_calendar_color_%s', color) : ''}" role="tooltip">
|
||||
<div class="arrow"/>
|
||||
<div class="card-header d-flex justify-content-between py-2 pr-2">
|
||||
<h4 class="popover-header border-0 p-0 pt-1"/>
|
||||
<span class="o_cw_popover_close ml-4"><i class="fa fa-close small"/></span>
|
||||
</div>
|
||||
<div class="o_cw_body">
|
||||
<div class="py-4 text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="CalendarView.event.popover">
|
||||
<div class="o_cw_body">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li t-if="!widget.hideDate and widget.eventDate.date" class="list-group-item">
|
||||
<i class="fa fa-fw fa-calendar-o"/>
|
||||
<b class="text-capitalize" t-esc="widget.eventDate.date"/> <small t-if="widget.eventDate.duration"><b t-esc="_.str.sprintf('(%s)', widget.eventDate.duration)"/></small>
|
||||
</li>
|
||||
<li t-if="!widget.hideTime and widget.eventTime.time" class="list-group-item">
|
||||
<i class="fa fa-fw fa-clock-o"/>
|
||||
<b t-esc="widget.eventTime.time"/> <small t-if="widget.eventTime.duration"><b t-esc="_.str.sprintf('(%s)', widget.eventTime.duration)"/></small>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="list-group list-group-flush o_cw_popover_fields_secondary"/>
|
||||
<div class="card-footer border-top">
|
||||
<a href="#" class="btn btn-primary o_cw_popover_edit">Edit</a>
|
||||
<a href="#" class="btn btn-secondary o_cw_popover_delete ml-2">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="CalendarView.sidebar.filter">
|
||||
<t t-set="needCollapse" t-value="widget.filters.length > 2 and widget.title"></t>
|
||||
<div class="o_calendar_filter">
|
||||
<h3 t-if="widget.title"><t t-esc="widget.title"/></h3>
|
||||
<div class="o_calendar_filter_items">
|
||||
<div t-foreach="widget.filters" t-as="filter" t-if="filter.display == null || filter.display" class="o_calendar_filter_item" t-att-data-value="filter.value" t-att-data-id="filter.id">
|
||||
<t t-set="id_for_label" t-value="_.uniqueId('o_calendar_filter_item_')"/>
|
||||
<div class="custom-control custom-control-inline custom-checkbox">
|
||||
<input type="checkbox"
|
||||
t-att-id="id_for_label"
|
||||
name="selection"
|
||||
class="custom-control-input"
|
||||
t-att-checked="(filter.active ? true : undefined)"/>
|
||||
<label t-att-for="id_for_label"
|
||||
class="custom-control-label">
|
||||
<t t-if="filter.value == 'all'">
|
||||
<span><i class="fa fa-users fa-fw o_cal_avatar" role="img" aria-label="Avatar" title="Avatar"/></span>
|
||||
</t>
|
||||
<t t-if="widget.avatar_field && (filter.value && filter.value != 'all')">
|
||||
<img t-attf-src="/web/image/#{widget.avatar_model}/#{filter.value}/#{widget.avatar_field}" class="o_cal_avatar" alt="Avatar"/>
|
||||
</t>
|
||||
<t t-set="color" t-value="widget.getColor(filter.color_index)"/>
|
||||
<span t-if="typeof color === 'number'" t-attf-class="color_filter o_underline_color_#{widget.getColor(filter.color_index)}"><t t-esc="filter.label"/></span>
|
||||
<span t-elif="color" t-attf-style="border-bottom: 4px solid #{color};"><t t-esc="filter.label"/></span>
|
||||
<span t-else=""><t t-esc="filter.label"/></span>
|
||||
<h5 t-if="needCollapse" type="button" data-toggle="collapse" t-attf-data-target="#o_cw_filter_collapse_#{widget.title}" class="d-flex justify-content-between align-items-center">
|
||||
<t t-esc="widget.title"/><i class="o_cw_filter_collapse_icon fa fa-chevron-down"/>
|
||||
</h5>
|
||||
|
||||
<h5 t-elif="widget.title" t-esc="widget.title"/>
|
||||
|
||||
<div t-attf-class="o_calendar_filter_items #{needCollapse ? 'collapse show' : 0}" aria-expanded="true" t-attf-id="o_cw_filter_collapse_#{widget.title}">
|
||||
<t t-foreach="widget.filters" t-as="filter" t-if="filter.display == null || filter.display">
|
||||
<t t-set="idForLabel" t-value="_.uniqueId('o_calendar_filter_item_')"/>
|
||||
<t t-set="color" t-value="widget.getColor(filter.color_index)"/>
|
||||
<t t-set="isColorNumber" t-value="typeof color === 'number'"/>
|
||||
|
||||
<div t-attf-class="o_calendar_filter_item w-100 position-relative mb-2 #{isColorNumber ? _.str.sprintf('o_cw_filter_color_%s', color) : ''}"
|
||||
t-att-data-value="filter.value" t-att-data-id="filter.id">
|
||||
|
||||
<input type="checkbox" t-att-id="idForLabel" name="selection" class="position-absolute" t-att-checked="filter.active ? true : undefined"/>
|
||||
|
||||
<label t-att-for="idForLabel" class="d-flex align-items-center m-0">
|
||||
<span t-attf-class="o_cw_filter_input_bg align-items-center d-flex flex-shrink-0 justify-content-center position-relative mr-1 #{widget.avatar_field ? 'o_beside_avatar' : ''}"
|
||||
t-att-style="color and !isColorNumber ? _.str.sprintf('border-color:%s;background-color:%s', color, color) : ''">
|
||||
<i class="fa fa-check position-relative"/>
|
||||
</span>
|
||||
<i t-if="filter.value == 'all'" class="o_cw_filter_avatar fa fa-users fa-fw flex-shrink-0 mr-1" role="img" aria-label="Avatar" title="Avatar"/>
|
||||
<img t-elif="widget.avatar_field and filter.value" t-attf-src="/web/image/#{widget.avatar_model}/#{filter.value}/#{widget.avatar_field}" class="o_cw_filter_avatar flex-shrink-0 mr-1" alt="Avatar"/>
|
||||
<span class="o_cw_filter_title text-truncate flex-grow" t-esc="filter.label" t-att-title="filter.label"/>
|
||||
</label>
|
||||
|
||||
<button t-if="filter.id" class="o_remove btn bg-white text-700 py-0 px-2" title="Remove this favorite from the list" role="img" aria-label="Remove this favorite from the list">
|
||||
<i class="fa fa-times"/>
|
||||
</button>
|
||||
</div>
|
||||
<t t-if="filter.id">
|
||||
<span class="o_remove fa fa-times" title="Remove this favorite from the list" role="img" aria-label="Remove this favorite from the list"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -161,7 +161,6 @@ QUnit.module('Views', {
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
@@ -179,7 +178,7 @@ QUnit.module('Views', {
|
||||
// test view scales
|
||||
assert.containsN(calendar, '.fc-event', 9,
|
||||
"should display 9 events on the week (4 event + 5 days event)");
|
||||
assert.containsN($sidebar, '.o_selected_range', 7,
|
||||
assert.containsN($sidebar, 'tr:has(.ui-state-active) td', 7,
|
||||
"week scale should highlight 7 days in mini calendar");
|
||||
|
||||
await testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_day')); // display only one day
|
||||
@@ -190,44 +189,44 @@ QUnit.module('Views', {
|
||||
await testUtils.dom.click(calendar.$buttons.find('.o_calendar_button_month')); // display all the month
|
||||
assert.containsN(calendar, '.fc-event', 7,
|
||||
"should display 7 events on the month (5 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')");
|
||||
assert.containsN($sidebar, '.o_selected_range', 31,
|
||||
assert.containsN($sidebar, 'td a', 31,
|
||||
"month scale should highlight all days in mini calendar");
|
||||
|
||||
// test filters
|
||||
assert.containsN($sidebar, '.o_calendar_filter', 2, "should display 2 filters");
|
||||
|
||||
var $typeFilter = $sidebar.find('.o_calendar_filter:has(h3:contains(user))');
|
||||
var $typeFilter = $sidebar.find('.o_calendar_filter:has(h5:contains(user))');
|
||||
assert.ok($typeFilter.length, "should display 'user' filter");
|
||||
assert.containsN($typeFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'user'");
|
||||
|
||||
// filters which has no value should show with string "Undefined", should not have any user image and should show at the last
|
||||
assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last').data('value'), false, "filters having false value should be displayed at last in filter items");
|
||||
assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last span').text(), "Undefined", "filters having false value should display 'Undefined' string");
|
||||
assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last .o_cw_filter_title').text(), "Undefined", "filters having false value should display 'Undefined' string");
|
||||
assert.strictEqual($typeFilter.find('.o_calendar_filter_item:last label img').length, 0, "filters having false value should not have any user image");
|
||||
|
||||
var $attendeesFilter = $sidebar.find('.o_calendar_filter:has(h3:contains(attendees))');
|
||||
var $attendeesFilter = $sidebar.find('.o_calendar_filter:has(h5:contains(attendees))');
|
||||
assert.ok($attendeesFilter.length, "should display 'attendees' filter");
|
||||
assert.containsN($attendeesFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'attendees' who use write_model (2 saved + Everything)");
|
||||
assert.ok($attendeesFilter.find('.o_field_many2one').length, "should display one2many search bar for 'attendees' filter");
|
||||
|
||||
assert.containsN(calendar, '.fc-event', 7,
|
||||
"should display 7 events ('event 5' counts for 2 because it spans two weeks and thus generate two fc-event elements)");
|
||||
await testUtils.dom.click(calendar.$('.o_calendar_filter .custom-checkbox input').first());
|
||||
await testUtils.dom.click(calendar.$('.o_calendar_filter input[type="checkbox"]').first());
|
||||
assert.containsN(calendar, '.fc-event', 4, "should now only display 4 event");
|
||||
await testUtils.dom.click(calendar.$('.o_calendar_filter .custom-checkbox input').eq(1));
|
||||
await testUtils.dom.click(calendar.$('.o_calendar_filter input[type="checkbox"]').eq(1));
|
||||
assert.containsNone(calendar, '.fc-event', "should not display any event anymore");
|
||||
|
||||
// test search bar in filter
|
||||
await testUtils.dom.click($sidebar.find('input[type="text"]'));
|
||||
assert.strictEqual($('ul.ui-autocomplete li:not(.o_m2o_dropdown_option)').length, 2,"should display 2 choices in one2many autocomplete"); // TODO: remove :not(.o_m2o_dropdown_option) because can't have "create & edit" choice
|
||||
await testUtils.dom.click($('ul.ui-autocomplete li:first'));
|
||||
assert.containsN($sidebar, '.o_calendar_filter:has(h3:contains(attendees)) .o_calendar_filter_item', 4, "should display 4 filter items for 'attendees'");
|
||||
assert.containsN($sidebar, '.o_calendar_filter:has(h5:contains(attendees)) .o_calendar_filter_item', 4, "should display 4 filter items for 'attendees'");
|
||||
await testUtils.dom.click($sidebar.find('input[type="text"]'));
|
||||
assert.strictEqual($('ul.ui-autocomplete li:not(.o_m2o_dropdown_option)').text(), "partner 4", "should display the last choice in one2many autocomplete"); // TODO: remove :not(.o_m2o_dropdown_option) because can't have "create & edit" choice
|
||||
await testUtils.dom.click($sidebar.find('.o_calendar_filter_item .o_remove').first(), {allowInvisible: true});
|
||||
assert.ok($('.modal-footer button.btn:contains(Ok)').length, "should display the confirm message");
|
||||
await testUtils.dom.click($('.modal-footer button.btn:contains(Ok)'));
|
||||
assert.containsN($sidebar, '.o_calendar_filter:has(h3:contains(attendees)) .o_calendar_filter_item', 3, "click on remove then should display 3 filter items for 'attendees'");
|
||||
assert.containsN($sidebar, '.o_calendar_filter:has(h5:contains(attendees)) .o_calendar_filter_item', 3, "click on remove then should display 3 filter items for 'attendees'");
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
@@ -235,9 +234,7 @@ QUnit.module('Views', {
|
||||
assert.expect(3);
|
||||
|
||||
var archs = {
|
||||
'event,1,calendar': '<calendar date_start="start" date_stop="stop" all_day="allday">' +
|
||||
'<field name="name"/>' +
|
||||
'</calendar>',
|
||||
'event,1,calendar': '<calendar date_start="start" date_stop="stop" all_day="allday"/>',
|
||||
'event,false,search': '<search></search>',
|
||||
};
|
||||
|
||||
@@ -290,11 +287,14 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="month" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="month"/>',
|
||||
archs: archs,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'write') {
|
||||
assert.deepEqual(args.args[1], {name: 'event 4 modified'}, "should update the record");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
@@ -306,20 +306,19 @@ QUnit.module('Views', {
|
||||
|
||||
await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content'));
|
||||
|
||||
assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event");
|
||||
assert.ok(calendar.$('.o_cw_popover .o_cw_popover_edit').length, "popover should have an edit button");
|
||||
assert.ok(calendar.$('.o_cw_popover .o_cw_popover_delete').length, "popover should have a delete button");
|
||||
assert.ok(calendar.$('.o_cw_popover .o_cw_popover_close').length, "popover should have a close button");
|
||||
|
||||
await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit'));
|
||||
|
||||
assert.ok($('.modal-body').length, "should open the form view in dialog when click on event");
|
||||
assert.ok($('.modal-footer button.btn:contains(Edit)').length, "formViewDialog should be in readonly mode");
|
||||
assert.ok($('.modal-footer button.btn:contains(Delete)').length, "formViewDialog should have a delete button");
|
||||
|
||||
await testUtils.dom.click($('.modal-footer button.btn:contains(Edit)'));
|
||||
|
||||
assert.ok($('.modal-body').length, "should switch the modal in edit mode");
|
||||
assert.notOk($('.modal-footer button.btn:contains(Delete)').length, "formViewDialog should not have a delete button in edit mode");
|
||||
|
||||
await testUtils.fields.editInput($('.modal-body input:first'), 'event 4 modified');
|
||||
await testUtils.dom.click($('.modal-footer button.btn:contains(Save)'));
|
||||
|
||||
assert.notOk($('.modal-body').length, "save button should close the modal");
|
||||
assert.ok(calendar.$('.fc-event:contains(event 4 modified)').length, "should display the updated records");
|
||||
|
||||
// create a new event, quick create only
|
||||
|
||||
@@ -406,7 +405,7 @@ QUnit.module('Views', {
|
||||
// delete the a record
|
||||
|
||||
await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content'));
|
||||
await testUtils.dom.click($('.modal-footer button.btn:contains(Delete)'));
|
||||
await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_delete'));
|
||||
assert.ok($('.modal-footer button.btn:contains(Ok)').length, "should display the confirm message");
|
||||
await testUtils.dom.click($('.modal-footer button.btn:contains(Ok)'));
|
||||
assert.notOk(calendar.$('.fc-event:contains(event 4) .fc-content').length, "the record should be deleted");
|
||||
@@ -435,10 +434,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="month" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="month"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -497,8 +493,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'mode="week">'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="start"/>'+
|
||||
'<field name="allday"/>'+
|
||||
@@ -541,7 +536,7 @@ QUnit.module('Views', {
|
||||
|
||||
testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove");
|
||||
|
||||
assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "08:00 - 10:00",
|
||||
assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "8:00 - 10:00",
|
||||
"should display the time in the calendar sticker");
|
||||
|
||||
await testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup");
|
||||
@@ -550,8 +545,8 @@ QUnit.module('Views', {
|
||||
await testUtils.dom.click($('.modal button.btn:contains(Create)'));
|
||||
var $newevent = calendar.$('.fc-event:contains(new event)');
|
||||
|
||||
assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "08:00-10:00newevent12/13/201608:00:00False",
|
||||
"should display the new event with time, title and additional fields");
|
||||
assert.strictEqual($newevent.find('.o_event_title').text(), "new event",
|
||||
"should display the new event with title");
|
||||
|
||||
assert.deepEqual($newevent.data('fcSeg').event.record,
|
||||
{
|
||||
@@ -567,7 +562,7 @@ QUnit.module('Views', {
|
||||
// delete record
|
||||
|
||||
await testUtils.dom.click($newevent);
|
||||
await testUtils.dom.click($('.modal button.btn-secondary:contains(Delete)'));
|
||||
await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_delete'));
|
||||
await testUtils.dom.click($('.modal button.btn-primary:contains(Ok)'));
|
||||
assert.containsNone(calendar, '.fc-content', "should delete the record");
|
||||
|
||||
@@ -595,7 +590,7 @@ QUnit.module('Views', {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
}).then(function (calendar) {
|
||||
assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Sun 12/11",
|
||||
assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Sun 11",
|
||||
"The first day of the week should be Sunday");
|
||||
calendar.destroy();
|
||||
done();
|
||||
@@ -626,7 +621,7 @@ QUnit.module('Views', {
|
||||
week_start: 1,
|
||||
},
|
||||
}).then(function (calendar) {
|
||||
assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Mon 12/12",
|
||||
assert.strictEqual(calendar.$('.fc-day-header').first().text(), "Mon 12",
|
||||
"The first day of the week should be Monday");
|
||||
calendar.destroy();
|
||||
done();
|
||||
@@ -659,15 +654,89 @@ QUnit.module('Views', {
|
||||
week_start: 7,
|
||||
},
|
||||
}).then(function (calendar) {
|
||||
assert.strictEqual(calendar.$('.fc-week-number').text(), "W51",
|
||||
assert.strictEqual(calendar.$('.fc-week-number').text(), "Week 51",
|
||||
"We should be on the 51st week");
|
||||
calendar.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('render popover', async function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
var calendar = await createCalendarView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar class="o_calendar_test" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week">'+
|
||||
'<field name="name" string="Custom Name"/>'+
|
||||
'<field name="partner_id"/>'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click($('.fc-event:contains(event 4)'));
|
||||
|
||||
assert.containsOnce(calendar, '.o_cw_popover', "should open a popover clicking on event");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .popover-header').text(), 'event 4', "popover should have a title 'event 4'");
|
||||
assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_edit', "popover should have an edit button");
|
||||
assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_delete', "popover should have a delete button");
|
||||
assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_close', "popover should have a close button");
|
||||
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .list-group-item:first b.text-capitalize').text(), 'Wednesday, December 14, 2016', "should display date 'Wednesday, December 14, 2016'");
|
||||
assert.containsN(calendar, '.o_cw_popover .o_cw_popover_fields_secondary .list-group-item', 2, "popover should have a two fields");
|
||||
|
||||
assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:first .o_field_char', "should apply char widget");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:first strong').text(), 'Custom Name : ', "label should be a 'Custom Name'");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:first .o_field_char').text(), 'event 4', "value should be a 'event 4'");
|
||||
|
||||
assert.containsOnce(calendar, '.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_form_uri', "should apply m20 widget");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last strong').text(), 'user : ', "label should be a 'user'");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_form_uri').text(), 'partner 1', "value should be a 'partner 1'");
|
||||
|
||||
await testUtils.dom.click($('.o_cw_popover .o_cw_popover_close'));
|
||||
assert.containsNone(calendar, '.o_cw_popover', "should close a popover");
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('attributes hide_date and hide_time', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var calendar = await createCalendarView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar class="o_calendar_test" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'hide_date="true" '+
|
||||
'hide_time="true" '+
|
||||
'mode="month">'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
},
|
||||
});
|
||||
|
||||
await testUtils.dom.click($('.fc-event:contains(event 4)'));
|
||||
assert.containsNone(calendar, '.o_cw_popover .list-group-item', "popover should not contain date/time");
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('create event with timezone in week mode with formViewDialog European locale', async function (assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(7);
|
||||
|
||||
this.data.event.records = [];
|
||||
this.data.event.onchanges = {
|
||||
@@ -692,8 +761,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'mode="week">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
@@ -771,8 +839,8 @@ QUnit.module('Views', {
|
||||
await testUtils.dom.click($('.modal-lg button.btn:contains(Save)'));
|
||||
var $newevent = calendar.$('.fc-event:contains(new event)');
|
||||
|
||||
assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "08:00-10:00newevent",
|
||||
"should display the new event with time and title");
|
||||
assert.strictEqual($newevent.find('.o_event_title').text(), "new event",
|
||||
"should display the new event with title");
|
||||
|
||||
assert.deepEqual($newevent.data('fcSeg').event.record,
|
||||
{
|
||||
@@ -831,8 +899,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'mode="week">'+
|
||||
'<field name="name"/>'+
|
||||
'<field name="start"/>'+
|
||||
'<field name="allday"/>'+
|
||||
@@ -875,7 +942,7 @@ QUnit.module('Views', {
|
||||
|
||||
testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mousemove");
|
||||
|
||||
assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "8:00am - 10:00am",
|
||||
assert.strictEqual(calendar.$('.fc-content .fc-time').text(), "8:00 - 10:00",
|
||||
"should display the time in the calendar sticker");
|
||||
|
||||
testUtils.dom.triggerPositionalMouseEvent(left, top + 60, "mouseup");
|
||||
@@ -884,8 +951,8 @@ QUnit.module('Views', {
|
||||
await testUtils.dom.click($('.modal button.btn:contains(Create)'));
|
||||
var $newevent = calendar.$('.fc-event:contains(new event)');
|
||||
|
||||
assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "8:00am-10:00amnewevent12/13/201608:00:00False",
|
||||
"should display the new event with time, title and additional fields");
|
||||
assert.strictEqual($newevent.find('.o_event_title').text(), "new event",
|
||||
"should display the new event with title");
|
||||
|
||||
assert.deepEqual($newevent.data('fcSeg').event.record,
|
||||
{
|
||||
@@ -901,7 +968,7 @@ QUnit.module('Views', {
|
||||
// delete record
|
||||
|
||||
await testUtils.dom.click($newevent);
|
||||
await testUtils.dom.click($('.modal button.btn-secondary:contains(Delete)'));
|
||||
await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_delete'));
|
||||
await testUtils.dom.click($('.modal button.btn-primary:contains(Ok)'));
|
||||
assert.containsNone(calendar, '.fc-content', "should delete the record");
|
||||
|
||||
@@ -909,7 +976,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
QUnit.test('create event with timezone in week mode with formViewDialog American locale', async function (assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(7);
|
||||
|
||||
this.data.event.records = [];
|
||||
this.data.event.onchanges = {
|
||||
@@ -934,8 +1001,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'mode="week">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
@@ -1013,8 +1079,8 @@ QUnit.module('Views', {
|
||||
await testUtils.dom.click($('.modal-lg button.btn:contains(Save)'));
|
||||
var $newevent = calendar.$('.fc-event:contains(new event)');
|
||||
|
||||
assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "8:00am-10:00amnewevent",
|
||||
"should display the new event with time and title");
|
||||
assert.strictEqual($newevent.find('.o_event_title').text(), "new event",
|
||||
"should display the new event with title");
|
||||
|
||||
assert.deepEqual($newevent.data('fcSeg').event.record,
|
||||
{
|
||||
@@ -1058,7 +1124,7 @@ QUnit.module('Views', {
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('check calendar week column timeformat and event content timeformat', async function (assert) {
|
||||
QUnit.test('check calendar week column timeformat', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var calendar = await createCalendarView({
|
||||
@@ -1066,9 +1132,7 @@ QUnit.module('Views', {
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar date_start="start">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'<calendar date_start="start"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -1079,7 +1143,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
assert.strictEqual(calendar.$('.fc-axis:contains(8am)').length, 1, "calendar should show according to timeformat");
|
||||
assert.strictEqual(calendar.$('.fc-event:first:contains(12:00am)').length, 1,
|
||||
assert.strictEqual(calendar.$('.fc-axis:contains(11pm)').length, 1,
|
||||
"event time format should 12 hour");
|
||||
|
||||
calendar.destroy();
|
||||
@@ -1100,8 +1164,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'mode="week">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
@@ -1164,10 +1227,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'mode="week" '+
|
||||
'quick_add="False" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'quick_add="False"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -1217,8 +1277,7 @@ QUnit.module('Views', {
|
||||
'event_open_popup="true" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'mode="month" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'mode="month">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
@@ -1243,14 +1302,14 @@ QUnit.module('Views', {
|
||||
},
|
||||
}, {positionalClicks: true});
|
||||
|
||||
var pos = calendar.$('.fc-bg td:eq(20)').offset();
|
||||
var pos = calendar.$('.fc-bg td:eq(17)').offset();
|
||||
try {
|
||||
testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown");
|
||||
} catch (e) {
|
||||
calendar.destroy();
|
||||
throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.');
|
||||
}
|
||||
pos = calendar.$('.fc-bg td:eq(21)').offset();
|
||||
pos = calendar.$('.fc-bg td:eq(18)').offset();
|
||||
testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove");
|
||||
testUtils.dom.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup");
|
||||
await testUtils.nextTick();
|
||||
@@ -1288,10 +1347,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="week" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="week"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -1344,7 +1400,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday"> '+
|
||||
'<field name="partner_ids" avatar_field="image" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'<field name="partner_ids" widget="many2many_tags_avatar" avatar_field="image" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
@@ -1352,18 +1408,19 @@ QUnit.module('Views', {
|
||||
},
|
||||
});
|
||||
|
||||
assert.containsN(calendar, '.o_calendar_filter_items .o_cal_avatar', 3,
|
||||
assert.containsN(calendar, '.o_calendar_filter_items .o_cw_filter_avatar', 3,
|
||||
"should have 3 avatars in the side bar");
|
||||
|
||||
var $event1Avatars = calendar.$('.fc-event .o_calendar_avatars').first();
|
||||
assert.containsOnce($event1Avatars, 'img', "should have 1 avatar");
|
||||
assert.containsNone($event1Avatars, 'span',
|
||||
"should not have a span for more attendees since there is only one");
|
||||
// Event 1
|
||||
await testUtils.dom.click(calendar.$('.fc-event:first'));
|
||||
assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover').find('img').length, 1, "should have 1 avatar");
|
||||
|
||||
// Event 2
|
||||
await testUtils.dom.click(calendar.$('.fc-event:eq(1)'));
|
||||
assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover').find('img').length, 5, "should have 5 avatar");
|
||||
|
||||
var $event2Avatars = calendar.$('.fc-event:contains(All day) .o_calendar_avatars');
|
||||
assert.containsN($event2Avatars, 'img', 3, "should have 3 avatars");
|
||||
assert.strictEqual($event2Avatars.find('span').text(), '+2',
|
||||
"should indicate there are 2 more attendees that we don't show");
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
@@ -1380,10 +1437,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="month" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="month"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -1411,6 +1465,7 @@ QUnit.module('Views', {
|
||||
"should open the form view");
|
||||
});
|
||||
await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content'));
|
||||
await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit'));
|
||||
|
||||
// create a new event and edit it
|
||||
|
||||
@@ -1456,10 +1511,7 @@ QUnit.module('Views', {
|
||||
'string="Events" ' +
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'mode="month" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="month"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -1515,10 +1567,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="month" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="month"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -1548,6 +1597,7 @@ QUnit.module('Views', {
|
||||
"should open the form view");
|
||||
});
|
||||
await testUtils.dom.click(calendar.$('.fc-event:contains(event 4) .fc-content'));
|
||||
await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit'));
|
||||
|
||||
// create a new event and edit it
|
||||
|
||||
@@ -1610,7 +1660,6 @@ QUnit.module('Views', {
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
@@ -1662,7 +1711,6 @@ QUnit.module('Views', {
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
@@ -1745,7 +1793,6 @@ QUnit.module('Views', {
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
@@ -1797,7 +1844,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
QUnit.test('Update event with filters', async function (assert) {
|
||||
assert.expect(4);
|
||||
assert.expect(5);
|
||||
|
||||
var records = this.data.user.records;
|
||||
records.push({
|
||||
@@ -1825,7 +1872,6 @@ QUnit.module('Views', {
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
@@ -1852,7 +1898,8 @@ QUnit.module('Views', {
|
||||
assert.containsN(calendar, '.fc-event', 3, "should display 3 events");
|
||||
|
||||
await testUtils.dom.click(calendar.$('.fc-event:contains(event 2) .fc-content'));
|
||||
await testUtils.dom.click($('.modal button.btn:contains(Edit)'));
|
||||
assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event");
|
||||
await testUtils.dom.click(calendar.$('.o_cw_popover .o_cw_popover_edit'));
|
||||
await testUtils.dom.click($('.modal .o_field_widget[name="user_id"] input'));
|
||||
await testUtils.dom.click($('.ui-menu-item a:contains(user 5)').trigger('mouseenter'));
|
||||
await testUtils.dom.click($('.modal button.btn:contains(Save)'));
|
||||
@@ -1916,7 +1963,6 @@ QUnit.module('Views', {
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
@@ -1930,7 +1976,7 @@ QUnit.module('Views', {
|
||||
|
||||
assert.containsN(calendar, '.o_calendar_filter_item', 6, "should display 6 filter items");
|
||||
assert.containsN(calendar, '.fc-event', 2, "should display 2 events");
|
||||
assert.strictEqual(calendar.$('.fc-event .o_field_name').text().replace(/\s/g, ''), "event7event8",
|
||||
assert.strictEqual(calendar.$('.fc-event .o_event_title').text().replace(/\s/g, ''), "event7event8",
|
||||
"should display 2 events");
|
||||
|
||||
calendar.destroy();
|
||||
@@ -2021,7 +2067,6 @@ QUnit.module('Views', {
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
@@ -2056,10 +2101,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="month" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="month"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -2126,7 +2168,6 @@ QUnit.module('Views', {
|
||||
'mode="week" '+
|
||||
'attendee="partner_ids" '+
|
||||
'color="partner_id">'+
|
||||
'<field name="name"/>'+
|
||||
'<filter name="user_id" avatar_field="image"/>'+
|
||||
'<field name="partner_ids" write_model="filter_partner" write_field="partner_id"/>'+
|
||||
'</calendar>',
|
||||
@@ -2178,10 +2219,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday" '+
|
||||
'mode="month" '+
|
||||
'readonly_form_view_id="1">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="month"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -2216,9 +2254,7 @@ QUnit.module('Views', {
|
||||
'event,1,calendar': '<calendar class="o_calendar_test" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'all_day="allday"> '+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'all_day="allday"/>',
|
||||
'event,false,search': '<search></search>',
|
||||
};
|
||||
|
||||
@@ -2250,9 +2286,7 @@ QUnit.module('Views', {
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar date_start="start_date">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'<calendar date_start="start_date"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -2278,7 +2312,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
QUnit.test('timezone does not affect drag and drop', async function (assert) {
|
||||
assert.expect(6);
|
||||
assert.expect(10);
|
||||
|
||||
var calendar = await createCalendarView({
|
||||
View: CalendarView,
|
||||
@@ -2308,12 +2342,13 @@ QUnit.module('Views', {
|
||||
},
|
||||
});
|
||||
|
||||
var events = calendar.$('.fc-event').map(function () {
|
||||
return $(this).text().trim().replace(/\s+/g, '|');
|
||||
});
|
||||
assert.strictEqual(calendar.$('.fc-event:eq(0)').text().trim(), "event 1");
|
||||
await testUtils.dom.click(calendar.$('.fc-event:eq(0)'));
|
||||
assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "12/09/2016 08:00:00");
|
||||
|
||||
assert.strictEqual(events[0], "event|1|12/09/2016|08:00:00");
|
||||
assert.strictEqual(events[5], "event|6|12/16/2016|16:00:00");
|
||||
assert.strictEqual(calendar.$('.fc-event:eq(5)').text().trim(), "event 6");
|
||||
await testUtils.dom.click(calendar.$('.fc-event:eq(5)'));
|
||||
assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "12/16/2016 16:00:00");
|
||||
|
||||
// Move event 6 as on first day of month view (27th november 2016)
|
||||
await testUtils.dragAndDrop(
|
||||
@@ -2322,18 +2357,19 @@ QUnit.module('Views', {
|
||||
);
|
||||
await testUtils.nextTick();
|
||||
|
||||
events = calendar.$('.fc-event').map(function () {
|
||||
return $(this).text().trim().replace(/\s+/g, '|');
|
||||
});
|
||||
assert.strictEqual(calendar.$('.fc-event:eq(0)').text().trim(), "event 6");
|
||||
await testUtils.dom.click(calendar.$('.fc-event:eq(0)'));
|
||||
assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "11/27/2016 16:00:00");
|
||||
|
||||
assert.strictEqual(events[0], "event|6|11/27/2016|16:00:00");
|
||||
assert.strictEqual(events[1], "event|1|12/09/2016|08:00:00");
|
||||
assert.strictEqual(calendar.$('.fc-event:eq(1)').text().trim(), "event 1");
|
||||
await testUtils.dom.click(calendar.$('.fc-event:eq(1)'));
|
||||
assert.strictEqual(calendar.$('.o_field_widget[name="start"]').text(), "12/09/2016 08:00:00");
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('timzeone does not affect calendar with date field', async function (assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(11);
|
||||
|
||||
var calendar = await createCalendarView({
|
||||
View: CalendarView,
|
||||
@@ -2374,7 +2410,9 @@ QUnit.module('Views', {
|
||||
await testUtils.dom.click($('.modal button.btn:contains(Create)'));
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "12/20/2016");
|
||||
await testUtils.dom.click(calendar.$('.fc-event:contains(An event)'));
|
||||
assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_field_date').text(), '12/20/2016', "should have correct start date");
|
||||
|
||||
// Move event to another day (on 27 november)
|
||||
await testUtils.dragAndDrop(
|
||||
@@ -2383,7 +2421,9 @@ QUnit.module('Views', {
|
||||
);
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps(["2016-11-27 00:00:00"]);
|
||||
assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "11/27/2016");
|
||||
await testUtils.dom.click(calendar.$('.fc-event:contains(An event)'));
|
||||
assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_field_date').text(), '11/27/2016', "should have correct start date");
|
||||
|
||||
// Move event to last day (on 7 january)
|
||||
await testUtils.dragAndDrop(
|
||||
@@ -2392,7 +2432,9 @@ QUnit.module('Views', {
|
||||
);
|
||||
await testUtils.nextTick();
|
||||
assert.verifySteps(["2017-01-07 00:00:00"]);
|
||||
assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "01/07/2017");
|
||||
await testUtils.dom.click(calendar.$('.fc-event:contains(An event)'));
|
||||
assert.ok(calendar.$('.o_cw_popover').length, "should open a popover clicking on event");
|
||||
assert.strictEqual(calendar.$('.o_cw_popover .o_cw_popover_fields_secondary .list-group-item:last .o_field_date').text(), '01/07/2017', "should have correct start date");
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
@@ -2452,9 +2494,7 @@ QUnit.module('Views', {
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'mode="month" '+
|
||||
'form_view_id="42">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'form_view_id="42"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -2536,9 +2576,7 @@ QUnit.module('Views', {
|
||||
arch: '<calendar class="o_calendar_test" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'mode="month"> '+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="month"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -2593,9 +2631,7 @@ QUnit.module('Views', {
|
||||
arch: '<calendar class="o_calendar_test" '+
|
||||
'date_start="start" '+
|
||||
'date_stop="stop" '+
|
||||
'mode="week"> '+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
'mode="week"/>',
|
||||
archs: archs,
|
||||
viewOptions: {
|
||||
initialDate: initialDate,
|
||||
@@ -2604,7 +2640,7 @@ QUnit.module('Views', {
|
||||
|
||||
});
|
||||
|
||||
assert.strictEqual(calendar.$('.fc-day-header:first').text(), "zz1. 11/12",
|
||||
assert.strictEqual(calendar.$('.fc-day-header:first').text(), "zz1. 11",
|
||||
'The day should be in the given locale specific format');
|
||||
|
||||
moment.locale(initialLocale);
|
||||
@@ -2612,45 +2648,6 @@ QUnit.module('Views', {
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('calendar sidebar toggle', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
var calendar = await createCalendarView({
|
||||
View: CalendarView,
|
||||
model: 'event',
|
||||
data: this.data,
|
||||
arch:
|
||||
'<calendar date_start="start_date">'+
|
||||
'<field name="name"/>'+
|
||||
'</calendar>',
|
||||
services: {
|
||||
local_storage: AbstractStorageService.extend({storage: new RamStorage()}),
|
||||
},
|
||||
});
|
||||
|
||||
var fullWidth = calendar.call('local_storage', 'getItem', 'calendar_fullWidth');
|
||||
assert.strictEqual(fullWidth, undefined,
|
||||
"calendar_fullWidth should be undefined in local storage");
|
||||
assert.containsNone(calendar, '.o_calendar_sidebar_container.o_sidebar_hidden',
|
||||
"sidebar should be opened");
|
||||
|
||||
// click on close
|
||||
await testUtils.dom.click(calendar.$('.o_calendar_sidebar_toggler'));
|
||||
fullWidth = calendar.call('local_storage', 'getItem', 'calendar_fullWidth');
|
||||
assert.strictEqual(fullWidth, true,
|
||||
"calendar_fullWidth should be true in local storage");
|
||||
assert.containsOnce(calendar, '.o_calendar_sidebar_container.o_sidebar_hidden',
|
||||
"sidebar should be hidden");
|
||||
|
||||
// reload calendar view and check calendar view has still full width
|
||||
calendar.reload();
|
||||
await testUtils.nextTick();
|
||||
assert.containsOnce(calendar, '.o_calendar_sidebar_container.o_sidebar_hidden',
|
||||
"sidebar should be hidden");
|
||||
|
||||
calendar.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -355,6 +355,7 @@
|
||||
|
||||
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_controller.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_model.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_popover.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_quick_create.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_renderer.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/calendar/calendar_view.js"></script>
|
||||
|
||||
@@ -1168,8 +1168,6 @@ calendar view are:
|
||||
same color segment are allocated the same highlight color in the calendar,
|
||||
colors are allocated semi-randomly.
|
||||
Displayed the display_name/avatar of the visible record in the sidebar
|
||||
``readonly_form_view_id``
|
||||
view to open in readonly mode
|
||||
``form_view_id``
|
||||
view to open when the user create or edit an event. Note that if this attribute
|
||||
is not set, the calendar view will fall back to the id of the form view in the
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
<rng:optional><rng:attribute name="date_stop" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="date_delay" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="all_day" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="readonly_form_view_id" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="form_view_id" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="event_limit" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="quick_add" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="color" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="event_open_popup" /></rng:optional>
|
||||
<rng:optional><rng:attribute name="js_class"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="hide_time"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="hide_date"/></rng:optional>
|
||||
<rng:optional>
|
||||
<rng:attribute name="mode">
|
||||
<rng:choice>
|
||||
@@ -32,13 +33,6 @@
|
||||
<rng:zeroOrMore>
|
||||
<rng:ref name="field"/>
|
||||
</rng:zeroOrMore>
|
||||
<rng:optional>
|
||||
<rng:element name="templates">
|
||||
<rng:oneOrMore>
|
||||
<rng:ref name="any"/>
|
||||
</rng:oneOrMore>
|
||||
</rng:element>
|
||||
</rng:optional>
|
||||
</rng:element>
|
||||
</rng:define>
|
||||
<rng:start>
|
||||
|
||||
Reference in New Issue
Block a user