[IMP] im_livechat: improve session history tree view
This PR improves the livechat session history list view: - Clicking on a session opens the channel in discuss if the user is a member of it. Opens the form view otherwise. - Display duration of the session (last message datetime - first message datetime) - Display country of the visitor part of task-3332872 closes odoo/odoo#125899 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -71,6 +71,7 @@ Help your customers with this chat, and analyse their feedback.
|
||||
'im_livechat/static/src/js/colors_reset_button/*',
|
||||
'im_livechat/static/src/js/im_livechat_chatbot_steps_one2many.js',
|
||||
'im_livechat/static/src/js/im_livechat_chatbot_script_answers_m2m.js',
|
||||
'im_livechat/static/src/views/**/*',
|
||||
'im_livechat/static/src/chat_window/**/*',
|
||||
'im_livechat/static/src/composer/**/*',
|
||||
'im_livechat/static/src/core/**/*',
|
||||
|
||||
@@ -18,6 +18,7 @@ class DiscussChannel(models.Model):
|
||||
|
||||
anonymous_name = fields.Char('Anonymous Name')
|
||||
channel_type = fields.Selection(selection_add=[('livechat', 'Livechat Conversation')], ondelete={'livechat': 'cascade'})
|
||||
duration = fields.Float('Duration', compute='_compute_duration', help='Duration of the session in hours')
|
||||
livechat_active = fields.Boolean('Is livechat ongoing?', help='Livechat session is active until visitor leaves the conversation.')
|
||||
livechat_channel_id = fields.Many2one('im_livechat.channel', 'Channel')
|
||||
livechat_operator_id = fields.Many2one('res.partner', string='Operator')
|
||||
@@ -28,6 +29,13 @@ class DiscussChannel(models.Model):
|
||||
_sql_constraints = [('livechat_operator_id', "CHECK((channel_type = 'livechat' and livechat_operator_id is not null) or (channel_type != 'livechat'))",
|
||||
'Livechat Operator ID is required for a channel of type livechat.')]
|
||||
|
||||
@api.depends('message_ids')
|
||||
def _compute_duration(self):
|
||||
for record in self:
|
||||
start = record.message_ids[-1].date if record.message_ids else record.create_date
|
||||
end = record.message_ids[0].date if record.message_ids else fields.Datetime.now()
|
||||
record.duration = (end - start).total_seconds() / 3600
|
||||
|
||||
def _compute_is_chat(self):
|
||||
super()._compute_is_chat()
|
||||
for record in self:
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { DiscussChannelListController } from "@im_livechat/views/discuss_channel_list/discuss_channel_list_view_controller";
|
||||
|
||||
import { listView } from "@web/views/list/list_view";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const discussChannelListView = {
|
||||
...listView,
|
||||
Controller: DiscussChannelListController,
|
||||
};
|
||||
|
||||
registry.category("views").add("im_livechat.discuss_channel_list", discussChannelListView);
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { createLocalId } from "@mail/utils/common/misc";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ListController } from "@web/views/list/list_controller";
|
||||
|
||||
export class DiscussChannelListController extends ListController {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.threadService = useService("mail.thread");
|
||||
this.store = useService("mail.store");
|
||||
}
|
||||
|
||||
async openRecord(record) {
|
||||
if (!record.data.is_member) {
|
||||
return super.openRecord(record);
|
||||
}
|
||||
const channel =
|
||||
this.store.threads[createLocalId(record.resModel, record.resId)] ??
|
||||
(await this.threadService.fetchChannel(record.resId));
|
||||
if (!channel.is_pinned) {
|
||||
this.threadService.pin(channel);
|
||||
}
|
||||
this.threadService.setDiscussThread(channel);
|
||||
this.actionService.doAction("mail.action_discuss", {
|
||||
name: _t("Discuss"),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,12 @@
|
||||
<field name="name">discuss.channel.tree</field>
|
||||
<field name="model">discuss.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="History" create="false" default_order="create_date desc">
|
||||
<tree js_class="im_livechat.discuss_channel_list" string="History" create="false" default_order="create_date desc">
|
||||
<field name="is_member" invisible="1"/>
|
||||
<field name="create_date" string="Session Date"/>
|
||||
<field name="name" string="Attendees"/>
|
||||
<field name="country_id"/>
|
||||
<field name="duration" widget="float_time" options="{'displaySeconds': True}"/>
|
||||
<field name="message_ids" string="# Messages"/>
|
||||
<field name="rating_last_image" string="Rating" widget="image" options='{"size": [20, 20]}' class="bg-view"/>
|
||||
</tree>
|
||||
|
||||
@@ -72,6 +72,20 @@ export class ThreadService {
|
||||
return thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("@mail/core/common/thread_model").Thread} thread
|
||||
* @param {number} id
|
||||
* @returns {Promise<Thread>}
|
||||
*/
|
||||
async fetchChannel(id) {
|
||||
const [channelData] = await this.orm.call("discuss.channel", "channel_info", [id]);
|
||||
return this.insert({
|
||||
...channelData,
|
||||
model: "discuss.channel",
|
||||
type: channelData.channel.channel_type,
|
||||
});
|
||||
}
|
||||
|
||||
async fetchChannelMembers(thread) {
|
||||
const known_member_ids = thread.channelMembers.map((channelMember) => channelMember.id);
|
||||
const results = await this.rpc("/discuss/channel/members", {
|
||||
@@ -439,6 +453,9 @@ export class ThreadService {
|
||||
}
|
||||
|
||||
unpin(thread) {
|
||||
if (this.store.discuss.threadLocalId === thread.localId) {
|
||||
this.router.replaceState({ active_id: undefined });
|
||||
}
|
||||
if (thread.model !== "discuss.channel") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export class FloatTimeField extends Component {
|
||||
...standardFieldProps,
|
||||
inputType: { type: String, optional: true },
|
||||
placeholder: { type: String, optional: true },
|
||||
displaySeconds: { type: Boolean, optional: true },
|
||||
};
|
||||
static defaultProps = {
|
||||
inputType: "text",
|
||||
@@ -31,7 +32,9 @@ export class FloatTimeField extends Component {
|
||||
}
|
||||
|
||||
get formattedValue() {
|
||||
return formatFloatTime(this.props.record.data[this.props.name]);
|
||||
return formatFloatTime(this.props.record.data[this.props.name], {
|
||||
displaySeconds: this.props.displaySeconds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +51,7 @@ export const floatTimeField = {
|
||||
supportedTypes: ["float"],
|
||||
isEmpty: () => false,
|
||||
extractProps: ({ attrs, options }) => ({
|
||||
displaySeconds: options.displaySeconds,
|
||||
inputType: options.type,
|
||||
placeholder: attrs.placeholder,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user