diff --git a/addons/project/static/src/views/fields/project_task_priority_switch_field.js b/addons/project/static/src/views/fields/project_task_priority_switch_field.js
new file mode 100644
index 00000000000..c6c7acaa694
--- /dev/null
+++ b/addons/project/static/src/views/fields/project_task_priority_switch_field.js
@@ -0,0 +1,32 @@
+/** @odoo-module **/
+
+import { sprintf } from "@web/core/utils/strings";
+import { registry } from "@web/core/registry";
+import { PriorityField } from "@web/views/fields/priority/priority_field";
+import { useCommand } from "@web/core/commands/command_hook";
+import { useState } from "@odoo/owl";
+
+export class PrioritySwitchField extends PriorityField {
+ setup() {
+ this.state = useState({
+ index: -1,
+ });
+ if (this.props.record.activeFields[this.props.name].viewType !== "form") {
+ return;
+ }
+
+ for (const [id, name] of this.options) {
+ useCommand(
+ sprintf(this.env._t("Set priority as %s"), name),
+ () => this.props.update(id),
+ {
+ category: "smart_action",
+ hotkey: "alt+r",
+ isAvailable: () => this.props.value !== id,
+ }
+ );
+ }
+ }
+}
+
+registry.category("fields").add("priority_switch", PrioritySwitchField);
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 01bc0cbbb0a..b4ad612bb87 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -1090,7 +1090,7 @@
-
+
Edit recurring task
@@ -1151,7 +1151,7 @@
-
+
diff --git a/addons/web/static/src/views/fields/state_selection/state_selection_field.js b/addons/web/static/src/views/fields/state_selection/state_selection_field.js
index 1f98939b57d..fad3364c409 100644
--- a/addons/web/static/src/views/fields/state_selection/state_selection_field.js
+++ b/addons/web/static/src/views/fields/state_selection/state_selection_field.js
@@ -4,42 +4,34 @@ import { useCommand } from "@web/core/commands/command_hook";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { registry } from "@web/core/registry";
+import { sprintf } from "@web/core/utils/strings";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "../standard_field_props";
import { formatSelection } from "../formatters";
-
import { Component } from "@odoo/owl";
export class StateSelectionField extends Component {
setup() {
- if (this.props.record.activeFields[this.props.name].viewType === "form") {
- const commandName = this.env._t("Set kanban state...");
- useCommand(
- commandName,
- () => {
- return {
- placeholder: commandName,
- providers: [
- {
- provide: () =>
- this.options.map((value) => ({
- name: value[1],
- action: () => {
- this.props.update(value[0]);
- },
- })),
- },
- ],
- };
- },
- { category: "smart_action", hotkey: "alt+shift+r" }
- );
- }
this.colorPrefix = "o_status_";
this.colors = {
blocked: "red",
done: "green",
};
+ if (this.props.record.activeFields[this.props.name].viewType !== "form") {
+ return;
+ }
+ const hotkeys = ["D", "F", "G"];
+ for (const [index, [value, label]] of this.options.entries()) {
+ useCommand(
+ sprintf(this.env._t("Set kanban state as %s"), label),
+ () => this.props.update(value),
+ {
+ category: "smart_action",
+ hotkey: "alt+" + hotkeys[index],
+ isAvailable: () => this.props.value !== value,
+ }
+ );
+ }
}
get options() {
return this.props.record.fields[this.props.name].selection.map(([state, label]) => {
diff --git a/addons/web/static/src/views/fields/statusbar/statusbar_field.js b/addons/web/static/src/views/fields/statusbar/statusbar_field.js
index 585b6ad41c7..f2f90d9f02a 100644
--- a/addons/web/static/src/views/fields/statusbar/statusbar_field.js
+++ b/addons/web/static/src/views/fields/statusbar/statusbar_field.js
@@ -9,7 +9,6 @@ import { escape, sprintf } from "@web/core/utils/strings";
import { Domain } from "@web/core/domain";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "../standard_field_props";
-
import { Component } from "@odoo/owl";
export class StatusBarField extends Component {
@@ -27,7 +26,7 @@ export class StatusBarField extends Component {
providers: [
{
provide: () =>
- this.computeItems().unfolded.map((value) => ({
+ this.computeItems(false).map((value) => ({
name: value.name,
action: () => {
this.selectItem(value);
@@ -39,11 +38,39 @@ export class StatusBarField extends Component {
},
{ category: "smart_action", hotkey: "alt+shift+x" }
);
+ useCommand(
+ sprintf(this.env._t(`Move to next %s`), this.props.displayName),
+ () => {
+ const options = this.computeItems(false);
+ const nextOption =
+ options[
+ options.findIndex(
+ (option) =>
+ option.id ===
+ (this.type === "many2one"
+ ? this.props.value[0]
+ : this.props.value)
+ ) + 1
+ ];
+ this.selectItem(nextOption);
+ },
+ {
+ category: "smart_action",
+ hotkey: "alt+x",
+ isAvailable: () => {
+ const options = this.computeItems(false);
+ return (
+ options[options.length - 1].id !==
+ (this.type === "many2one" ? this.props.value[0] : this.props.value)
+ );
+ },
+ }
+ );
}
}
get currentName() {
- switch (this.props.record.fields[this.props.name].type) {
+ switch (this.type) {
case "many2one": {
const item = this.options.find((item) => this.props.value && item.id === this.props.value[0]);
return item ? item.display_name : "";
@@ -56,7 +83,7 @@ export class StatusBarField extends Component {
throw new Error("Unsupported field type for StatusBarField");
}
get options() {
- switch (this.props.record.fields[this.props.name].type) {
+ switch (this.type) {
case "many2one":
return this.props.record.preloadedData[this.props.name];
case "selection":
@@ -66,6 +93,10 @@ export class StatusBarField extends Component {
}
}
+ get type() {
+ return this.props.record.fields[this.props.name].type;
+ }
+
getDropdownItemClassNames(item) {
const classNames = [
"btn",
@@ -110,13 +141,16 @@ export class StatusBarField extends Component {
}));
}
- computeItems() {
+ computeItems(grouped = true) {
let items = null;
if (this.props.type === "many2one") {
items = this.getVisibleMany2Ones();
} else {
items = this.getVisibleSelection();
}
+ if (!grouped) {
+ return items;
+ }
if (this.env.isSmall) {
return {
diff --git a/addons/web/static/tests/views/fields/state_selection_field_tests.js b/addons/web/static/tests/views/fields/state_selection_field_tests.js
index 831c8535552..ceb0647f93f 100644
--- a/addons/web/static/tests/views/fields/state_selection_field_tests.js
+++ b/addons/web/static/tests/views/fields/state_selection_field_tests.js
@@ -431,7 +431,7 @@ QUnit.module("Fields", (hooks) => {
});
QUnit.test(
- 'StateSelectionField edited by the smart action "Set kanban state..."',
+ 'StateSelectionField edited by the smart actions "Set kanban state as "',
async function (assert) {
await makeView({
type: "form",
@@ -448,20 +448,23 @@ QUnit.module("Fields", (hooks) => {
triggerHotkey("control+k");
await nextTick();
- const idx = [...target.querySelectorAll(".o_command")]
- .map((el) => el.textContent)
- .indexOf("Set kanban state...ALT + SHIFT + R");
+ var commandTexts = [...target.querySelectorAll(".o_command")].map(
+ (el) => el.textContent
+ );
+ assert.ok(commandTexts.includes("Set kanban state as NormalALT + D"));
+ const idx = commandTexts.indexOf("Set kanban state as DoneALT + G");
assert.ok(idx >= 0);
await click([...target.querySelectorAll(".o_command")][idx]);
await nextTick();
- assert.deepEqual(
- [...target.querySelectorAll(".o_command")].map((el) => el.textContent),
- ["Normal", "Blocked", "Done"]
- );
- await click(target, "#o_command_2");
- await nextTick();
assert.containsOnce(target, ".o_status_green");
+
+ triggerHotkey("control+k");
+ await nextTick();
+ commandTexts = [...target.querySelectorAll(".o_command")].map((el) => el.textContent);
+ assert.ok(commandTexts.includes("Set kanban state as NormalALT + D"));
+ assert.ok(commandTexts.includes("Set kanban state as BlockedALT + F"));
+ assert.notOk(commandTexts.includes("Set kanban state as DoneALT + G"));
}
);