[REF] hr: create hr_presence_status widget

Before this commit, the hr presence status icon is hardcoded in
each view in which the icon is needed.

This commit creates a widget for that icon to be able to use that
widget in the views in which the icon is needed to avoid duplicating
the code.

task-3376776
This commit is contained in:
Xavier Bol (xbo)
2023-10-16 08:17:30 +02:00
parent adbcf6e1da
commit 6ff5f9eb16
8 changed files with 181 additions and 141 deletions
@@ -0,0 +1,77 @@
/** @odoo-module */
import { Component } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
export class HrPresenceStatus extends Component {
static template = "hr.HrPresenceStatus";
static props = {
...standardFieldProps,
tag: { type: String, optional: true },
};
static defaultProps = {
tag: "small",
};
get classNames() {
const classNames = ["fa"];
classNames.push(
`fa-${this.props.tag === "small" ? "fw" : "2x"}`,
this.icon,
"o_button_icon",
"hr_presence",
"align-middle",
this.color,
)
return classNames.join(" ");
}
get color() {
switch (this.value) {
case "presence_present":
case "presence_absent_active":
return "text-success";
case "presence_absent":
return "text-muted";
case "presence_to_define":
return "text-warning";
default:
return "";
}
}
get icon() {
return `fa-circle${this.value.startsWith("presence_absent") ? "-o" : ""}`;
}
get label() {
return this.value !== false
? this.options.find(([value, label]) => value === this.value)[1]
: "";
}
get options() {
return this.props.record.fields[this.props.name].selection.filter(
(option) => option[0] !== false && option[1] !== ""
);
}
get value() {
return this.props.record.data[this.props.name];
}
}
export const hrPresenceStatus = {
component: HrPresenceStatus,
displayName: _t("HR Presence Status"),
extractProps({ viewType }, dynamicInfo) {
return {
tag: viewType === "kanban" ? "span" : "small",
};
},
};
registry.category("fields").add("hr_presence_status", hrPresenceStatus)
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="hr.HrPresenceStatus">
<t t-tag="props.tag" role="img" t-att-class="classNames" t-att-aria-label="label" t-att-title="label"/>
</t>
</templates>
+2 -25
View File
@@ -147,31 +147,8 @@
</div>
<ul>
<li id="last_login"/>
<li id="presence">
<t t-if="record.show_hr_icon_display.raw_value">
<div class="o_employee_availability align-items-center">
<!-- Employee is present/connected and it is normal according to his work schedule -->
<span t-if="record.hr_icon_display.raw_value == 'presence_present'"
class="fa fa-2x fa-circle text-success" name="presence_present" role="img"
aria-label="Present" title="Present"
/>
<!-- Employee is not present and it is normal according to his work schedule -->
<span t-if="record.hr_icon_display.raw_value == 'presence_absent'"
class="fa fa-2x fa-circle-o text-warning" name="presence_absent" role="img"
aria-label="Absent" title="Absent"
/>
<!-- Employee is connected but according to his work schedule, he should not work for now -->
<span t-if="record.hr_icon_display.raw_value == 'presence_absent_active'"
class="fa fa-2x fa-circle-o text-success" name="presence_absent_active" role="img"
aria-label="Present but not active" title="Present but not active"
/>
<!-- Employee is not here but according to his work schedule, he should be connected -->
<span t-if="record.hr_icon_display.raw_value == 'presence_to_define'"
class="fa fa-2x fa-circle text-muted" name="presence_to_define" role="img"
aria-label="To define" title="To define"
/>
</div>
</t>
<li t-if="record.show_hr_icon_display.raw_value">
<field name="hr_icon_display" class="o_employee_availability align-items-center" widget="hr_presence_status" />
</li>
<li t-if="record.work_email.raw_value">
<i class="fa fa-fw me-2 fa-envelope text-primary" title="Email"/>
+3 -37
View File
@@ -52,8 +52,6 @@
<field name="active" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="user_partner_id" invisible="1"/>
<field name="hr_presence_state" invisible="1"/>
<field name="hr_icon_display" invisible="1"/>
<field name="image_128" invisible="1" />
<field name="company_id" invisible="1"/>
<field name="last_activity_time" invisible="1"/>
@@ -81,16 +79,7 @@
</div>
<div class="o_employee_avatar m-0 p-0">
<field name="image_1920" widget='image' class="oe_avatar m-0" options='{"zoom": true, "preview_image":"avatar_128"}'/>
<div id="hr_presence_status" class="d-flex align-items-end fs-6 o_employee_availability">
<!-- Employee is present/connected and it is normal according to his work schedule -->
<small role="img" class="fa fa-fw fa-circle text-success o_button_icon hr_presence align-middle" invisible="hr_icon_display != 'presence_present'" aria-label="Present" title="Present"/>
<!-- Employee is not present and it is normal according to his work schedule -->
<small role="img" class="fa fa-fw fa-circle-o text-muted o_button_icon hr_presence align-middle" invisible="hr_icon_display != 'presence_absent'" aria-label="Absent" title="Absent" name="presence_absent"/>
<!-- Employee is connected but according to his work schedule, he should not work for now -->
<small role="img" class="fa fa-fw fa-circle-o text-success o_button_icon hr_presence align-middle" invisible="hr_icon_display != 'presence_absent_active'" aria-label="Present but not active" title="Present but not active" name="presence_absent_active"/>
<!-- Employee is not here but according to his work schedule, he should be connected -->
<small role="img" class="fa fa-fw fa-circle text-warning o_button_icon hr_presence align-middle" invisible="hr_icon_display != 'presence_to_define'" aria-label="To define" title="To define" name="presence_to_define"/>
</div>
<field name="hr_icon_display" class="d-flex align-items-end fs-6 o_employee_availability" widget="hr_presence_status"/>
</div>
</div>
<group>
@@ -300,31 +289,8 @@
</div>
<ul>
<li id="last_login"/>
<li id="presence">
<t t-if="record.show_hr_icon_display.raw_value">
<div class="o_employee_availability d-flex flex-row align-items-start">
<!-- Employee is present/connected and it is normal according to his work schedule -->
<span t-if="record.hr_icon_display.raw_value == 'presence_present'"
class="fa fa-2x fa-circle text-success" name="presence_present" role="img"
aria-label="Present" title="Present"
/>
<!-- Employee is not present/connected and it is normal according to his work schedule -->
<span t-if="record.hr_icon_display.raw_value == 'presence_absent'"
class="fa fa-2x fa-circle-o text-warning" name="presence_absent" role="img"
aria-label="Absent" title="Absent"
/>
<!-- Employee is connected but according to his work schedule, he should not work for now -->
<span t-if="record.hr_icon_display.raw_value == 'presence_absent_active'"
class="fa fa-2x fa-circle-o text-success" name="presence_absent_active" role="img"
aria-label="Present but not active" title="Present but not active"
/>
<!-- Employee is not here but according to his work schedule, he should be connected -->
<span t-if="record.hr_icon_display.raw_value == 'presence_to_define'"
class="fa fa-2x fa-circle text-muted" name="presence_to_define" role="img"
aria-label="To define" title="To define"
/>
</div>
</t>
<li t-if="record.show_hr_icon_display.raw_value">
<field name="hr_icon_display" class="o_employee_availability" widget="hr_presence_status" />
</li>
<li t-if="record.work_email.raw_value" class="o_text_overflow">
<i class="fa fa-fw me-2 fa-envelope text-primary" title="Email"/>
@@ -0,0 +1,22 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { HrPresenceStatus } from "@hr/components/hr_presence_status/hr_presence_status";
patch(HrPresenceStatus.prototype, {
get classNames() {
const classNames = super.classNames;
if (this.value.startsWith("presence_holiday")) {
return `${classNames} fa-plane`;
}
return classNames;
},
get color() {
if (this.value.startsWith("presence_holiday")) {
return `text-${this.value === "presence_holiday_present" ? "success" : "warning"}`;
}
return super.color;
},
});
+2 -40
View File
@@ -94,21 +94,6 @@
<xpath expr="//templates" position="before">
<field name="is_absent"/>
</xpath>
<xpath expr="//div[hasclass('o_employee_availability')]" position="inside">
<t t-if="record.hr_icon_display.raw_value.startsWith('presence_holiday')">
<!-- Employee is on time off but he is connected -->
<span t-if="record.hr_icon_display.raw_value == 'presence_holiday_present'"
class="fa fa-2x fa-plane text-success me-1" name="presence_absent_active" role="img"
aria-label="Present but on leave" title="Present but on leave"
/>
<!-- Employee is on time off and he is not connected -->
<span t-if="record.hr_icon_display.raw_value == 'presence_holiday_absent'"
class="fa fa-2x fa-plane text-warning me-1" name="presence_absent" role="img"
aria-label="To define" title="On Time Off"
/>
<span>On Time Off</span>
</t>
</xpath>
</field>
</record>
@@ -124,23 +109,6 @@
<field name="leave_date_to"/>
<field name="is_absent"/>
</xpath>
<xpath expr="//div[hasclass('o_employee_availability')]" position="inside">
<span class="d-flex" style="font-size: 100%"
t-att-class="record.current_leave_state.raw_value=='validate'?'oe_kanban_button oe_kanban_color_3':'oe_kanban_button oe_kanban_color_2'"
t-att-title="luxon.DateTime.fromISO(record.leave_date_from.raw_value).toFormat('ccc d MMM') + ' - ' + luxon.DateTime.fromISO(record.leave_date_to.raw_value).toFormat('ccc d MMM')">
<!-- Employee is absent, in holiday but he is connected -->
<i t-if="record.hr_icon_display.raw_value == 'presence_holiday_present'"
class="fa fa-2x fa-plane text-success me-1" name="presence_absent_active" role="img"
aria-label="Present but on leave" title="Present but on leave"
/>
<!-- Employee is on holiday, not present and not connected -->
<i t-if="record.hr_icon_display.raw_value == 'presence_holiday_absent'"
class="fa fa-2x fa-plane text-warning me-1" name="presence_absent" role="img"
aria-label="To define" title="On Time Off"
/>
<field t-if="record.current_leave_id.raw_value" name="current_leave_id"/>
</span>
</xpath>
</field>
</record>
@@ -151,14 +119,8 @@
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name = "priority" eval="20" />
<field name="arch" type="xml">
<xpath expr="//div[@id='hr_presence_status']" position="attributes">
<attribute name="invisible">not last_activity or not user_id or not id</attribute>
</xpath>
<xpath expr="//div[@id='hr_presence_status']" position="inside">
<!-- Employee is absent, in holiday but he is connected -->
<div role="img" class="fa fa-fw fa-plane text-success o_button_icon hr_presence" invisible="hr_icon_display != 'presence_holiday_present'" aria-label="Present but on leave" title="Present but on leave" name="presence_absent_active"/>
<!-- Employee is on holiday, not present and not connected -->
<div role="img" class="fa fa-fw fa-plane text-warning o_button_icon hr_presence" invisible="hr_icon_display != 'presence_holiday_absent'" aria-label="On Time Off" title="On Time Off" name="presence_absent"/>
<xpath expr="//field[@name='hr_icon_display']" position="attributes">
<attribute name="invisible">not last_activity or not id</attribute>
</xpath>
<xpath expr="//group[@name='managers']" position="inside">
<field name="leave_manager_id" widget="many2one_avatar_user"/>
@@ -0,0 +1,65 @@
/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
import { patch } from "@web/core/utils/patch";
import { HrPresenceStatus, hrPresenceStatus } from "@hr/components/hr_presence_status/hr_presence_status";
patch(HrPresenceStatus.prototype, {
get color() {
if (this.location) {
let color = "text-muted";
if (this.props.record.data.hr_presence_state !== "to_define") {
color = this.props.record.data.hr_presence_state === "present" ? "text-success" : "text-warning";
}
return color;
}
return super.color;
},
get icon() {
if (this.location) {
switch (this.location) {
case "home":
return "fa-home";
case "office":
return "fa-building";
case "other":
return "fa-map-marker";
}
}
return super.icon;
},
get label() {
if (this.location) {
return _t("Working from %s", this.location);
}
return super.label;
},
get location() {
let location = this.value?.split("_")[1] || "";
if (location && !['home', 'office', 'other'].includes(location)) {
location = "";
}
return location;
},
});
const additionalFieldDependencies = [
{ name: "hr_presence_state", type: "selection" },
];
if (typeof hrPresenceStatus.fieldDependencies === "function") {
const oldFieldDependencies = hrPresenceStatus.fieldDependencies;
hrPresenceStatus.fieldDependencies = (widgetInfo) => {
const fieldDependencies = oldFieldDependencies(widgetInfo);
fieldDependencies.push(...additionalFieldDependencies);
return fieldDependencies;
}
} else {
hrPresenceStatus.fieldDependencies = [
...(hrPresenceStatus.fieldDependencies || []),
...additionalFieldDependencies,
];
}
@@ -53,45 +53,8 @@
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.hr_kanban_view_employees"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('o_employee_availability')]" position="inside">
<t t-set="text_color" t-value="'text-muted'"/>
<t t-if="record.hr_presence_state.raw_value !== 'to_define'">
<t t-set="text_color" t-value="record.hr_presence_state.raw_value === 'present' ? 'text-success' : 'text-warning'"/>
</t>
<t t-set="location_map" t-value="{'home': 'fa-home', 'office': 'fa-building', 'other': 'fa-map-marker'}"/>
<t t-set="location" t-value="record.hr_icon_display.raw_value.split('_')[1]"/>
<t t-set="icon" t-value="location_map[location]"/>
<t t-if="location !== 'holiday'">
<span class="fa fa-2x" t-attf-class="{{icon}} {{text_color}} d-flex me-2" role="img"
t-attf-aria-label="Working from {{location}}" t-attf-title="Working from {{location}}"
t-attf-name="presence {{record.hr_presence_state.raw_value}} at {{location}}"
/>
<field name="name_work_location_display" string="Work Location"/>
</t>
</xpath>
</field>
</record>
<record id="hr_employee_public_view_kanban" model="ir.ui.view">
<field name="name">hr.employee.kanban.inherit.homeworking</field>
<field name="model">hr.employee.public</field>
<field name="inherit_id" ref="hr.hr_employee_public_view_kanban"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('o_employee_availability')]" position="inside">
<t t-set="text_color" t-value="text-muted"/>
<t t-if="record.hr_presence_state.raw_value !== 'to_define'">
<t t-set="text_color" t-value="record.hr_presence_state.raw_value === 'present' ? 'text-success' : 'text-warning'"/>
</t>
<t t-set="location_map" t-value="{'home': 'fa-home', 'office': 'fa-building', 'other': 'fa-map-marker'}"/>
<t t-set="location" t-value="record.hr_icon_display.raw_value.split('_')[1]"/>
<t t-set="icon" t-value="location_map[location]"/>
<t t-if="location !== 'holiday'">
<span class="fa fa-2x" t-attf-class="{{icon}} {{text_color}}" role="img"
t-att-aria-label="`Working from ${location}`" t-att-title="`Working from ${location}`"
t-att-name="`presence ${record.hr_presence_state.raw_value} at ${location}`"
/>
</t>
<xpath expr="//field[@name='hr_icon_display'][hasclass('o_employee_availability')]" position="after">
<field name="name_work_location_display" class="ps-2" string="Work Location"/>
</xpath>
</field>
</record>