Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 38 additions & 38 deletions web_timeline/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ render the timeline items. You have to name the template
'timeline-item'. These are the variables available in template
rendering:

- ``record``: to access the fields values selected in the timeline
definition.
- ``formatters``: used to format values (see available functions in
``@web/views/fields/formatters``).
- ``parsers``: used to parse values (see available functions in
``@web/views/fields/parsers``).
- ``record``: to access the fields values selected in the timeline
definition.
- ``formatters``: used to format values (see available functions in
``@web/views/fields/formatters``).
- ``parsers``: used to parse values (see available functions in
``@web/views/fields/parsers``).

You also need to declare the view in an action window of the involved
model.
Expand Down Expand Up @@ -213,7 +213,7 @@ Records are grouped in different blocks depending on the group by
criteria selected (if none is specified, then the default group by is
applied). Dragging a record from one block to another change the
corresponding field to the value that represents the block. You can also
click on the group name to edit the involved record directly.
double-click on the group name to edit the involved record directly.

Double-click on the record to edit it. Double-click in open area to
create a new record with the group and start date linked to the area you
Expand All @@ -223,20 +223,20 @@ create a new record with the dragged start and end date.
Known issues / Roadmap
======================

- Implement a more efficient way of refreshing timeline after a record
update;
- Make ``attrs`` attribute work;
- When grouping by m2m and more than one record is set, the timeline
item appears only on one group. Allow showing in both groups.
- When grouping by m2m and dragging for changing the time or the group,
the changes on the group will not be set, because it could make
disappear the records not related with the changes that we want to
make. When the item is showed in all groups change the value according
the group of the dragged item.
- When an item label does not fit in its date-range box: ✅ the label
correctly overflows the box; ✅ clicking anywhere on the label allows
moving the box; ❌ double-clicking the label outside of the box does
not open that item.
- Implement a more efficient way of refreshing timeline after a record
update;
- Make ``attrs`` attribute work;
- When grouping by m2m and more than one record is set, the timeline
item appears only on one group. Allow showing in both groups.
- When grouping by m2m and dragging for changing the time or the group,
the changes on the group will not be set, because it could make
disappear the records not related with the changes that we want to
make. When the item is showed in all groups change the value
according the group of the dragged item.
- When an item label does not fit in its date-range box: ✅ the label
correctly overflows the box; ✅ clicking anywhere on the label allows
moving the box; ❌ double-clicking the label outside of the box does
not open that item.

Bug Tracker
===========
Expand All @@ -263,28 +263,28 @@ Authors
Contributors
------------

- Laurent Mignon <laurent.mignon@acsone.eu>
- Adrien Peiffer <adrien.peiffer@acsone.eu>
- Leonardo Donelli <donelli@webmonks.it>
- Adrien Didenot <adrien.didenot@horanet.com>
- Thong Nguyen Van <thongnv@trobz.com>
- Murtaza Mithaiwala <mmithaiwala@opensourceintegrators.com>
- Ammar Officewala <aofficewala@opensourceintegrators.com>
- `Tecnativa <https://www.tecnativa.com>`__:
- Laurent Mignon <laurent.mignon@acsone.eu>
- Adrien Peiffer <adrien.peiffer@acsone.eu>
- Leonardo Donelli <donelli@webmonks.it>
- Adrien Didenot <adrien.didenot@horanet.com>
- Thong Nguyen Van <thongnv@trobz.com>
- Murtaza Mithaiwala <mmithaiwala@opensourceintegrators.com>
- Ammar Officewala <aofficewala@opensourceintegrators.com>
- `Tecnativa <https://www.tecnativa.com>`__:

- Pedro M. Baeza
- Alexandre Díaz
- César A. Sánchez
- Carlos López
- Pedro M. Baeza
- Alexandre Díaz
- César A. Sánchez
- Carlos López

- `Onestein <https://www.onestein.nl>`__:
- `Onestein <https://www.onestein.nl>`__:

- Dennis Sluijk <d.sluijk@onestein.nl>
- Anjeel Haria
- Dennis Sluijk <d.sluijk@onestein.nl>
- Anjeel Haria

- `XCG Consulting <https://xcg-consulting.fr>`__:
- `XCG Consulting <https://xcg-consulting.fr>`__:

- Houzéfa Abbasbhay
- Houzéfa Abbasbhay

- `PyTech <https://www.pytech.it>`__:

Expand Down
2 changes: 1 addition & 1 deletion web_timeline/readme/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Records are grouped in different blocks depending on the group by
criteria selected (if none is specified, then the default group by is
applied). Dragging a record from one block to another change the
corresponding field to the value that represents the block. You can also
click on the group name to edit the involved record directly.
double-click on the group name to edit the involved record directly.

Double-click on the record to edit it. Double-click in open area to
create a new record with the group and start date linked to the area you
Expand Down
6 changes: 3 additions & 3 deletions web_timeline/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ <h2><a class="toc-backref" href="#toc-entry-2">Usage</a></h2>
criteria selected (if none is specified, then the default group by is
applied). Dragging a record from one block to another change the
corresponding field to the value that represents the block. You can also
click on the group name to edit the involved record directly.</p>
double-click on the group name to edit the involved record directly.</p>
<p>Double-click on the record to edit it. Double-click in open area to
create a new record with the group and start date linked to the area you
clicked in. By holding the Ctrl key and dragging left to right, you can
Expand All @@ -604,8 +604,8 @@ <h2><a class="toc-backref" href="#toc-entry-3">Known issues / Roadmap</a></h2>
<li>When grouping by m2m and dragging for changing the time or the group,
the changes on the group will not be set, because it could make
disappear the records not related with the changes that we want to
make. When the item is showed in all groups change the value according
the group of the dragged item.</li>
make. When the item is showed in all groups change the value
according the group of the dragged item.</li>
<li>When an item label does not fit in its date-range box: ✅ the label
correctly overflows the box; ✅ clicking anywhere on the label allows
moving the box; ❌ double-clicking the label outside of the box does
Expand Down
56 changes: 36 additions & 20 deletions web_timeline/static/src/views/timeline/timeline_arch_parser.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class TimelineArchParser {
zoomKey: "ctrlKey",
},
};
const fieldNames = fields.display_name ? ["display_name"] : [];
const fieldNames = new Set(fields.display_name ? ["display_name"] : []);
visitXML(arch, (node) => {
switch (node.tagName) {
case "timeline": {
Expand Down Expand Up @@ -122,9 +122,7 @@ export class TimelineArchParser {
}
case "field": {
const fieldName = node.getAttribute("name");
if (!fieldNames.includes(fieldName)) {
fieldNames.push(fieldName);
}
fieldNames.add(fieldName);
break;
}
case "t": {
Expand All @@ -142,27 +140,45 @@ export class TimelineArchParser {
"default_group_by",
"progress",
"date_delay",
archInfo.default_group_by,
...archInfo.default_group_by.split(","),
];
fieldsToGather
.map((field) =>
field === "default_group_by"
? archInfo[field].split(",")
: archInfo[field]
)
.flat()
.filter(Boolean)
.forEach((field) => fieldNames.add(field));

for (const field of fieldsToGather) {
if (archInfo[field] && !fieldNames.includes(archInfo[field])) {
fieldNames.push(archInfo[field]);
}
}
for (const color of archInfo.colors) {
if (!fieldNames.includes(color.field)) {
fieldNames.push(color.field);
}
archInfo.colors
.map((color) => color.field)
.forEach((field) => fieldNames.add(field));

if (archInfo.dependency_arrow) {
fieldNames.add(archInfo.dependency_arrow);
}
archInfo.fieldNames = [...fieldNames];

fieldsToGather
.map((field) =>
field === "default_group_by"
? archInfo[field].split(",")
: archInfo[field]
)
.flat()
.filter(Boolean)
.forEach((field) => fieldNames.add(field));

archInfo.colors
.map((color) => color.field)
.forEach((field) => fieldNames.add(field));

if (
archInfo.dependency_arrow &&
!fieldNames.includes(archInfo.dependency_arrow)
) {
fieldNames.push(archInfo.dependency_arrow);
if (archInfo.dependency_arrow) {
fieldNames.add(archInfo.dependency_arrow);
}
archInfo.fieldNames = fieldNames;
archInfo.fieldNames = [...fieldNames];
return archInfo;
}
/**
Expand Down
134 changes: 113 additions & 21 deletions web_timeline/static/src/views/timeline/timeline_controller.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,36 @@ export class TimelineController extends Component {
* @param {EventObject} item
*/
_onGroupClick(item) {
const groupField = this.model.last_group_bys[0];
this.actionService.doAction({
type: "ir.actions.act_window",
res_model: this.model.fields[groupField].relation,
res_id: item.group,
views: [[false, "form"]],
view_mode: "form",
target: "new",
});
try {
const groupPathSegments = JSON.parse(item.group);
if (!Array.isArray(groupPathSegments) || groupPathSegments.length === 0)
return;

const lastSegment = groupPathSegments[groupPathSegments.length - 1];
const group_key = lastSegment.field;
const group_value = lastSegment.value;

if (
group_value === false ||
group_value === null ||
group_value === undefined
)
return;

const fieldInfo = this.model.fields[group_key];
if (!fieldInfo || !fieldInfo.relation) return;

this.actionService.doAction({
type: "ir.actions.act_window",
res_model: fieldInfo.relation,
res_id: parseInt(group_value, 10),
views: [[false, "form"]],
view_mode: "form",
target: "new",
});
} catch (e) {
console.error("Error parsing group JSON for click event:", item.group, e);
}
}

/**
Expand All @@ -80,7 +101,8 @@ export class TimelineController extends Component {
* @returns {jQuery.Deferred}
*/
_onItemDoubleClick(event) {
return this.openItem(event.item, false);
const item_id = event.item.split("_")[0];
return this.openItem(Number(item_id) || item_id, false);
}

/**
Expand Down Expand Up @@ -134,10 +156,6 @@ export class TimelineController extends Component {
_onMove(item, callback) {
const event_start = DateTime.fromJSDate(item.start);
const event_end = item.end ? DateTime.fromJSDate(item.end) : false;
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
Expand All @@ -157,12 +175,50 @@ export class TimelineController extends Component {
const diff = event_end.diff(event_start, "hours");
data[this.date_delay] = diff.hours;
}
const grouped_field = this.model.last_group_bys[0];
if (this.model.fields[grouped_field].type !== "many2many") {
data[grouped_field] = group;

// Parse the group JSON to extract field values for all group levels
if (item.group) {
try {
const groupPathSegments = JSON.parse(item.group);
if (Array.isArray(groupPathSegments)) {
for (const segment of groupPathSegments) {
// Skip m2m fields as they are complicated to handle
if (
this.model.fields[segment.field] &&
this.model.fields[segment.field].type === "many2many"
) {
continue;
}
// Skip date and datetime fields
if (
this.model.fields[segment.field] &&
(this.model.fields[segment.field].type === "date" ||
this.model.fields[segment.field].type === "datetime")
) {
continue;
}
if (
segment.value !== false &&
segment.value !== null &&
segment.value !== undefined
) {
data[segment.field] = segment.value;
} else {
data[segment.field] = false;
}
}
}
} catch (e) {
console.error(
"Error parsing group JSON for move event:",
item.group,
e
);
}
}

this.moveQueue.push({
id: item.id,
id: item.record_id,
data,
item,
callback,
Expand Down Expand Up @@ -202,7 +258,8 @@ export class TimelineController extends Component {
confirmLabel: _t("Confirm"),
cancelLabel: _t("Discard"),
confirm: async () => {
await this.model.remove_completed(item);
// Use record_id for deletion, not the composite id
await this.model.remove_completed({...item, id: item.record_id});
callback(item);
},
cancel: () => {
Expand Down Expand Up @@ -242,8 +299,43 @@ export class TimelineController extends Component {
const diff = item_end.diff(item_start, "hours");
context[`default_${this.date_delay}`] = diff.hours;
}
if (item.group > 0) {
context[`default_${this.model.last_group_bys[0]}`] = item.group;
if (item.group) {
try {
const groupPathSegments = JSON.parse(item.group);
if (Array.isArray(groupPathSegments)) {
groupPathSegments.forEach((segment) => {
// Set m2m fields using command format [(6, 0, [id])]
if (
this.model.fields[segment.field] &&
this.model.fields[segment.field].type === "many2many"
) {
if (
segment.value !== false &&
segment.value !== null &&
segment.value !== undefined
) {
context[`default_${segment.field}`] = [
[6, 0, [segment.value]],
];
}
return;
}
if (
segment.value !== false &&
segment.value !== null &&
segment.value !== undefined
) {
context[`default_${segment.field}`] = Number.isInteger(
segment.value
)
? segment.value
: segment.value;
}
});
}
} catch (e) {
console.error("Error parsing group JSON for add event:", item.group, e);
}
}
// Show popup
this.dialogService.add(
Expand Down
Loading
Loading