diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index ea6d9ca9248..e0c820458f9 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -70,6 +70,7 @@ 'project/static/src/components/**/*', 'project/static/src/views/**/*', 'project/static/src/js/project_graph_view.js', + 'project/static/src/js/project_pivot_model.js', 'project/static/src/js/project_pivot_view.js', 'project/static/src/js/project_rating_graph_view.js', 'project/static/src/js/project_rating_pivot_view.js', diff --git a/addons/project/static/src/js/project_pivot_model.js b/addons/project/static/src/js/project_pivot_model.js new file mode 100644 index 00000000000..3cb7b55320a --- /dev/null +++ b/addons/project/static/src/js/project_pivot_model.js @@ -0,0 +1,18 @@ +/** @odoo-module **/ + +import { PivotModel } from "@web/views/pivot/pivot_model"; + +export class ProjectTaskPivotModel extends PivotModel { + /** + * @override + */ + _getEmptyGroupLabel(fieldName) { + if (fieldName === "project_id") { + return this.env._t("Private"); + } else if (fieldName === "user_ids") { + return this.env._t("Unassigned"); + } else { + return super._getEmptyGroupLabel(fieldName); + } + } +} diff --git a/addons/project/static/src/js/project_pivot_view.js b/addons/project/static/src/js/project_pivot_view.js index e821b6fe0c2..ce1e1954e5c 100644 --- a/addons/project/static/src/js/project_pivot_view.js +++ b/addons/project/static/src/js/project_pivot_view.js @@ -3,7 +3,12 @@ import { ProjectControlPanel } from "@project/components/project_control_panel/project_control_panel"; import { registry } from "@web/core/registry"; import { pivotView } from "@web/views/pivot/pivot_view"; +import { ProjectTaskPivotModel } from "./project_pivot_model"; -const projectPivotView = {...pivotView, ControlPanel: ProjectControlPanel}; +const projectPivotView = { + ...pivotView, + ControlPanel: ProjectControlPanel, + Model: ProjectTaskPivotModel, +}; registry.category("views").add("project_pivot", projectPivotView); diff --git a/addons/project/static/src/views/project_task_kanban/project_task_kanban_header.js b/addons/project/static/src/views/project_task_kanban/project_task_kanban_header.js index 1bae963c11e..e3e84099a2f 100644 --- a/addons/project/static/src/views/project_task_kanban/project_task_kanban_header.js +++ b/addons/project/static/src/views/project_task_kanban/project_task_kanban_header.js @@ -63,4 +63,17 @@ export class ProjectTaskKanbanHeader extends KanbanHeader { canDeleteGroup(group) { return super.canDeleteGroup(group) && (!this.props.list.isGroupedByStage || this.isProjectManager) || this.props.list.isGroupedByPersonalStages; } + + /** + * @override + */ + _getEmptyGroupLabel(fieldName) { + if (fieldName === "project_id") { + return this.env._t("🔒 Private"); + } else if (fieldName === "user_ids") { + return this.env._t("👤 Unassigned"); + } else { + return super._getEmptyGroupLabel(fieldName); + } + } } diff --git a/addons/project/static/src/views/project_task_list/project_task_list_renderer.js b/addons/project/static/src/views/project_task_list/project_task_list_renderer.js index 8ff3e9d0552..84f3ac19e76 100644 --- a/addons/project/static/src/views/project_task_list/project_task_list_renderer.js +++ b/addons/project/static/src/views/project_task_list/project_task_list_renderer.js @@ -15,4 +15,15 @@ export class ProjectTaskListRenderer extends ListRenderer { } return readonly || super.isCellReadonly(column, record); } + + getGroupDisplayName(group) { + const { _t } = this.env; + if (group.groupByField.name === "project_id" && !group.value) { + return _t("🔒 Private"); + } else if (group.groupByField.name === "user_ids" && !group.value) { + return _t("👤 Unassigned"); + } else { + return super.getGroupDisplayName(group); + } + } } diff --git a/addons/project/static/tests/project_task_groupby_tests.js b/addons/project/static/tests/project_task_groupby_tests.js new file mode 100644 index 00000000000..79d8d643260 --- /dev/null +++ b/addons/project/static/tests/project_task_groupby_tests.js @@ -0,0 +1,95 @@ +/** @odoo-module */ + +import { getFixture } from "@web/../tests/helpers/utils"; +import { setupViewRegistries } from "@web/../tests/views/helpers"; +import { start, startServer } from "@mail/../tests/helpers/test_utils"; + +let target; + +QUnit.module("Project", (hooks) => { + hooks.beforeEach(async () => { + const pyEnv = await startServer(); + pyEnv.mockServer.models['project.task'] = { + fields: { + id: { string: "Id", type: "integer" }, + display_name: { string: "Name", type: "char" }, + project_id: { + string: "Project", + type: "many2one", + relation: "project.project", + }, + }, + records: [{ + id: 1, + display_name: "My task", + project_id: false, + }], + }; + target = getFixture(); + setupViewRegistries(); + }); + + QUnit.test("Test group label for empty project in tree", async function (assert) { + assert.expect(1); + + const views = { + "project.task,false,list": + ``, + }; + const { openView } = await start({ + serverData: { views }, + }); + await openView({ + res_model: "project.task", + views: [[false, "tree"]], + context: { group_by: ["project_id"] }, + }); + + assert.strictEqual(target.querySelector(".o_group_name").innerText, "🔒 Private (1)"); + }); + + QUnit.test("Test group label for empty project in kanban", async function (assert) { + assert.expect(1); + + const views = { + "project.task,false,kanban": + ` + + + + `, + }; + const { openView } = await start({ + serverData: { views }, + }); + await openView({ + res_model: "project.task", + views: [[false, "kanban"]], + }); + + assert.strictEqual(target.querySelector(".o_column_title").innerText, "🔒 Private (1)"); + }); + + QUnit.test("Test group label for empty project in pivot", async function (assert) { + assert.expect(1); + + const views = { + "project.task,false,pivot": + ` + + `, + }; + const { openView } = await start({ + serverData: { views }, + }); + await openView({ + res_model: "project.task", + views: [[false, "pivot"]], + }); + + assert.strictEqual( + target.querySelector("tr:nth-of-type(2) .o_pivot_header_cell_closed").innerText, + "Private", + ); + }); +}); diff --git a/addons/project/views/project_task_views.xml b/addons/project/views/project_task_views.xml index 8600137dcf5..0a02f6a3913 100644 --- a/addons/project/views/project_task_views.xml +++ b/addons/project/views/project_task_views.xml @@ -19,6 +19,7 @@ + diff --git a/addons/web/static/src/views/kanban/kanban_header.js b/addons/web/static/src/views/kanban/kanban_header.js index 597201dbfd7..6d20a40138a 100644 --- a/addons/web/static/src/views/kanban/kanban_header.js +++ b/addons/web/static/src/views/kanban/kanban_header.js @@ -75,6 +75,10 @@ export class KanbanHeader extends Component { return this.props.group; } + _getEmptyGroupLabel(fieldName) { + return this.env._t("None"); + } + get groupName() { const { groupByField, displayName } = this.group; let name = displayName; @@ -87,7 +91,7 @@ export class KanbanHeader extends Component { groupByField.type === "datetime" || isNull(name) ) { - name = this.env._t("None"); + name = this._getEmptyGroupLabel(groupByField.name); } } return name; diff --git a/addons/web/static/src/views/pivot/pivot_model.js b/addons/web/static/src/views/pivot/pivot_model.js index b833589624b..5e68fe5e31e 100644 --- a/addons/web/static/src/views/pivot/pivot_model.js +++ b/addons/web/static/src/views/pivot/pivot_model.js @@ -1525,6 +1525,11 @@ export class PivotModel extends Model { } }); } + + _getEmptyGroupLabel(fieldName) { + return this.env._t("None"); + } + /** * Extract from a groupBy value a label. * @@ -1549,7 +1554,7 @@ export class PivotModel extends Model { : this.env._t("No"); } if (value === false) { - return this.env._t("None"); + return this._getEmptyGroupLabel(groupBy); } if (value instanceof Array) { return this._getNumberedLabel(value, fieldName, config);