[MERGE] project,hr_timesheet: simplify the user onboarding

This merge provides many little changes to improve the usability
and the onboarding of users in project and timesheet
applications.
This mostly contains labels renaming, restruration of views, ...
but some new mecanism appears like the "ghost kanban examples",
and improvements of the kanban examples modal.

Task-1893021

closes odoo/odoo#28461
This commit is contained in:
Robot Odoo
2019-01-17 11:23:11 +01:00
committed by GitHub
7 changed files with 156 additions and 58 deletions
+10 -10
View File
@@ -203,17 +203,17 @@ class Project(models.Model):
help="Internal email associated with this project. Incoming emails are automatically synchronized "
"with Tasks (or optionally Issues if the Issue Tracker module is installed).")
privacy_visibility = fields.Selection([
('followers', 'On invitation only'),
('employees', 'Visible by all employees'),
('portal', 'Visible by following customers'),
('followers', 'Visible by invited employees'),
('employees', 'Visible by employees'),
('portal', 'Visible by portal users and employees'),
],
string='Privacy', required=True,
default='portal',
help="Defines the visibility of the tasks of the project:\n"
"- On invitation only: employees may only see the followed project and tasks.\n"
"- Visible by all employees: employees may see all project and tasks.\n"
"- Visible by following customers: employees may see everything."
" if website is activated, portal users may see project and tasks followed by.\n"
"- Visible by invited employees: employees may only see the followed project and tasks.\n"
"- Visible by employees: employees may see all project and tasks.\n"
"- Visible by portal users and employees: employees may see everything."
" 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')
@@ -428,9 +428,9 @@ class Task(models.Model):
name = fields.Char(string='Title', tracking=True, required=True, index=True)
description = fields.Html(string='Description')
priority = fields.Selection([
('0', 'Low'),
('1', 'Normal'),
], default='0', index=True, string="Priority")
('0', 'Normal'),
('1', 'Important'),
], default='0', index=True, string="Priority")
sequence = fields.Integer(string='Sequence', index=True, default=10,
help="Gives the sequence order when displaying a list of tasks.")
stage_id = fields.Many2one('project.task.type', string='Stage', ondelete='restrict', tracking=True, index=True,
@@ -27,25 +27,29 @@ function escFormat() {
kanbanExamplesRegistry.add('project', [{
name: _t('Software Development'),
columns: [_t('Backlog'), _t('Specifications'), _t('Development'), _t('Tests'), _t('Delivered')],
description: escFormat(_t('Once a task is specified, set it %s in the Specifications ' +
'column, so that developers know they can pull it. If you work in sprints, use %s to ' +
'mark tasks of the current sprint.'), greenBullet, star),
description: escFormat(_t('Prioritize Tasks by using the %s icon.'+
'%s Use the %s button to signalize to your colleagues that a task is ready for the next stage.'+
'%s Use the %s to signalize a problem or a need for discussion on a task.'), star, '<br/>', greenBullet, '<br/>', redBullet),
bullets: [greenBullet, redBullet, star],
}, {
name: _t('Agile'),
columns: [_t('Backlog'), _t('Analysis'), _t('Development'), _t('Testing'), _t('Done')],
description: escFormat(_t('Waiting for the next stage: use %s and %s bullets.'), greenBullet, redBullet),
bullets: [greenBullet, redBullet],
}, {
name: _t('Digital Marketing'),
columns: [_t('Ideas'), _t('Researching'), _t('Writing'), _t('Editing'), _t('Done')],
description: escFormat(_t('Everyone can propose ideas, and the Editor marks the best ones ' +
'as %s. Attach all documents or links to the task directly, to have all information about ' +
'a research centralized.'), greenBullet),
bullets: [greenBullet, redBullet],
}, {
name: _t('Customer Feedback'),
columns: [_t('New'), _t('In development'), _t('Done'), _t('Refused')],
description: escFormat(_t('Customers propose feedbacks by email; Odoo creates tasks ' +
'automatically, and you can communicate on the task directly. Your managers decide which ' +
'feedback is accepted %s and which feedback is moved to the "Refused" column.'), greenBullet),
bullets: [greenBullet, redBullet],
}, {
name: _t('Getting Things Done (GTD)'),
columns: [_t('Inbox'), _t('Today'), _t('This Week'), _t('This Month'), _t('Long Term')],
@@ -56,18 +60,24 @@ kanbanExamplesRegistry.add('project', [{
}, {
name: _t('Consulting'),
columns: [_t('New Projects'), _t('Resources Allocation'), _t('In Progress'), _t('Done')],
description: escFormat(_t('Manage the lifecycle of your project using the kanban view. Add newly acquired project, assign them and use the %s and %s to define if the project is ready for the next step.'), greenBullet, redBullet),
bullets: [greenBullet, redBullet],
}, {
name: _t('Research Project'),
columns: [_t('Brainstorm'), _t('Research'), _t('Draft'), _t('Final Document')],
description: escFormat(_t('Handle your idea gahtering within Tasks of your new Project and discuss them in the chatter of the tasks. Use the %s and %s to signalize what is the current status of your Idea'), greenBullet, redBullet),
bullets: [greenBullet, redBullet],
}, {
name: _t('Website Redesign'),
columns: [_t('Page Ideas'), _t('Copywriting'), _t('Design'), _t('Live')],
description: escFormat(_t('Handle your idea gahtering within Tasks of your new Project and discuss them in the chatter of the tasks. Use the %s and %s to signalize what is the current status of your Idea'), greenBullet, redBullet),
}, {
name: _t('T-shirt Printing'),
columns: [_t('New Orders'), _t('Logo Design'), _t('To Print'), _t('Done')],
description: escFormat(_t('Communicate with customers on the task using the email gateway. ' +
'Attach logo designs to the task, so that information flow from designers to the workers ' +
'who print the t-shirt. Organize priorities amongst orders %s using the icon.'), star),
bullets: [star],
}]);
});
+2 -2
View File
@@ -444,7 +444,7 @@
</div>
<group>
<group>
<field name="project_id" domain="[('active', '=', True)]"/>
<field name="project_id" domain="[('active', '=', True)]" options="{'no_create': True}"/>
<field name="user_id"
class="o_task_user_field"
options='{"no_open": True}'/>
@@ -467,7 +467,7 @@
<group>
<field name="sequence" groups="base.group_no_one"/>
<field name="partner_id" attrs="{'readonly': [('parent_id', '!=', False)]}"/>
<field name="email_from" attrs="{'readonly': [('parent_id', '!=', False)]}"/>
<field name="email_from" attrs="{'readonly': [('parent_id', '!=', False)]}" groups="base.group_no_one"/>
<field name="email_cc" groups="base.group_no_one"/>
<field name="parent_id" attrs="{'invisible' : [('subtask_count', '>', 0)]}" groups="project.group_subtask_project"/>
<field name="child_ids" invisible="1" />
@@ -33,6 +33,9 @@ var KanbanController = BasicController.extend({
column_toggle_fold: '_onToggleColumn',
kanban_column_records_toggle_active: '_onToggleActiveRecords',
}),
events: _.extend({}, BasicController.prototype.events, {
click: '_onClick',
}),
/**
* @override
* @param {Object} params
@@ -302,6 +305,23 @@ var KanbanController = BasicController.extend({
this.$('.o_kanban_record:first').focus();
}
},
/**
* Bounce the 'Create' button.
*
* @private
* @param {MouseEvent} ev
**/
_onClick: function (ev) {
var state = this.model.get(this.handle, {raw: true});
if (!state.count && this.buttons) {
var classesList = ['o_kanban_view', 'o_kanban_group', 'o_column_quick_create', 'o_view_nocontent_smiling_face'];
var $target = $(ev.target);
var hasClassList = _.map(classesList, function(klass){ return $target.hasClass(klass) });
if (_.some(hasClassList)) {
this.$buttons.find('.o-kanban-button-new').odooBounce();
}
}
},
/**
* @private
* @param {OdooEvent} ev
@@ -321,12 +341,7 @@ var KanbanController = BasicController.extend({
this.model
.deleteRecords([column.db_id], relatedModelName)
.done(function () {
if (column.isEmpty()) {
self.renderer.removeWidget(column);
self._updateButtons();
} else {
self.reload();
}
self.update({}, {reload: !column.isEmpty()});
});
},
/**
@@ -166,15 +166,6 @@ var KanbanRenderer = BasicRenderer.extend({
this.quickCreate.toggleFold();
this._toggleNoContentHelper();
},
/**
* Removes a widget (record if ungrouped, column if grouped) from the view.
*
* @param {Widget} widget the instance of the widget to remove
*/
removeWidget: function (widget) {
this.widgets.splice(this.widgets.indexOf(widget), 1);
widget.destroy();
},
/**
* Updates a given column with its new state.
*
@@ -275,6 +266,16 @@ var KanbanRenderer = BasicRenderer.extend({
previousCard.focus();
}
},
/**
* Render the Example Ghost Kanban card on the background
*
* @private
* @param {DocumentFragment} fragment
*/
_renderExampleBackground: function (fragment) {
var $background = $(qweb.render('KanbanView.ExamplesBackground'));
$background.appendTo(fragment);
},
/**
* Renders empty invisible divs in a document fragment.
*
@@ -351,9 +352,9 @@ var KanbanRenderer = BasicRenderer.extend({
// Open it directly if there is no column yet
if (!self.state.data.length) {
self.quickCreate.toggleFold();
self._renderExampleBackground(fragment);
}
});
}
}
},
@@ -546,6 +547,7 @@ var KanbanRenderer = BasicRenderer.extend({
_onQuickCreateColumnUpdated: function (ev) {
ev.stopPropagation();
this._toggleNoContentHelper();
this._updateExampleBackground();
},
/**
* @private
@@ -596,6 +598,21 @@ var KanbanRenderer = BasicRenderer.extend({
_onStartQuickCreate: function () {
this._toggleNoContentHelper(true);
},
/**
* Hide or display the background example:
* - displayed when quick create column is display and there is no column else
* - hidden otherwise
*
* @private
**/
_updateExampleBackground: function () {
var $elem = this.$('.o_kanban_example_background_container');
if (!this.state.data.length && !$elem.length) {
this._renderExampleBackground(this.$el);
} else {
$elem.remove();
}
},
});
return KanbanRenderer;
@@ -1,3 +1,4 @@
// container ghost card in modal
.o_kanban_examples_dialog {
display: flex;
padding: 0px;
@@ -37,35 +38,59 @@
padding: 16px 16px 0;
text-align: justify;
}
}
}
// container ghost card in background
.o_kanban_example_background_container {
display: flex;
padding: 0px;
opacity: 0.4;
// content
.o_kanban_example_background {
flex: 1 1 100%;
.o_kanban_examples {
display: flex;
padding: 0 6px;
.o_kanban_examples_group {
flex: 1 1 100%;
margin: 10px;
.o_kanban_examples_ghost {
padding: 5px;
margin-top: -1px;
border: thin solid gray('300');
background-color: white;
.o_ghost_content {
height: 12px;
background: gray('300');
&.o_ghost_tag {
display: inline-block;
width: 40%;
margin-top: 10px;
}
}
.o_ghost_avatar {
height: 20px;
width: 20px;
margin-top: 6px;
}
width: 224px;
margin-bottom: 10px;
}
}
}
}
}
// kanban ghost card
.o_kanban_examples {
display: flex;
padding: 0 6px;
.o_kanban_examples_group {
flex: 1 1 100%;
margin: 10px;
.o_kanban_examples_ghost {
padding: 5px;
margin-top: -1px;
border: thin solid gray('300');
background-color: white;
.o_ghost_content {
height: 12px;
background: gray('300');
&.o_ghost_tag {
display: inline-block;
width: 40%;
margin-top: 10px;
}
}
.o_ghost_avatar {
height: 20px;
width: 20px;
margin-top: 6px;
}
}
}
}
+34 -3
View File
@@ -56,10 +56,10 @@
<button class="btn btn-primary o_kanban_add" type="button">Add</button>
</span>
</div>
<t t-if="widget.examples">
<button type="button" class="btn btn-link o_kanban_examples">Examples</button>
</t>
<span class="o_discard_msg text-muted float-right">Esc to discard</span>
<t t-if="widget.examples">
<button type="button" class="btn btn-link o_kanban_examples">See examples</button>
</t>
</div>
<div class="o_kanban_muted_record"/>
<div class="o_kanban_muted_record"/>
@@ -158,10 +158,41 @@
<div class="o_kanban_examples_ghost">
<div class="o_ghost_content"/>
<div class="o_ghost_content o_ghost_tag"/>
<t t-if="example.bullets">
<t t-if="_.random(0,5) > 3">
<t t-raw="example.bullets[_.random(0, example.bullets.length-1)]"/>
</t>
</t>
<img class="float-right o_ghost_avatar" src="/base/static/img/avatar.png" alt="Avatar"/>
</div>
</t>
<!-- Kanban Example Background -->
<t t-name="KanbanView.ExamplesBackground">
<div class="o_kanban_example_background_container">
<div class="o_kanban_example_background">
<div class="o_kanban_examples">
<t t-foreach="_.range(1,5)" t-as="num">
<div class="o_kanban_examples_group">
<h6><b>Column <t t-esc="num"/></b></h6>
<t t-foreach="_.random(2,6)">
<t t-call="KanbanView.ExamplesBackground.RecordGhost"/>
</t>
</div>
</t>
</div>
</div>
</div>
</t>
<t t-name="KanbanView.ExamplesBackground.RecordGhost">
<div class="o_kanban_examples_ghost">
<div class="o_ghost_content"/>
<div class="o_ghost_content o_ghost_tag"/>
<img class="float-right o_ghost_avatar" src="/base/static/img/avatar.png" alt="Avatar"/>
</div>
</t>
<!-- Mobile templates -->
<t t-name="KanbanView.MobileTabs">
<div class="o_kanban_mobile_tabs">