[IMP] project: Improve ux

Before this commit:
- project.task > list view: Currently, the progress is displayed
in purple if less than 100% and in red if above. The two colors
don't contrast a lot, making it hard to read. In addition, it
is not coherent with the color we usually apply for the remaining
hours in the rest of the app.
- onbording tour: the manual saving step is no longer necessary
as records are now automatically saved. In addition, showing it
doesn't provide a wow effect and is an extra opportunity to lose
people in the process

After this commit:
- the following color code was applied for the progress bar:
*if progress < 80 -> green
*if progress is between 80 and 100 included -> orange
*if progress > 100 -> red

- step removed.

task-3475170

closes odoo/odoo#132854

Related: odoo/enterprise#46162
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
This commit is contained in:
Abderraouf Ghrissi (abgh)
2023-10-18 18:27:25 +00:00
parent 5fa618cb99
commit b9532e7549
8 changed files with 52 additions and 13 deletions
@@ -0,0 +1,21 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { progressBarField, ProgressBarField } from "@web/views/fields/progress_bar/progress_bar_field";
export class ProjectTaskProgressBarField extends ProgressBarField {
get progressBarColorClass() {
if (this.currentValue > this.maxValue) {
return super.progressBarColorClass;
}
return this.currentValue < 80 ? "bg-success" : "bg-warning";
}
}
export const projectTaskProgressBarField = {
...progressBarField,
component: ProjectTaskProgressBarField,
};
registry.category("fields").add("project_task_progressbar", projectTaskProgressBarField);
@@ -35,11 +35,12 @@ export const getServerData = () => JSON.parse(JSON.stringify({
fields: {
name: { string: "Name", type: "string" },
project_id: { string: "Project", type: "many2one", relation: "project.project" },
progress: { string: "progress", type: "float" },
},
records: [
{ id: 1, name: "Task 1\u00A0AdditionalInfo", project_id: 1 },
{ id: 2, name: "Task 2\u00A0AdditionalInfo", project_id: 1 },
{ id: 3, name: "Task 3\u00A0AdditionalInfo", project_id: 1 },
{ id: 1, name: "Task 1\u00A0AdditionalInfo", project_id: 1, progress: 50 },
{ id: 2, name: "Task 2\u00A0AdditionalInfo", project_id: 1, progress: 80 },
{ id: 3, name: "Task 3\u00A0AdditionalInfo", project_id: 1, progress: 104 },
],
},
},
@@ -64,4 +64,22 @@ QUnit.module("hr_timesheet", (hooks) => {
assert.containsN(firstRow, '.o_list_many2one[name=task_id] .dropdown ul li:contains("AdditionalInfo")', 3);
});
QUnit.test("project task progress bar color", async function (assert) {
await makeView({
serverData,
type: "list",
resModel: "project.task",
arch: `
<tree>
<field name="name"/>
<field name="project_id"/>
<field name="progress" widget="project_task_progressbar" options="{'overflow_class': 'bg-danger'}"/>
</tree>
`,
});
assert.containsOnce(target, "div.o_progressbar .bg-success", "Task 1 having progress = 50 < 80 => green color")
assert.containsOnce(target, "div.o_progressbar .bg-warning", "Task 2 having progress = 80 >= 80 => orange color")
assert.containsOnce(target, "div.o_progressbar .bg-success", "Task 3 having progress = 101 > 100 => red color")
});
});
@@ -12,7 +12,7 @@
<field name="subtask_effective_hours" string="Sub-tasks Hours Spent" widget="timesheet_uom" sum="Sub-tasks Hours Spent" optional="hide"/>
<field name="total_hours_spent" string="Total Hours" widget="timesheet_uom" sum="Total Hours" optional="hide"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100"/>
<field name="progress" widget="progressbar" optional="hide" options="{'overflow_class': 'bg-danger'}"/>
<field name="progress" widget="project_task_progressbar" optional="hide" options="{'overflow_class': 'bg-danger'}"/>
</xpath>
<xpath expr="//field[@name='partner_id']" position="after">
<field name="encode_uom_in_days" invisible="1"/>
@@ -12,7 +12,7 @@
<field name="subtask_effective_hours" widget="timesheet_uom" sum="Sub-tasks Hours Spent" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="total_hours_spent" widget="timesheet_uom" sum="Total Hours" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="progress" widget="progressbar" optional="hide" options="{'overflow_class': 'bg-danger'}" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="progress" widget="project_task_progressbar" optional="hide" options="{'overflow_class': 'bg-danger'}" groups="hr_timesheet.group_hr_timesheet_user"/>
</xpath>
<xpath expr="//label[@for='date_deadline']" position="before">
<field name="encode_uom_in_days" invisible="1"/>
@@ -148,7 +148,7 @@
<field name="subtask_effective_hours" widget="timesheet_uom" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="total_hours_spent" widget="timesheet_uom" optional="hide" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="progress" widget="progressbar" optional="hide" options="{'overflow_class': 'bg-danger'}" groups="hr_timesheet.group_hr_timesheet_user"/>
<field name="progress" widget="project_task_progressbar" optional="hide" options="{'overflow_class': 'bg-danger'}" groups="hr_timesheet.group_hr_timesheet_user"/>
</xpath>
</field>
</record>
@@ -166,7 +166,7 @@
<field name="subtask_effective_hours" widget="timesheet_uom" sum="Sub-Tasks Total Effective Hours" optional="hide"/>
<field name="total_hours_spent" widget="timesheet_uom" sum="Total Hours" optional="hide"/>
<field name="remaining_hours" widget="timesheet_uom" sum="Total Remaining Hours" optional="hide" decoration-danger="progress &gt;= 100" decoration-warning="progress &gt;= 80 and progress &lt; 100" invisible="allocated_hours == 0"/>
<field name="progress" widget="progressbar" avg="Average of Progress" optional="show" groups="hr_timesheet.group_hr_timesheet_user" invisible="allocated_hours == 0" options="{'overflow_class': 'bg-danger'}" />
<field name="progress" widget="project_task_progressbar" avg="Average of Progress" optional="show" groups="hr_timesheet.group_hr_timesheet_user" invisible="allocated_hours == 0" options="{'overflow_class': 'bg-danger'}" />
</field>
</field>
</record>
@@ -121,11 +121,6 @@ registry.category("web_tour.tours").add('project_tour', {
}, {
trigger: ".ui-autocomplete > li > a:not(:has(i.fa))",
auto: true,
}, {
trigger: ".o_form_button_save",
extra_trigger: '.o_form_project_tasks .o_form_dirty',
content: markup(_t("You have unsaved changes - no worries! Odoo will automatically save it as you navigate.<br/> You can discard these changes from here or manually save your task.<br/>Let's save it manually.")),
position: "bottom",
}, {
trigger: ".breadcrumb-item:not(.active):last",
extra_trigger: '.o_form_project_tasks',
@@ -54,6 +54,10 @@ export class ProgressBarField extends Component {
return this.props.record.data[this.maxValueField] || 100;
}
get progressBarColorClass() {
return this.currentValue > this.maxValue ? this.props.overflowClass : "bg-primary";
}
formatCurrentValue(humanReadable = !this.state.isEditing) {
const formatter = formatters.get(Number.isInteger(this.currentValue) ? "integer" : "float");
return formatter(this.currentValue, { humanReadable });
@@ -5,7 +5,7 @@
<div class="o_progressbar w-100 d-flex align-items-center" t-ref="numpadDecimal">
<div t-if="props.title" class="o_progressbar_title text-nowrap pe-1"><t t-esc="props.title"/></div>
<div class="o_progress align-middle overflow-hidden" aria-valuemin="0" t-att-aria-valuemax="this.maxValue" t-att-aria-valuenow="this.currentValue">
<div t-attf-class="{{ this.currentValue > this.maxValue ? props.overflowClass : 'bg-primary' }} h-100" t-att-style="'width: min(' + 100 * this.currentValue / this.maxValue + '%, 100%)'"></div>
<div t-attf-class="{{ progressBarColorClass }} h-100" t-att-style="'width: min(' + 100 * this.currentValue / this.maxValue + '%, 100%)'"></div>
</div>
<div class="o_progressbar_value d-flex">
<t t-if="isPercentage">