diff --git a/web_timeline/README.rst b/web_timeline/README.rst
index fb91d6451..e5aed5fa2 100644
--- a/web_timeline/README.rst
+++ b/web_timeline/README.rst
@@ -14,13 +14,13 @@ Web timeline
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
- :target: https://github.com/OCA/web/tree/15.0/web_timeline
+ :target: https://github.com/OCA/web/tree/16.0/web_timeline
:alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_timeline
+ :target: https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_timeline
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/162/15.0
+ :target: https://runbot.odoo-community.org/runbot/162/16.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -91,25 +91,50 @@ Example:
-
+ colors="white: user_ids == []; #2ecb71: kanban_state == 'done'; #ec7063: kanban_state == 'blocked'"
+ dependency_arrow="depend_on_ids"
+ >
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
- kanban,tree,form,calendar,gantt,timeline,graph
+ kanban,tree,form,calendar,timeline,pivot,graph,activity
@@ -165,7 +190,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -188,7 +213,6 @@ Contributors
* Adrien Peiffer
* Leonardo Donelli
* Adrien Didenot
-* Dennis Sluijk
* Thong Nguyen Van
* Murtaza Mithaiwala
* Ammar Officewala
@@ -197,6 +221,9 @@ Contributors
* Pedro M. Baeza
* Alexandre Díaz
* César A. Sánchez
+* `Onestein `_:
+ * Dennis Sluijk
+ * Anjeel Haria
Maintainers
~~~~~~~~~~~
@@ -219,6 +246,6 @@ Current `maintainer `__:
|maintainer-tarteo|
-This module is part of the `OCA/web `_ project on GitHub.
+This module is part of the `OCA/web `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/web_timeline/__manifest__.py b/web_timeline/__manifest__.py
index b87b79fa1..84c79ddae 100644
--- a/web_timeline/__manifest__.py
+++ b/web_timeline/__manifest__.py
@@ -4,7 +4,7 @@
{
"name": "Web timeline",
"summary": "Interactive visualization chart to show events in time",
- "version": "15.0.1.0.1",
+ "version": "16.0.1.0.0",
"development_status": "Production/Stable",
"author": "ACSONE SA/NV, "
"Tecnativa, "
@@ -25,11 +25,9 @@
"web_timeline/static/src/scss/web_timeline.scss",
"web_timeline/static/src/js/timeline_view.js",
"web_timeline/static/src/js/timeline_renderer.js",
- "web_timeline/static/src/js/timeline_controller.js",
+ "web_timeline/static/src/js/timeline_controller.esm.js",
"web_timeline/static/src/js/timeline_model.js",
"web_timeline/static/src/js/timeline_canvas.js",
- ],
- "web.assets_qweb": [
"web_timeline/static/src/xml/web_timeline.xml",
],
},
diff --git a/web_timeline/i18n/web_timeline.pot b/web_timeline/i18n/web_timeline.pot
index c796429f4..a31a5b83d 100644
--- a/web_timeline/i18n/web_timeline.pot
+++ b/web_timeline/i18n/web_timeline.pot
@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 15.0\n"
+"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -14,42 +14,42 @@ msgstr ""
"Plural-Forms: \n"
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/js/timeline_renderer.js:0
#, python-format
msgid "UNASSIGNED"
msgstr ""
#. module: web_timeline
-#. openerp-web
-#: code:addons/web_timeline/static/src/js/timeline_controller.js:0
+#. odoo-javascript
+#: code:addons/web_timeline/static/src/js/timeline_controller.esm.js:0
#, python-format
msgid "Are you sure you want to delete this record?"
msgstr ""
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:0
#, python-format
msgid "Day"
msgstr ""
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:0
#, python-format
msgid "Month"
msgstr ""
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/js/timeline_renderer.js:0
#, python-format
msgid "Template \"timeline-item\" not present in timeline view definition."
msgstr ""
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/js/timeline_view.js:0
#: model:ir.model.fields.selection,name:web_timeline.selection__ir_ui_view__type__timeline
#, python-format
@@ -57,14 +57,14 @@ msgid "Timeline"
msgstr ""
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/js/timeline_renderer.js:0
#, python-format
msgid "Timeline view has not defined 'date_start' attribute."
msgstr ""
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:0
#, python-format
msgid "Today"
@@ -81,21 +81,21 @@ msgid "View Type"
msgstr ""
#. module: web_timeline
-#. openerp-web
-#: code:addons/web_timeline/static/src/js/timeline_controller.js:0
+#. odoo-javascript
+#: code:addons/web_timeline/static/src/js/timeline_controller.esm.js:0
#, python-format
msgid "Warning"
msgstr ""
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:0
#, python-format
msgid "Week"
msgstr ""
#. module: web_timeline
-#. openerp-web
+#. odoo-javascript
#: code:addons/web_timeline/static/src/xml/web_timeline.xml:0
#, python-format
msgid "Year"
diff --git a/web_timeline/readme/CONFIGURE.rst b/web_timeline/readme/CONFIGURE.rst
index 4ae188970..db4147293 100644
--- a/web_timeline/readme/CONFIGURE.rst
+++ b/web_timeline/readme/CONFIGURE.rst
@@ -51,24 +51,49 @@ Example:
-
+ colors="white: user_ids == []; #2ecb71: kanban_state == 'done'; #ec7063: kanban_state == 'blocked'"
+ dependency_arrow="depend_on_ids"
+ >
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
- kanban,tree,form,calendar,gantt,timeline,graph
+ kanban,tree,form,calendar,timeline,pivot,graph,activity
diff --git a/web_timeline/readme/CONTRIBUTORS.rst b/web_timeline/readme/CONTRIBUTORS.rst
index 8c7dfbdbb..d13a44e9c 100644
--- a/web_timeline/readme/CONTRIBUTORS.rst
+++ b/web_timeline/readme/CONTRIBUTORS.rst
@@ -2,7 +2,6 @@
* Adrien Peiffer
* Leonardo Donelli
* Adrien Didenot
-* Dennis Sluijk
* Thong Nguyen Van
* Murtaza Mithaiwala
* Ammar Officewala
@@ -11,3 +10,6 @@
* Pedro M. Baeza
* Alexandre Díaz
* César A. Sánchez
+* `Onestein `_:
+ * Dennis Sluijk
+ * Anjeel Haria
diff --git a/web_timeline/static/description/index.html b/web_timeline/static/description/index.html
index 0e5e3fd03..597a08b73 100644
--- a/web_timeline/static/description/index.html
+++ b/web_timeline/static/description/index.html
@@ -367,7 +367,7 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

Define a new view displaying events in an interactive visualization chart.
The widget is based on the external library
https://visjs.github.io/vis-timeline/examples/timeline
@@ -470,25 +470,50 @@ These are the variables available in template rendering:
<timeline date_start="date_assign"
date_stop="date_end"
string="Tasks"
- default_group_by="user_id"
+ default_group_by="project_id"
event_open_popup="true"
- zoomKey="ctrlKey"
- colors="#ec7063:user_id == false;#2ecb71:kanban_state=='done';"
- dependency_arrow="task_dependency_ids">
- <field name="user_id"/>
+ colors="white: user_ids == []; #2ecb71: kanban_state == 'done'; #ec7063: kanban_state == 'blocked'"
+ dependency_arrow="depend_on_ids"
+ >
+ <field name="user_ids" />
+ <field name="planned_hours" />
<templates>
- <div t-name="timeline-item">
- <div t-esc="record.display_name"/>
- Assigned to:
- <span t-esc="record.user_id[1]"/>
- </div>
+ <t t-name="timeline-item">
+ <div class="o_project_timeline_item">
+ <t t-foreach="record.user_ids" t-as="user">
+ <img
+ t-if="record.user_ids"
+ t-attf-src="/web/image/res.users/#{user}/image_128/16x16"
+ t-att-title="record.user"
+ width="16"
+ height="16"
+ class="mr8"
+ alt="User"
+ />
+ </t>
+ <span name="display_name">
+ <t t-esc="record.display_name" />
+ </span>
+ <small
+ name="planned_hours"
+ class="text-info ml4"
+ t-if="record.planned_hours"
+ >
+ <t
+ t-esc="field_utils.format.float_time(record.planned_hours)"
+ />
+ </small>
+ </div>
+ </t>
</templates>
</timeline>
</field>
</record>
<record id="project.action_view_task" model="ir.actions.act_window">
- <field name="view_mode">kanban,tree,form,calendar,gantt,timeline,graph</field>
+ <field
+ name="view_mode"
+ >kanban,tree,form,calendar,timeline,pivot,graph,activity</field>
</record>
</odoo>
@@ -539,7 +564,7 @@ the value according the group of the dragged item.
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback.
+feedback.
Do not contact contributors directly about support or help with technical issues.
@@ -582,7 +615,7 @@ mission is to support the collaborative development of Odoo features and
promote its widespread use.
Current maintainer:

-
This module is part of the OCA/web project on GitHub.
+
This module is part of the OCA/web project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js b/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js
index d776b5609..55e2137e4 100644
--- a/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js
+++ b/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js
@@ -50109,4 +50109,3 @@
Object.defineProperty(exports, '__esModule', { value: true });
}));
-//# sourceMappingURL=vis-timeline-graph2d.js.map
diff --git a/web_timeline/static/src/js/timeline_controller.esm.js b/web_timeline/static/src/js/timeline_controller.esm.js
new file mode 100644
index 000000000..630609a1f
--- /dev/null
+++ b/web_timeline/static/src/js/timeline_controller.esm.js
@@ -0,0 +1,360 @@
+/** @odoo-module alias=web_timeline.TimelineController **/
+/* Copyright 2023 Onestein - Anjeel Haria
+ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
+import AbstractController from "web.AbstractController";
+import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog";
+import time from "web.time";
+import core from "web.core";
+import Dialog from "web.Dialog";
+var _t = core._t;
+import {Component} from "@odoo/owl";
+
+export default AbstractController.extend({
+ custom_events: _.extend({}, AbstractController.prototype.custom_events, {
+ onGroupClick: "_onGroupClick",
+ onUpdate: "_onUpdate",
+ onRemove: "_onRemove",
+ onMove: "_onMove",
+ onAdd: "_onAdd",
+ }),
+
+ /**
+ * @override
+ */
+ init: function (parent, model, renderer, params) {
+ this._super.apply(this, arguments);
+ this.open_popup_action = params.open_popup_action;
+ this.date_start = params.date_start;
+ this.date_stop = params.date_stop;
+ this.date_delay = params.date_delay;
+ this.context = params.actionContext;
+ this.moveQueue = [];
+ this.debouncedInternalMove = _.debounce(this.internalMove, 0);
+ },
+ on_detach_callback() {
+ if (this.Dialog) {
+ this.Dialog();
+ this.Dialog = undefined;
+ }
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * @override
+ */
+ update: function (params, options) {
+ const res = this._super.apply(this, arguments);
+ if (_.isEmpty(params)) {
+ return res;
+ }
+ const defaults = _.defaults({}, options, {
+ adjust_window: true,
+ });
+ const domains = params.domain || this.renderer.last_domains || [];
+ const contexts = params.context || [];
+ const group_bys = params.groupBy || this.renderer.last_group_bys || [];
+ this.last_domains = domains;
+ this.last_contexts = contexts;
+ // Select the group by
+ let n_group_bys = group_bys;
+ if (!n_group_bys.length && this.renderer.arch.attrs.default_group_by) {
+ n_group_bys = this.renderer.arch.attrs.default_group_by.split(",");
+ }
+ this.renderer.last_group_bys = n_group_bys;
+ this.renderer.last_domains = domains;
+
+ let fields = this.renderer.fieldNames;
+ fields = _.uniq(fields.concat(n_group_bys));
+ $.when(
+ res,
+ this._rpc({
+ model: this.model.modelName,
+ method: "search_read",
+ kwargs: {
+ fields: fields,
+ domain: domains,
+ order: [{name: this.renderer.arch.attrs.default_group_by}],
+ },
+ context: this.getSession().user_context,
+ }).then((data) =>
+ this.renderer.on_data_loaded(data, n_group_bys, defaults.adjust_window)
+ )
+ );
+ return res;
+ },
+
+ /**
+ * Gets triggered when a group in the timeline is
+ * clicked (by the TimelineRenderer).
+ *
+ * @private
+ * @param {EventObject} event
+ * @returns {jQuery.Deferred}
+ */
+ _onGroupClick: function (event) {
+ const groupField = this.renderer.last_group_bys[0];
+ return this.do_action({
+ type: "ir.actions.act_window",
+ res_model: this.renderer.fields[groupField].relation,
+ res_id: event.data.item.group,
+ target: "new",
+ views: [[false, "form"]],
+ });
+ },
+
+ /**
+ * Opens a form view of a clicked timeline
+ * item (triggered by the TimelineRenderer).
+ *
+ * @private
+ * @param {EventObject} event
+ */
+ _onUpdate: function (event) {
+ this.renderer = event.data.renderer;
+ const rights = event.data.rights;
+ const item = event.data.item;
+ const id = Number(item.evt.id) || item.evt.id;
+ const title = item.evt.__name;
+ if (this.open_popup_action) {
+ this.Dialog = Component.env.services.dialog.add(
+ FormViewDialog,
+ {
+ resId: id,
+ context: this.getSession().user_context,
+ title: title,
+ onRecordSaved: () => this.write_completed(),
+ resModel: this.model.modelName,
+ },
+ {}
+ );
+ } else {
+ let mode = "readonly";
+ if (rights.write) {
+ mode = "edit";
+ }
+ this.trigger_up("switch_view", {
+ view_type: "form",
+ res_id: id,
+ mode: mode,
+ model: this.model.modelName,
+ });
+ }
+ },
+
+ /**
+ * Gets triggered when a timeline item is
+ * moved (triggered by the TimelineRenderer).
+ *
+ * @private
+ * @param {EventObject} event
+ */
+ _onMove: function (event) {
+ const item = event.data.item;
+ const fields = this.renderer.fields;
+ const event_start = item.start;
+ const event_end = item.end;
+ let group = false;
+ if (item.group !== -1) {
+ group = item.group;
+ }
+ const data = {};
+ // In case of a move event, the date_delay stay the same,
+ // only date_start and stop must be updated
+ data[this.date_start] = time.auto_date_to_str(
+ event_start,
+ fields[this.date_start].type
+ );
+ if (this.date_stop) {
+ // In case of instantaneous event, item.end is not defined
+ if (event_end) {
+ data[this.date_stop] = time.auto_date_to_str(
+ event_end,
+ fields[this.date_stop].type
+ );
+ } else {
+ data[this.date_stop] = data[this.date_start];
+ }
+ }
+ if (this.date_delay && event_end) {
+ const diff_seconds = Math.round(
+ (event_end.getTime() - event_start.getTime()) / 1000
+ );
+ data[this.date_delay] = diff_seconds / 3600;
+ }
+ const grouped_field = this.renderer.last_group_bys[0];
+ this._rpc({
+ model: this.modelName,
+ method: "fields_get",
+ args: [grouped_field],
+ context: this.getSession().user_context,
+ }).then(async (fields_processed) => {
+ if (
+ this.renderer.last_group_bys &&
+ this.renderer.last_group_bys instanceof Array &&
+ fields_processed[grouped_field].type !== "many2many"
+ ) {
+ data[this.renderer.last_group_bys[0]] = group;
+ }
+
+ this.moveQueue.push({
+ id: event.data.item.id,
+ data: data,
+ event: event,
+ });
+
+ this.debouncedInternalMove();
+ });
+ },
+
+ /**
+ * Write enqueued moves to Odoo. After all writes are finished it updates
+ * the view once (prevents flickering of the view when multiple timeline items
+ * are moved at once).
+ *
+ * @returns {jQuery.Deferred}
+ */
+ internalMove: function () {
+ const queues = this.moveQueue.slice();
+ this.moveQueue = [];
+ const defers = [];
+ for (const item of queues) {
+ defers.push(
+ this._rpc({
+ model: this.model.modelName,
+ method: "write",
+ args: [[item.event.data.item.id], item.data],
+ context: this.getSession().user_context,
+ }).then(() => {
+ item.event.data.callback(item.event.data.item);
+ })
+ );
+ }
+ return $.when.apply($, defers).done(() => {
+ this.write_completed({
+ adjust_window: false,
+ });
+ });
+ },
+
+ /**
+ * Triggered when a timeline item gets removed from the view.
+ * Requires user confirmation before it gets actually deleted.
+ *
+ * @private
+ * @param {EventObject} event
+ * @returns {jQuery.Deferred}
+ */
+ _onRemove: function (event) {
+ var def = $.Deferred();
+
+ Dialog.confirm(this, _t("Are you sure you want to delete this record?"), {
+ title: _t("Warning"),
+ confirm_callback: () => {
+ this.remove_completed(event).then(def.resolve.bind(def));
+ },
+ cancel_callback: def.resolve.bind(def),
+ });
+
+ return def;
+ },
+
+ /**
+ * Triggered when a timeline item gets added and opens a form view.
+ *
+ * @private
+ * @param {EventObject} event
+ * @returns {dialogs.FormViewDialog}
+ */
+ _onAdd: function (event) {
+ const item = event.data.item;
+ // Initialize default values for creation
+ const default_context = {};
+ default_context["default_".concat(this.date_start)] = item.start;
+ if (this.date_delay) {
+ default_context["default_".concat(this.date_delay)] = 1;
+ }
+ if (this.date_start) {
+ default_context["default_".concat(this.date_start)] = moment(item.start)
+ .utc()
+ .format("YYYY-MM-DD HH:mm:ss");
+ }
+ if (this.date_stop && item.end) {
+ default_context["default_".concat(this.date_stop)] = moment(item.end)
+ .utc()
+ .format("YYYY-MM-DD HH:mm:ss");
+ }
+ if (item.group > 0) {
+ default_context["default_".concat(this.renderer.last_group_bys[0])] =
+ item.group;
+ }
+ // Show popup
+ this.Dialog = Component.env.services.dialog.add(
+ FormViewDialog,
+ {
+ resId: null,
+ context: _.extend(default_context, this.context),
+ onRecordSaved: (record) => this.create_completed([record.res_id]),
+ resModel: this.model.modelName,
+ },
+ {onClose: () => event.data.callback()}
+ );
+ return false;
+ },
+
+ /**
+ * Triggered upon completion of a new record.
+ * Updates the timeline view with the new record.
+ *
+ * @param {RecordId} id
+ * @returns {jQuery.Deferred}
+ */
+ create_completed: function (id) {
+ return this._rpc({
+ model: this.model.modelName,
+ method: "read",
+ args: [id, this.model.fieldNames],
+ context: this.context,
+ }).then((records) => {
+ var new_event = this.renderer.event_data_transform(records[0]);
+ var items = this.renderer.timeline.itemsData;
+ items.add(new_event);
+ });
+ },
+
+ /**
+ * Triggered upon completion of writing a record.
+ * @param {ControllerOptions} options
+ */
+ write_completed: function (options) {
+ const params = {
+ domain: this.renderer.last_domains,
+ context: this.context,
+ groupBy: this.renderer.last_group_bys,
+ };
+ this.update(params, options);
+ },
+
+ /**
+ * Triggered upon confirm of removing a record.
+ * @param {EventObject} event
+ * @returns {jQuery.Deferred}
+ */
+ remove_completed: function (event) {
+ return this._rpc({
+ model: this.modelName,
+ method: "unlink",
+ args: [[event.data.item.id]],
+ context: this.getSession().user_context,
+ }).then(() => {
+ let unlink_index = false;
+ for (var i = 0; i < this.model.data.data.length; i++) {
+ if (this.model.data.data[i].id === event.data.item.id) {
+ unlink_index = i;
+ }
+ }
+ if (!isNaN(unlink_index)) {
+ this.model.data.data.splice(unlink_index, 1);
+ }
+ event.data.callback(event.data.item);
+ });
+ },
+});
diff --git a/web_timeline/static/src/js/timeline_controller.js b/web_timeline/static/src/js/timeline_controller.js
deleted file mode 100644
index bd814f0aa..000000000
--- a/web_timeline/static/src/js/timeline_controller.js
+++ /dev/null
@@ -1,364 +0,0 @@
-odoo.define("web_timeline.TimelineController", function (require) {
- "use strict";
-
- const AbstractController = require("web.AbstractController");
- const dialogs = require("web.view_dialogs");
- const core = require("web.core");
- const time = require("web.time");
- const Dialog = require("web.Dialog");
-
- const _t = core._t;
-
- const TimelineController = AbstractController.extend({
- custom_events: _.extend({}, AbstractController.prototype.custom_events, {
- onGroupClick: "_onGroupClick",
- onUpdate: "_onUpdate",
- onRemove: "_onRemove",
- onMove: "_onMove",
- onAdd: "_onAdd",
- }),
-
- /**
- * @override
- */
- init: function (parent, model, renderer, params) {
- this._super.apply(this, arguments);
- this.open_popup_action = params.open_popup_action;
- this.date_start = params.date_start;
- this.date_stop = params.date_stop;
- this.date_delay = params.date_delay;
- this.context = params.actionContext;
- this.moveQueue = [];
- this.debouncedInternalMove = _.debounce(this.internalMove, 0);
- },
-
- /**
- * @override
- */
- update: function (params, options) {
- const res = this._super.apply(this, arguments);
- if (_.isEmpty(params)) {
- return res;
- }
- const defaults = _.defaults({}, options, {
- adjust_window: true,
- });
- const domains = params.domain || this.renderer.last_domains || [];
- const contexts = params.context || [];
- const group_bys = params.groupBy || this.renderer.last_group_bys || [];
- this.last_domains = domains;
- this.last_contexts = contexts;
- // Select the group by
- let n_group_bys = group_bys;
- if (!n_group_bys.length && this.renderer.arch.attrs.default_group_by) {
- n_group_bys = this.renderer.arch.attrs.default_group_by.split(",");
- }
- this.renderer.last_group_bys = n_group_bys;
- this.renderer.last_domains = domains;
-
- let fields = this.renderer.fieldNames;
- fields = _.uniq(fields.concat(n_group_bys));
- $.when(
- res,
- this._rpc({
- model: this.model.modelName,
- method: "search_read",
- kwargs: {
- fields: fields,
- domain: domains,
- order: [{name: this.renderer.arch.attrs.default_group_by}],
- },
- context: this.getSession().user_context,
- }).then((data) =>
- this.renderer.on_data_loaded(
- data,
- n_group_bys,
- defaults.adjust_window
- )
- )
- );
- return res;
- },
-
- /**
- * Gets triggered when a group in the timeline is
- * clicked (by the TimelineRenderer).
- *
- * @private
- * @param {EventObject} event
- * @returns {jQuery.Deferred}
- */
- _onGroupClick: function (event) {
- const groupField = this.renderer.last_group_bys[0];
- return this.do_action({
- type: "ir.actions.act_window",
- res_model: this.renderer.fields[groupField].relation,
- res_id: event.data.item.group,
- target: "new",
- views: [[false, "form"]],
- });
- },
-
- /**
- * Opens a form view of a clicked timeline
- * item (triggered by the TimelineRenderer).
- *
- * @private
- * @param {EventObject} event
- */
- _onUpdate: function (event) {
- this.renderer = event.data.renderer;
- const rights = event.data.rights;
- const item = event.data.item;
- const id = Number(item.evt.id) || item.evt.id;
- const title = item.evt.__name;
- if (this.open_popup_action) {
- new dialogs.FormViewDialog(this, {
- res_model: this.model.modelName,
- res_id: id,
- context: this.getSession().user_context,
- title: title,
- view_id: Number(this.open_popup_action),
- on_saved: () => {
- this.write_completed();
- },
- }).open();
- } else {
- let mode = "readonly";
- if (rights.write) {
- mode = "edit";
- }
- this.trigger_up("switch_view", {
- view_type: "form",
- res_id: id,
- mode: mode,
- model: this.model.modelName,
- });
- }
- },
-
- /**
- * Gets triggered when a timeline item is
- * moved (triggered by the TimelineRenderer).
- *
- * @private
- * @param {EventObject} event
- */
- _onMove: function (event) {
- const item = event.data.item;
- const fields = this.renderer.fields;
- const event_start = item.start;
- const event_end = item.end;
- let group = false;
- if (item.group !== -1) {
- group = item.group;
- }
- const data = {};
- // In case of a move event, the date_delay stay the same,
- // only date_start and stop must be updated
- data[this.date_start] = time.auto_date_to_str(
- event_start,
- fields[this.date_start].type
- );
- if (this.date_stop) {
- // In case of instantaneous event, item.end is not defined
- if (event_end) {
- data[this.date_stop] = time.auto_date_to_str(
- event_end,
- fields[this.date_stop].type
- );
- } else {
- data[this.date_stop] = data[this.date_start];
- }
- }
- if (this.date_delay && event_end) {
- const diff_seconds = Math.round(
- (event_end.getTime() - event_start.getTime()) / 1000
- );
- data[this.date_delay] = diff_seconds / 3600;
- }
- const grouped_field = this.renderer.last_group_bys[0];
- this._rpc({
- model: this.modelName,
- method: "fields_get",
- args: [grouped_field],
- context: this.getSession().user_context,
- }).then(async (fields_processed) => {
- if (
- this.renderer.last_group_bys &&
- this.renderer.last_group_bys instanceof Array &&
- fields_processed[grouped_field].type !== "many2many"
- ) {
- data[this.renderer.last_group_bys[0]] = group;
- }
-
- this.moveQueue.push({
- id: event.data.item.id,
- data: data,
- event: event,
- });
-
- this.debouncedInternalMove();
- });
- },
-
- /**
- * Write enqueued moves to Odoo. After all writes are finished it updates
- * the view once (prevents flickering of the view when multiple timeline items
- * are moved at once).
- *
- * @returns {jQuery.Deferred}
- */
- internalMove: function () {
- const queues = this.moveQueue.slice();
- this.moveQueue = [];
- const defers = [];
- for (const item of queues) {
- defers.push(
- this._rpc({
- model: this.model.modelName,
- method: "write",
- args: [[item.event.data.item.id], item.data],
- context: this.getSession().user_context,
- }).then(() => {
- item.event.data.callback(item.event.data.item);
- })
- );
- }
- return $.when.apply($, defers).done(() => {
- this.write_completed({
- adjust_window: false,
- });
- });
- },
-
- /**
- * Triggered when a timeline item gets removed from the view.
- * Requires user confirmation before it gets actually deleted.
- *
- * @private
- * @param {EventObject} event
- * @returns {jQuery.Deferred}
- */
- _onRemove: function (event) {
- var def = $.Deferred();
-
- Dialog.confirm(this, _t("Are you sure you want to delete this record?"), {
- title: _t("Warning"),
- confirm_callback: () => {
- this.remove_completed(event).then(def.resolve.bind(def));
- },
- cancel_callback: def.resolve.bind(def),
- });
-
- return def;
- },
-
- /**
- * Triggered when a timeline item gets added and opens a form view.
- *
- * @private
- * @param {EventObject} event
- * @returns {dialogs.FormViewDialog}
- */
- _onAdd: function (event) {
- const item = event.data.item;
- // Initialize default values for creation
- const default_context = {};
- default_context["default_".concat(this.date_start)] = item.start;
- if (this.date_delay) {
- default_context["default_".concat(this.date_delay)] = 1;
- }
- if (this.date_start) {
- default_context["default_".concat(this.date_start)] = moment(item.start)
- .utc()
- .format("YYYY-MM-DD HH:mm:ss");
- }
- if (this.date_stop && item.end) {
- default_context["default_".concat(this.date_stop)] = moment(item.end)
- .utc()
- .format("YYYY-MM-DD HH:mm:ss");
- }
- if (item.group > 0) {
- default_context["default_".concat(this.renderer.last_group_bys[0])] =
- item.group;
- }
- // Show popup
- new dialogs.FormViewDialog(this, {
- res_model: this.model.modelName,
- res_id: null,
- context: _.extend(default_context, this.context),
- view_id: Number(this.open_popup_action),
- on_saved: (record) => {
- this.create_completed([record.res_id]);
- },
- })
- .open()
- .on("closed", this, () => {
- event.data.callback();
- });
-
- return false;
- },
-
- /**
- * Triggered upon completion of a new record.
- * Updates the timeline view with the new record.
- *
- * @param {RecordId} id
- * @returns {jQuery.Deferred}
- */
- create_completed: function (id) {
- return this._rpc({
- model: this.model.modelName,
- method: "read",
- args: [id, this.model.fieldNames],
- context: this.context,
- }).then((records) => {
- var new_event = this.renderer.event_data_transform(records[0]);
- var items = this.renderer.timeline.itemsData;
- items.add(new_event);
- });
- },
-
- /**
- * Triggered upon completion of writing a record.
- * @param {ControllerOptions} options
- */
- write_completed: function (options) {
- const params = {
- domain: this.renderer.last_domains,
- context: this.context,
- groupBy: this.renderer.last_group_bys,
- };
- this.update(params, options);
- },
-
- /**
- * Triggered upon confirm of removing a record.
- * @param {EventObject} event
- * @returns {jQuery.Deferred}
- */
- remove_completed: function (event) {
- return this._rpc({
- model: this.modelName,
- method: "unlink",
- args: [[event.data.item.id]],
- context: this.getSession().user_context,
- }).then(() => {
- let unlink_index = false;
- for (var i = 0; i < this.model.data.data.length; i++) {
- if (this.model.data.data[i].id === event.data.item.id) {
- unlink_index = i;
- }
- }
- if (!isNaN(unlink_index)) {
- this.model.data.data.splice(unlink_index, 1);
- }
- event.data.callback(event.data.item);
- });
- },
- });
-
- return TimelineController;
-});
diff --git a/web_timeline/static/src/js/timeline_view.js b/web_timeline/static/src/js/timeline_view.js
index 82dcccd70..66acca98a 100644
--- a/web_timeline/static/src/js/timeline_view.js
+++ b/web_timeline/static/src/js/timeline_view.js
@@ -2,6 +2,7 @@
/* Odoo web_timeline
* Copyright 2015 ACSONE SA/NV
* Copyright 2016 Pedro M. Baeza
+ * Copyright 2023 Onestein - Anjeel Haria
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
odoo.define("web_timeline.TimelineView", function (require) {
@@ -23,7 +24,7 @@ odoo.define("web_timeline.TimelineView", function (require) {
var TimelineView = AbstractView.extend({
display_name: _lt("Timeline"),
- icon: "fa-tasks",
+ icon: "fa fa-tasks",
jsLibs: ["/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.js"],
cssLibs: ["/web_timeline/static/lib/vis-timeline/vis-timeline-graph2d.css"],
config: _.extend({}, AbstractView.prototype.config, {