summaryrefslogtreecommitdiff
path: root/addons/hr_skills/static/src
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/hr_skills/static/src
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/hr_skills/static/src')
-rw-r--r--addons/hr_skills/static/src/css/hr_skills.scss152
-rw-r--r--addons/hr_skills/static/src/js/resume_widget.js240
-rw-r--r--addons/hr_skills/static/src/xml/resume_templates.xml55
-rw-r--r--addons/hr_skills/static/src/xml/skills_templates.xml24
4 files changed, 471 insertions, 0 deletions
diff --git a/addons/hr_skills/static/src/css/hr_skills.scss b/addons/hr_skills/static/src/css/hr_skills.scss
new file mode 100644
index 00000000..9ff24615
--- /dev/null
+++ b/addons/hr_skills/static/src/css/hr_skills.scss
@@ -0,0 +1,152 @@
+.o_form_sheet {
+ $o-hrs-timeline-entry-padding: .5rem;
+ $o-hrs-timeline-dot-size: .6rem;
+
+ // Overall design
+ // =========================================
+ .o_hr_skills_group {
+ // Overwrite '.o_list_table' default design
+ .o_list_view {
+ .o_list_table {
+ cursor: auto;
+ }
+
+ tbody:first-of-type > .o_resume_group_header:first-child {
+ box-shadow: none;
+ }
+ }
+
+ // Use 'cursor:pointer' where is needed only
+ .o_data_row {
+ cursor: pointer;
+ }
+
+ // Deny user interaction to headers but keep access to buttons
+ .o_group_header, .o_resume_group_header {
+ &, &:hover {
+ background: none !important;
+ cursor: initial;
+ pointer-events: none;
+ }
+ .o_field_x2many_list_row_add, .o_field_x2many_list_row_add:hover {
+ cursor: pointer;
+ pointer-events: initial;
+ }
+ }
+ }
+
+ // Resumé design
+ // =========================================
+ .o_group_resume {
+ .o_data_row td {
+ padding: $o-hrs-timeline-entry-padding;
+
+ &.o_resume_timeline_cell {
+ div {
+ @include size($o-hrs-timeline-dot-size);
+ }
+
+ &:before {
+ @include o-position-absolute(0, $left: ($o-hrs-timeline-dot-size * .5 + $o-hrs-timeline-entry-padding));
+ @include size(1px, 100%);
+ margin-left: -.01rem;
+ background-color: $border-color;
+ content: "";
+ }
+ }
+ }
+
+ .o_resume_line_title, .o_resume_line_desc {
+ white-space: normal;
+ }
+
+ .o_resume_line_title, .o_resume_line_dates {
+ line-height: 1;
+ }
+
+ .o_resume_group_header + .o_data_row .o_resume_timeline_cell:before {
+ top: $o-hrs-timeline-entry-padding;
+ }
+
+ .o_data_row.o_data_row_last {
+ .o_resume_line_desc {
+ margin-bottom: $headings-margin-bottom;
+ }
+
+ .o_resume_timeline_cell:before {
+ height: $o-hrs-timeline-entry-padding;
+ }
+ }
+ }
+
+ // Skills design
+ // =========================================
+ .o_group_skills {
+ .o_resume_empty_helper {
+ display: none;
+ }
+
+ .o_group_header {
+ > .o_group_name {
+ padding: .8rem .5rem 0 0;
+ }
+
+ &:first-child > .o_group_name {
+ padding-top: 0;
+ }
+ }
+
+ .o_skill_cell {
+ padding-left: 0;
+ white-space: normal !important;
+
+ .o_progressbar {
+ display: inline-flex;
+ align-items: center;
+ }
+
+ .o_progress {
+ border: 0;
+ background: $gray-300;
+ height: 5px;
+ }
+
+ .o_progressbar_value {
+ width: auto;
+ font-size: $font-size-sm;
+ font-weight: bold;
+ }
+ }
+ }
+
+ // Editing mode
+ // =========================================
+ .o_form_view.o_form_editable & {
+ .o_group_name {
+ background-color: gray('200');
+ }
+
+ .o_resume_group_header .btn {
+ margin-top: .25rem;
+ margin-right: .4rem;
+ }
+
+ .o_group_skills .o_group_name {
+ padding-top: .4em;
+ padding-bottom: .4rem;
+ }
+
+ .o_group_name, .o_skill_cell {
+ padding-left: .5rem;
+ }
+
+ .o_group_skills .o_group_name > b, .o_hr_skills_group .o_horizontal_separator {
+ color: color-yiq(gray('200'));
+ font-style: italic;
+ }
+
+ .o_list_record_remove > button {
+ @include o-hover-text-color($text-muted, theme-color('danger'));
+ }
+ }
+}
diff --git a/addons/hr_skills/static/src/js/resume_widget.js b/addons/hr_skills/static/src/js/resume_widget.js
new file mode 100644
index 00000000..413942ba
--- /dev/null
+++ b/addons/hr_skills/static/src/js/resume_widget.js
@@ -0,0 +1,240 @@
+odoo.define('web.FieldResume', function (require) {
+"use strict";
+
+var time = require('web.time');
+var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
+var FieldProgressBar = require('web.basic_fields').FieldProgressBar;
+var ListRenderer = require('web.ListRenderer');
+var field_registry = require('web.field_registry');
+
+var core = require('web.core');
+var qweb = core.qweb;
+var _t = core._t;
+
+var AbstractGroupedOne2ManyRenderer = ListRenderer.extend({
+ /**
+ * This abstract renderer is use to render a one2many field in a form view.
+ * The records in the one2many field are displayed grouped by a specific field.
+ *
+ * A concrete renderer can/should set:
+ * - groupBy: field to group records
+ * - dataRowTemplate: template to render a record's data
+ * - groupTitleTemplate (optional): template to render the header row of a group
+ * - addLineButtonTemplate (optional): template to render the 'Add a line' button at the end of each group (edit mode only)
+ **/
+
+ groupBy: '', // Field: records are grouped based on this field
+ groupTitleTemplate: 'hr_default_group_row', // Template used to render the title row of a group
+ dataRowTemplate: '', // Template used to render a record
+ addLineButtonTemplate: 'group_add_item',
+
+ /**
+ * Don't freeze the columns because as the header is empty, the algorithm
+ * won't work.
+ *
+ * @override
+ * @private
+ */
+ _freezeColumnWidths: function () {},
+
+ /**
+ * Renders a empty header
+ *
+ * @override
+ * @private
+ */
+ _renderHeader: function () {
+ return $('<thead/>');
+ },
+
+ /**
+ * Renders a empty footer
+ *
+ * @override
+ * @private
+ */
+ _renderFooter: function () {
+ return $('<tfoot/>');
+ },
+
+ /**
+ * @override
+ * @private
+ */
+ _renderGroupRow: function (display_name) {
+ return qweb.render(this.groupTitleTemplate, {display_name: display_name});
+ },
+
+ /**
+ * This method is meant to be overriten by concrete renderers and
+ * is called each time a row is rendered.
+ * It is a hook to format record's data before it's given to the qweb template.
+ *
+ * @private
+ */
+ _formatData: function (data) {
+ return data;
+ },
+
+ _renderRow: function (record, isLast) {
+ return $(qweb.render(this.dataRowTemplate, {
+ id: record.id,
+ data: this._formatData(record.data),
+ is_last: isLast,
+ }));
+ },
+
+ /**
+ * This method is meant to be overridden by concrete renderers.
+ * Returns a context used for the 'Add a line' button.
+ * It's useful to set default values.
+ * An 'Add a line' button is added after each group of records.
+ * The group passed as parameters allow to set a different context based on the group.
+ * If no records exist, group is undefined.
+ *
+ * @private
+ */
+ _getCreateLineContext: function (group) {
+ return {};
+ },
+
+ _renderTrashIcon: function() {
+ return qweb.render('hr_trash_button');
+ },
+
+ _renderAddItemButton: function (group) {
+ return qweb.render(this.addLineButtonTemplate, {
+ context: JSON.stringify(this._getCreateLineContext(group)),
+ });
+ },
+
+ _renderBody: function () {
+ var self = this;
+
+ var grouped_by = _.groupBy(this.state.data, function (record) {
+ return record.data[self.groupBy].res_id;
+ });
+
+ var groupTitle;
+ var $body = $('<tbody>');
+ for (var key in grouped_by) {
+ var group = grouped_by[key];
+ if (key === 'undefined') {
+ groupTitle = _t("Other");
+ } else {
+ groupTitle = group[0].data[self.groupBy].data.display_name;
+ }
+ var $title_row = $(self._renderGroupRow(groupTitle));
+ $body.append($title_row);
+
+ // Render each rows
+ group.forEach(function (record, index) {
+ var isLast = (index + 1 === group.length);
+ var $row = self._renderRow(record, isLast);
+ if (self.addTrashIcon) $row.append(self._renderTrashIcon());
+ $body.append($row);
+ });
+
+ if (self.addCreateLine) {
+ $title_row.find('.o_group_name').append(self._renderAddItemButton(group));
+ }
+ }
+
+ if ($body.is(':empty') && self.addCreateLine) {
+ $body.append(this._renderAddItemButton());
+ }
+ return $body;
+ },
+
+});
+
+var ResumeLineRenderer = AbstractGroupedOne2ManyRenderer.extend({
+
+ groupBy: 'line_type_id',
+ groupTitleTemplate: 'hr_resume_group_row',
+ dataRowTemplate: 'hr_resume_data_row',
+
+ _formatData: function (data) {
+ var dateFormat = time.getLangDateFormat();
+ var date_start = data.date_start && data.date_start.format(dateFormat) || "";
+ var date_end = data.date_end && data.date_end.format(dateFormat) || _t("Current");
+ return _.extend(data, {
+ date_start: date_start,
+ date_end: date_end,
+ });
+ },
+
+ _getCreateLineContext: function (group) {
+ var ctx = this._super(group);
+ return group ? _.extend({default_line_type_id: group[0].data[this.groupBy] && group[0].data[this.groupBy].data.id || ""}, ctx) : ctx;
+ },
+
+ _render: function () {
+ var self = this;
+ return this._super().then(function () {
+ self.$el.find('table').removeClass('table-striped o_list_table_ungrouped');
+ self.$el.find('table').addClass('o_resume_table table-borderless');
+ });
+ },
+});
+
+
+var SkillsRenderer = AbstractGroupedOne2ManyRenderer.extend({
+
+ groupBy: 'skill_type_id',
+ dataRowTemplate: 'hr_skill_data_row',
+
+ _renderRow: function (record) {
+ var $row = this._super(record);
+ // Add progress bar widget at the end of rows
+ var $td = $('<td/>', {class: 'o_data_cell o_skill_cell'});
+ var progress = new FieldProgressBar(this, 'level_progress', record, {
+ current_value: record.data.level_progress,
+ attrs: this.arch.attrs,
+ });
+ progress.appendTo($td);
+ return $row.append($td);
+ },
+
+ _getCreateLineContext: function (group) {
+ var ctx = this._super(group);
+ return group ? _.extend({ default_skill_type_id: group[0].data[this.groupBy].data.id }, ctx) : ctx;
+ },
+
+ _render: function () {
+ var self = this;
+ return this._super().then(function () {
+ self.$el.find('table').toggleClass('table-striped');
+ });
+ },
+});
+
+
+var FieldResume = FieldOne2Many.extend({
+
+ /**
+ * @override
+ * @private
+ */
+ _getRenderer: function () {
+ return ResumeLineRenderer;
+ },
+});
+
+var FieldSkills = FieldOne2Many.extend({
+
+ /**
+ * @override
+ * @private
+ */
+ _getRenderer: function () {
+ return SkillsRenderer;
+ },
+});
+
+field_registry.add('hr_resume', FieldResume);
+field_registry.add('hr_skills', FieldSkills);
+
+return FieldResume;
+
+});
diff --git a/addons/hr_skills/static/src/xml/resume_templates.xml b/addons/hr_skills/static/src/xml/resume_templates.xml
new file mode 100644
index 00000000..5db3b006
--- /dev/null
+++ b/addons/hr_skills/static/src/xml/resume_templates.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+<t t-name="hr_resume_data_row">
+ <tr class="o_data_row" t-attf-class="o_data_row #{is_last? 'o_data_row_last' : ''}" t-att-data-id="id">
+ <t t-if="data.display_type === 'classic'">
+ <td class="o_resume_timeline_cell position-relative pr-lg-2">
+ <div class="rounded-circle bg-info position-relative"/>
+ </td>
+ <td class="o_data_cell pt-0 w-100">
+ <div class="o_resume_line" t-att-data-id="id">
+ <small class="o_resume_line_dates">
+ <b t-esc="data.date_start"/> - <b t-esc="data.date_end"/>
+ </small>
+ <h4 class="o_resume_line_title mt-2" t-esc="data.name"/>
+ <p t-if="data.description" class="o_resume_line_desc" t-esc="data.description"/>
+ </div>
+ </td>
+ </t>
+ </tr>
+</t>
+
+<t t-name="hr_trash_button">
+ <td class="o_list_record_remove pr-3">
+ <button name="delete" arial-label="Delete row" class="btn btn-secondary">
+ <i class="fa fa-trash"/>
+ </button>
+ </td>
+</t>
+
+<t t-name="hr_resume_group_row">
+ <tr class="o_resume_group_header">
+ <td class="o_group_name" colspan="100%"><span class="o_horizontal_separator my-0" t-esc="display_name"/></td>
+ </tr>
+</t>
+
+<t t-name="group_add_item">
+ <t t-set="empty" t-value="Object.keys(context).length == 2"/>
+
+ <div t-attf-class="o_field_x2many_list_row_add #{empty? 'd-block w-100' : 'd-inline pull-right'}">
+ <div t-if="empty" class="o_resume_empty_helper o_horizontal_separator text-muted my-0">
+ <em>Resumé empty</em>
+ </div>
+ <a href="#"
+ role="button"
+ t-attf-class="btn o-kanban-button-new #{empty? 'btn-primary mt-3' : 'btn-secondary btn-sm'}"
+ t-attf-data-context="{{ context }}">
+ <t t-if="empty">CREATE A NEW ENTRY</t>
+ <t t-else="">ADD</t>
+ </a>
+ </div>
+</t>
+
+
+</templates>
diff --git a/addons/hr_skills/static/src/xml/skills_templates.xml b/addons/hr_skills/static/src/xml/skills_templates.xml
new file mode 100644
index 00000000..e078ca3e
--- /dev/null
+++ b/addons/hr_skills/static/src/xml/skills_templates.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+<t t-name="hr_skill_data_row">
+ <tr class="o_data_row" t-att-data-id="id">
+ <td class="o_data_cell o_skill_cell w-100">
+ <t t-esc="data.skill_id.data.display_name"/>
+ </td>
+ <td class="o_data_cell o_skill_cell pr-3">
+ <t t-esc="data.skill_level_id.data.display_name"/>
+ </td>
+ </tr>
+</t>
+
+<t t-name="hr_default_group_row">
+ <tr class="o_group_header o_group_has_content">
+ <td class="o_group_name border-0 pr-2" colspan="99">
+ <b t-esc="display_name"/>
+ </td>
+ </tr>
+</t>
+
+
+</templates>