[IMP] project: improve globally the UX

The UX of the 4 main views (kanban, form, list, portal) of the project app has been globally improved.
In particular, different icons color/style were changed along with several strings, and the order of a few elements was revised to improve readability.

task-2508723
See odoo/enterprise#18387

closes odoo/odoo#71011

Related: odoo/upgrade#2506
Signed-off-by: LTU-Odoo <IT-Ideas@users.noreply.github.com>
This commit is contained in:
Cedric Prieels (cpr)
2021-06-28 12:58:25 +00:00
parent c6e40ec0b1
commit 0ed38652a4
12 changed files with 116 additions and 151 deletions
+4 -4
View File
@@ -24,7 +24,7 @@
<field name="inherit_id" ref="project.project_project_view_form_simplified"/>
<field name="priority">24</field>
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="user_id" position="before">
<field name="allow_timesheets"/>
</field>
</field>
@@ -36,8 +36,8 @@
<field name="inherit_id" ref="project.edit_project"/>
<field name="priority">24</field>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" name="%(act_hr_timesheet_line_by_project)d" type="action" icon="fa-calendar" attrs="{'invisible': [('allow_timesheets', '=', False)]}" groups="hr_timesheet.group_hr_timesheet_user">
<xpath expr="//button[@name='%(project.project_milestone_all)d']" position="after">
<button class="oe_stat_button" name="%(act_hr_timesheet_line_by_project)d" type="action" icon="fa-clock-o" attrs="{'invisible': [('allow_timesheets', '=', False)]}" groups="hr_timesheet.group_hr_timesheet_user">
<div class="o_field_widget o_stat_info">
<div class="oe_inline">
<span class="o_stat_value mr-1">
@@ -50,7 +50,7 @@
<span class="o_stat_text">Recorded</span>
</div>
</button>
</div>
</xpath>
<xpath expr="//div[@id='rating_settings']" position="before">
<div class="col-lg-6 o_setting_box" id="timesheet_settings">
<div class="o_setting_left_pane">
-1
View File
@@ -15,7 +15,6 @@ This module adds a PAD in all project form views.
'data': [
'views/res_config_settings_views.xml',
'views/project_views.xml',
'views/project_portal_templates.xml',
],
'auto_install': True,
'assets': {
-24
View File
@@ -11,10 +11,6 @@ class ProjectTask(models.Model):
description_pad = fields.Char('Pad URL', pad_content_field='description', copy=False)
use_pad = fields.Boolean(related="project_id.use_pads", string="Use collaborative pad", readonly=True)
pad_availability = fields.Selection(
related="project_id.pad_availability",
string="Availability of collaborative pads",
readonly=True)
@api.onchange('use_pad')
def _onchange_use_pads(self):
@@ -36,14 +32,6 @@ class ProjectTask(models.Model):
self = self.with_context(pad_no_create=True)
return super(ProjectTask, self).create(vals)
def _use_portal_pad(self):
"""
Indicates if the task configuration requires to provide
an access to a portal pad.
"""
self.ensure_one()
return self.use_pad and self.pad_availability == 'portal'
def _get_pad_content(self):
"""
Gets the content of the pad used to edit the task description
@@ -61,15 +49,3 @@ class ProjectProject(models.Model):
description_pad = fields.Char('Pad URL', pad_content_field='description', copy=False)
use_pads = fields.Boolean("Use collaborative pads", default=True,
help="Use collaborative pad for the tasks on this project.")
pad_availability = fields.Selection([
('internal', 'Internal Users'),
('portal', 'Internal Users & Portal Users')
], compute='_compute_pad_availability', store=True, readonly=False,
string='Availability of collaborative pads', required=True, default='internal')
@api.depends('use_pads', 'privacy_visibility')
def _compute_pad_availability(self):
for project in self:
if project.privacy_visibility != 'portal' or not project.use_pads:
project.pad_availability = 'internal'
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="portal_my_task" inherit_id="project.portal_my_task" priority="40">
<!-- add a button to the right of the 'description' title according to the mode (edit/read) -->
<xpath expr="//div[@t-if='task.description']//div[hasclass('d-flex')]" position="inside">
<t t-if="task._use_portal_pad()">
<t t-if="request.params.get('edit')">
<a role="button" class="btn btn-primary btn-sm ml-auto" t-attf-href="/my/task/#{task.id}">Save</a>
</t>
<t t-else="">
<a role="button" class="btn btn-primary btn-sm ml-auto" t-attf-href="/my/task/#{task.id}?edit=1">Edit</a>
</t>
</t>
</xpath>
<!-- show the description (read mode) or a pad (edit mode) -->
<xpath expr="//div[@t-field='task.description']" position="replace">
<t t-if="task._use_portal_pad()">
<t t-if="request.params.get('edit')">
<div class="o_pad_project_container">
<iframe width="100%" height="100%" frameborder="0" t-att-src="task.description_pad + '?showChat=false&amp;userName=' + request.env.user.name"/>
</div>
</t>
<t t-else="">
<div class="py-1 px-2 bg-100 small" t-esc="task._get_pad_content()"/>
</t>
</t>
<t t-else="">
<t>$0</t>
</t>
</xpath>
</template>
</odoo>
@@ -31,12 +31,6 @@
Edit tasks' description collaboratively in real time.<br/>
See each author's text in a distinct color.
</div>
<div class="content-group" attrs="{'invisible': ['|', ('use_pads', '=', False), ('privacy_visibility', '!=', 'portal')]}">
<div class="mt16 row">
<label for="pad_availability" string="Available to:" class="col-3 col-lg-3 o_light_label"/>
<field name="pad_availability" widget="radio"/>
</div>
</div>
</div>
</div>
</xpath>
+12 -3
View File
@@ -210,18 +210,19 @@ class Project(models.Model):
alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict", required=True,
help="Internal email associated with this project. Incoming emails are automatically synchronized "
"with Tasks (or optionally Issues if the Issue Tracker module is installed).")
alias_value = fields.Char(string='Alias email', compute='_compute_alias_value')
privacy_visibility = fields.Selection([
('followers', 'Invited employees'),
('employees', 'All employees'),
('portal', 'Portal users and all employees'),
('portal', 'Invited portal users and all employees'),
],
string='Visibility', required=True,
default='portal',
help="Defines the visibility of the tasks of the project:\n"
"- Invited employees: employees may only see the followed project and tasks.\n"
"- All employees: employees may see all project and tasks.\n"
"- Portal users and all employees: employees may see everything."
" Portal users may see project and tasks followed by.\n"
"- Invited portal users and all employees: employees may see everything."
" Invited portal users may see project and tasks followed by.\n"
" them or by someone of their company.")
doc_count = fields.Integer(compute='_compute_attached_docs_count', string="Number of documents attached")
date_start = fields.Date(string='Start Date')
@@ -330,6 +331,14 @@ class Project(models.Model):
for project in self:
project.milestone_count = mapped_count.get(project.id, 0)
@api.depends('alias_name', 'alias_domain')
def _compute_alias_value(self):
for project in self:
if not project.alias_name or not project.alias_domain:
project.alias_value = ''
else:
project.alias_value = "%s@%s" % (project.alias_name, project.alias_domain)
@api.model
def _map_tasks_default_valeus(self, task, project):
""" get the default value for the copied task on project duplication """
@@ -15,6 +15,11 @@
padding: 0 0 0 0;
margin: 0 5px;
&:first-child {
margin: 0 5px 0 0;
padding-left: 12px
}
.o_value {
font-weight: 800;
}
@@ -46,6 +51,15 @@
top: 15%;
}
}
.o_kanban_record {
div[name="is_favorite"] .fa-star-o {
visibility: hidden;
}
&:hover div[name="is_favorite"] .fa-star-o {
visibility: visible;
}
}
}
.o_dow_widget {
@@ -87,25 +87,29 @@
</t>
<t t-set="card_body">
<div class="row">
<div t-if="project.partner_id" class="col-12 col-md-6 mb-2 mb-md-0">
<h6>Customer</h6>
<div class="row">
<div class="col flex-grow-0 pr-3">
<img class="rounded-circle mt-1 o_portal_contact_img" t-att-src="image_data_uri(project.partner_id.avatar_1024)" alt="Contact"/>
</div>
<div class="col pl-sm-0">
<address t-field="project.partner_id" t-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'/>
</div>
</div>
</div>
<div t-if="project.user_id" class="col-12 col-md-6">
<h6>Project Manager</h6>
<h6><strong> Project Manager </strong></h6>
<div class="row">
<div class="col flex-grow-0 pr-3">
<img class="rounded-circle mt-1 o_portal_contact_img" t-att-src="image_data_uri(project.user_id.avatar_1024)" alt="Contact"/>
</div>
<div class="col pl-sm-0">
<address t-field="project.user_id" t-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'/>
<div t-field="project.user_id" t-options='{"widget": "contact", "fields": ["name"]}'/>
<a t-attf-href="mailto:{{project.user_id.email}}" t-if="project.user_id.email"><div t-field="project.user_id" t-options='{"widget": "contact", "fields": ["email"]}'/></a>
<a t-attf-href="tel:{{project.user_id.phone}}" t-if="project.user_id.phone"><div t-field="project.user_id" t-options='{"widget": "contact", "fields": ["phone"]}'/></a>
</div>
</div>
</div>
<div t-if="project.partner_id" class="col-12 col-md-6 mb-2 mb-md-0">
<h6><strong> Customer </strong></h6>
<div class="row">
<div class="col flex-grow-0 pr-3">
<img class="rounded-circle mt-1 o_portal_contact_img" t-att-src="image_data_uri(project.partner_id.avatar_1024)" alt="Contact"/>
</div>
<div class="col pl-sm-0">
<div t-field="project.partner_id" t-options='{"widget": "contact", "fields": ["name"]}'/>
<a t-attf-href="mailto:{{project.partner_email}}" t-if="project.partner_email"><div t-field="project.partner_id" t-options='{"widget": "contact", "fields": ["email"]}'/></a>
<a t-attf-href="tel:{{project.partner_phone}}" t-if="project.partner_phone"><div t-field="project.partner_id" t-options='{"widget": "contact", "fields": ["phone"]}'/></a>
</div>
</div>
</div>
+45 -51
View File
@@ -246,6 +246,16 @@
name="%(act_project_project_2_project_task_all)d" icon="fa-tasks">
<field string="Tasks In Progress" name="task_count" widget="statinfo" options="{'label_field': 'label_tasks'}"/>
</button>
<button class="oe_stat_button" name="%(project.project_milestone_all)d" type="action" icon="fa-check-square-o">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="milestone_count" nolabel="1"/>
</span>
<span class="o_stat_text">
Milestones
</span>
</div>
</button>
<button name="action_view_all_rating" type="object" attrs="{'invisible': ['|', ('rating_active', '=', False), ('rating_percentage_satisfaction', '=', -1)]}" class="oe_stat_button oe_read_only" icon="fa-smile-o" groups="project.group_project_rating">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
@@ -274,16 +284,6 @@
<field name="last_update_status" widget="status_with_color" options="{'color_field': 'last_update_color', 'no_quick_edit': 1}"/>
</div>
</button>
<button class="oe_stat_button" name="%(project.project_milestone_all)d" type="action" icon="fa-check-square-o">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="milestone_count" nolabel="1"/>
</span>
<span class="o_stat_text">
Milestones
</span>
</div>
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_title">
@@ -306,26 +306,18 @@
<field name="date" string="Deadline"/>
<div name="alias_def" colspan="2" attrs="{'invisible': [('alias_domain', '=', False)]}">
<!-- Always display the whole alias in edit mode. It depends in read only -->
<label for="alias_enabled" class="font-weight-bold" string="Create tasks by sending an email"/>
<field name="alias_enabled" invisible="1"/>
<span>
<span class="font-weight-bold oe_read_only" attrs="{'invisible': [('alias_name', '=', False)]}">to </span>
<span class="font-weight-bold oe_edit_only">to </span>
<field name="alias_name" class="oe_inline"/>
<span class="oe_read_only" attrs="{'invisible': [('alias_name', '=', False)]}">@</span>
<span class="oe_edit_only">@</span>
<field name="alias_domain"
attrs="{'invisible': [('alias_name', '=', False)]}"
class="oe_inline oe_read_only"
readonly="1"/>
<field name="alias_domain"
class="oe_inline oe_edit_only"
readonly="1"/>
</span>
<span class="font-weight-bold oe_read_only" attrs="{'invisible': [('alias_name', '=', False)]}">Create tasks by sending an email to </span>
<span class="font-weight-bold oe_edit_only">Create tasks by sending an email to </span>
<field name="alias_value" class="oe_read_only d-inline" readonly="1" widget="email" attrs="{'invisible': [('alias_name', '=', False)]}" />
<span class="oe_edit_only">
<field name="alias_name" class="oe_inline"/>@
<field name="alias_domain" class="oe_inline" readonly="1"/>
</span>
</div>
<!-- the alias contact must appear when the user start typing and it must disappear
when the string is deleted. -->
<field name="alias_contact" class="oe_inline oe_edit_only" string="Accept Emails From"
<field name="alias_contact" class="oe_inline" string="Accept Emails From"
attrs="{'invisible': ['|', ('alias_name', '=', ''), ('alias_name', '=', False)]}"/>
</group>
<group>
@@ -413,6 +405,7 @@
<field name="name" string="Project"/>
<field name="user_id" string="Project Manager"/>
<field name="partner_id" string="Customer" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="analytic_account_id"/>
<filter string="My Favorites" name="my_projects" domain="[('favorite_user_ids', 'in', uid)]"/>
<separator/>
<filter string="Followed" name="followed_by_me" domain="[('message_is_follower', '=', True)]"/>
@@ -437,13 +430,12 @@
<field name="message_needaction" invisible="1"/>
<field name="active" invisible="1"/>
<field name="name" string="Name" class="font-weight-bold"/>
<field name="user_id" optional="show" string="Project Manager" widget="many2one_avatar_user"/>
<field name="partner_id" optional="show" string="Customer"/>
<field name="analytic_account_id" optional="hide" groups="analytic.group_analytic_accounting"/>
<field name="privacy_visibility" optional="hide"/>
<field name="label_tasks" optional="hide"/>
<field name="company_id" optional="show" groups="base.group_multi_company"/>
<field name="date" string="Deadline" widget="remaining_days" optional="show"/>
<field name="company_id" optional="show" groups="base.group_multi_company" options="{'no_create': True, 'no_create_edit': True}"/>
<field name="analytic_account_id" optional="hide" groups="analytic.group_analytic_accounting"/>
<field name="date" optional="hide" string="Deadline" widget="remaining_days"/>
<field name="user_id" optional="show" string="Project Manager" widget="many2one_avatar_user" options="{'no_open':True, 'no_create': True, 'no_create_edit': True}"/>
<field name="last_update_color" invisible="1"/>
<field name="last_update_status" string="Status" optional="show" widget="status_with_color" options="{'color_field': 'last_update_color'}"/>
</tree>
@@ -490,7 +482,6 @@
<field name="name" class="o_project_name oe_inline"
string="Project Name" placeholder="e.g. Office Party"/>
<field name="user_id" invisible="1"/>
<field name="allow_recurring_tasks" groups="project.group_project_recurring_tasks" />
</group>
<div name="alias_def" colspan="2" attrs="{'invisible': [('alias_domain', '=', False)]}">
<label for="alias_name" class="oe_inline" string="Create tasks by sending an email to"/>
@@ -524,6 +515,7 @@
<field name="view_mode">form</field>
<field name="view_id" ref="project_project_view_form_simplified_footer"/>
<field name="target">new</field>
<field name="context">{"default_allow_billable": 1}</field>
</record>
<record model="ir.ui.view" id="view_project_kanban">
@@ -552,30 +544,28 @@
<field name="last_update_status"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="#{kanban_color(record.color.raw_value)} oe_kanban_global_click o_has_icon oe_kanban_content">
<div t-attf-class="#{kanban_color(record.color.raw_value)} oe_kanban_global_click o_has_icon oe_kanban_content oe_kanban_card">
<div class="o_project_kanban_main ">
<div class="o_kanban_card_content mw-100">
<div class="o_kanban_primary_left">
<div class="o_primary">
<span class="o_text_overflow"><t t-esc="record.name.value"/></span>
<span class="o_text_overflow" t-if="record.partner_id.value">
<strong><t t-esc="record.commercial_partner_id.value"/></strong>
<span class="o_text_overflow text-muted" t-if="record.partner_id.value">
<span class="fa fa-user mr-2" aria-label="Partner" title="Partner"></span><t t-esc="record.partner_id.value"/>
</span>
<div t-if="record.date.raw_value">
<field name="date" widget="remaining_days"/>
<div t-if="record.date.raw_value" class="text-muted">
<span class="fa fa-clock-o mr-2" aria-label="Remaining days" title="Remaining days"></span><field name="date" widget="remaining_days"/>
</div>
</div>
<div t-if="record.alias_name.value and record.alias_domain.value">
<span><i class="fa fa-envelope" role="img" aria-label="Domain Alias" title="Domain Alias"></i> <a t-att-href="'mailto:' + record.alias_id.value" title="Send an email"><t t-esc="record.alias_id.value"/></a></span>
</div>
<div t-if="record.rating_active.raw_value" class="text-primary" title="Percentage of happy ratings over the past 30 days." groups="project.group_project_rating">
<b>
<a name="action_view_all_rating" type="object" context="{'search_default_rating_last_30_days':1}">
<div t-if="record.alias_name.value and record.alias_domain.value" class="text-muted">
<span class="fa fa-envelope-o mr-2" aria-label="Domain Alias" title="Domain Alias"></span><t t-esc="record.alias_id.value"/>
</div>
<div t-if="record.rating_active.raw_value" class="text-muted" title="Percentage of happy ratings over the past 30 days." groups="project.group_project_rating">
<b>
<t t-if="record.rating_percentage_satisfaction.value != -1">
<i class="fa fa-smile-o" role="img" aria-label="Percentage of satisfaction" title="Percentage of satisfaction"/> <t t-esc="record.rating_percentage_satisfaction.value"/>%
</t>
</a>
</b>
</b>
</div>
</div>
</div>
</div>
@@ -614,10 +604,14 @@
</div>
</div>
</div>
<a class="o_kanban_manage_toggle_button o_left" href="#" groups="base.group_user"><i class="fa fa-ellipsis-v" role="img" aria-label="Manage" title="Manage"/></a>
<span class="o_right"><field name="is_favorite" widget="boolean_favorite" nolabel="1" force_save="1" /></span>
<a class="o_kanban_manage_toggle_button o_dropdown_kanban" href="#" groups="base.group_user">
<i class="fa fa-ellipsis-v" role="img" aria-label="Manage" title="Manage"/>
</a>
<span>
<field name="is_favorite" widget="boolean_favorite" nolabel="1" force_save="1" />
</span>
</div>
<div class="o_kanban_record_bottom">
<div class="o_kanban_record_bottom mt-3">
<div class="oe_kanban_bottom_left">
<div class="o_project_kanban_boxes">
<a class="o_project_kanban_box" name="action_view_tasks" type="object">
@@ -651,7 +645,7 @@
<p class="o_view_nocontent_smiling_face">
No projects found. Let's create one!
</p><p>
Projects regroup tasks on the same topic and each have their own dashboard.
Projects regroup tasks on the same topic, and each has its dashboard.
</p>
</field>
</record>
@@ -670,7 +664,7 @@
<p class="o_view_nocontent_smiling_face">
No projects found. Let's create one!
</p><p>
Projects regroup tasks on the same topic and each have their own dashboard.
Projects regroup tasks on the same topic, and each has its dashboard.
</p>
</field>
</record>
@@ -1,13 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="project_project_view_inherit_project_filter" model="ir.ui.view">
<field name="name">project.project.select.inherit.project</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project_project_filter"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="sale_order_id"/>
</xpath>
</field>
</record>
<record id="project_project_view_tree_inherit_sale_project" model="ir.ui.view">
<field name="name">project.project.tree.inherit.sale.project</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='analytic_account_id']" position="after">
<field name="sale_order_id" optional="hide" widget="sale_order_many2one"/>
<xpath expr="//field[@name='partner_id']" position="after">
<field name="sale_line_id" optional="hide" widget="sale_order_many2one" readonly="1"/>
</xpath>
</field>
@@ -60,11 +60,7 @@ tour.register('sale_timesheet_tour', {
run: function (actions) {
actions.text('Project for Freeman', this.$anchor);
},
}, {
trigger: 'div[name="allow_billable"] input',
content: 'Click on the checkbox to have a billable project.',
run: 'click',
}, {
}, {
trigger: 'button[name="action_view_tasks"]',
content: 'Click on Create button to create and enter to this newest project.',
run: 'click',
@@ -6,14 +6,16 @@
<field name="model">project.project</field>
<field name="inherit_id" ref="hr_timesheet.project_invoice_form"/>
<field name="arch" type="xml">
<div class="oe_button_box" position="inside">
<button string="Project Overview" class="oe_stat_button" type="object" name="action_view_timesheet" icon="fa-puzzle-piece" attrs="{'invisible': [('allow_billable', '=', False)]}" groups="project.group_project_manager"/>
<xpath expr="//button[@name='%(project.action_project_task_burndown_chart_report)d']" position="after">
<button string="Costs / Revenues" class="oe_stat_button" type="object" name="action_view_timesheet" icon="fa-puzzle-piece" attrs="{'invisible': [('allow_billable', '=', False)]}" groups="project.group_project_manager"/>
</xpath>
<xpath expr="//button[@name='%(project.act_project_project_2_project_task_all)d']" position="before">
<button class="d-none d-md-inline oe_stat_button"
type="object" name="action_view_so" icon="fa-dollar"
attrs="{'invisible': ['|', ('allow_billable', '=', False), ('sale_order_id', '=', False)]}"
string="Sales Order"
groups="sales_team.group_sale_salesman_all_leads"/>
</div>
</xpath>
<xpath expr="//header" position="inside">
<button name="action_make_billable" string="Create Sales Order" type="object" attrs="{'invisible': [('display_create_order', '=', False)]}" groups="sales_team.group_sale_salesman"/>
</xpath>
@@ -73,7 +75,7 @@
<field name="model">project.project</field>
<field name="inherit_id" ref="hr_timesheet.project_project_view_form_simplified_inherit_timesheet"/>
<field name="arch" type="xml">
<field name="allow_timesheets" position="after">
<field name="allow_timesheets" position="before">
<field name="company_id" invisible="1"/>
<field name="allow_billable"/>
</field>
@@ -92,9 +94,11 @@
<field name="pricing_type" invisible="1"/>
</xpath>
<xpath expr="//div[hasclass('o_kanban_manage_reporting')]" position="inside">
<div t-if="record.allow_timesheets.raw_value and record.allow_billable.raw_value" role="menuitem" groups="project.group_project_manager">
<a name="action_view_timesheet" type="object">Costs / Revenues</a>
</div>
<div role="menuitem" groups="project.group_project_manager">
<a name="action_view_all_rating" type="object" context="{'search_default_rating_last_30_days':1}">
Customer Ratings
</a>
</div>
</xpath>
<xpath expr="//div[hasclass('o_kanban_manage_view')]" position="inside">
<div t-if="record.allow_billable.raw_value and record.sale_order_id.raw_value and record.pricing_type.raw_value != 'task_rate'"
@@ -202,5 +206,4 @@
<field name="res_model">project.project</field>
<field name="view_mode">qweb</field>
</record>
</odoo>